2 * Provides desktop notifications via periodic polling with an
3 * increasing request delay on inactivity.
5 * @author Alexander Ebert
6 * @copyright 2001-2019 WoltLab GmbH
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
8 * @module WoltLabSuite/Core/Notification/Handler
10 define(['Ajax', 'Core', 'EventHandler', 'StringUtil'], function(Ajax
, Core
, EventHandler
, StringUtil
) {
13 if (!('Promise' in window
) || !('Notification' in window
)) {
14 // fake object exposed to ancient browsers (*cough* IE11 *cough*)
20 var _allowNotification
= false;
22 var _inactiveSince
= 0;
23 //noinspection JSUnresolvedVariable
24 var _lastRequestTimestamp
= window
.TIME_NOW
;
25 var _requestTimer
= null;
26 var _sessionKeepAlive
= 0;
29 * @exports WoltLabSuite/Core/Notification/Handler
33 * Initializes the desktop notification system.
35 * @param {Object} options initialization options
37 setup: function (options
) {
38 options
= Core
.extend({
39 enableNotifications
: false,
45 _sessionKeepAlive
= options
.sessionKeepAlive
* 60;
47 this._prepareNextRequest();
49 document
.addEventListener('visibilitychange', this._onVisibilityChange
.bind(this));
50 window
.addEventListener('storage', this._onStorage
.bind(this));
52 this._onVisibilityChange(null);
54 if (options
.enableNotifications
) {
55 switch (window
.Notification
.permission
) {
57 _allowNotification
= true;
60 window
.Notification
.requestPermission(function (result
) {
61 if (result
=== 'granted') {
62 _allowNotification
= true;
71 * Detects when this window is hidden or restored.
73 * @param {Event} event
76 _onVisibilityChange: function(event
) {
77 // document was hidden before
78 if (event
!== null && !document
.hidden
) {
79 var difference
= (Date
.now() - _inactiveSince
) / 60000;
82 this._dispatchRequest();
86 _inactiveSince
= (document
.hidden
) ? Date
.now() : 0;
90 * Returns the delay in minutes before the next request should be dispatched.
95 _getNextDelay: function() {
96 if (_inactiveSince
=== 0) return 5;
98 // milliseconds -> minutes
99 var inactiveMinutes
= ~~((Date
.now() - _inactiveSince
) / 60000);
100 if (inactiveMinutes
< 15) {
103 else if (inactiveMinutes
< 30) {
111 * Resets the request delay timer.
115 _resetTimer: function() {
116 if (_requestTimer
!== null) {
117 window
.clearTimeout(_requestTimer
);
118 _requestTimer
= null;
123 * Schedules the next request using a calculated delay.
127 _prepareNextRequest: function() {
130 var delay
= Math
.min(this._getNextDelay(), _sessionKeepAlive
);
131 _requestTimer
= window
.setTimeout(this._dispatchRequest
.bind(this), delay
* 60000);
135 * Requests new data from the server.
139 _dispatchRequest: function() {
141 EventHandler
.fire('com.woltlab.wcf.notification', 'beforePoll', parameters
);
143 // this timestamp is used to determine new notifications and to avoid
144 // notifications being displayed multiple times due to different origins
145 // (=subdomains) used, because we cannot synchronize them in the client
146 parameters
.lastRequestTimestamp
= _lastRequestTimestamp
;
149 parameters
: parameters
154 * Notifies subscribers for updated data received by another tab.
158 _onStorage: function() {
159 // abort and re-schedule periodic request
160 this._prepareNextRequest();
162 var pollData
, keepAliveData
, abort
= false;
164 pollData
= window
.localStorage
.getItem(Core
.getStoragePrefix() + 'notification');
165 keepAliveData
= window
.localStorage
.getItem(Core
.getStoragePrefix() + 'keepAliveData');
167 pollData
= JSON
.parse(pollData
);
168 keepAliveData
= JSON
.parse(keepAliveData
);
175 EventHandler
.fire('com.woltlab.wcf.notification', 'onStorage', {
177 keepAliveData
: keepAliveData
182 _ajaxSuccess: function(data
) {
184 var keepAliveData
= data
.returnValues
.keepAliveData
;
185 var pollData
= data
.returnValues
.pollData
;
187 // forward keep alive data
188 window
.WCF
.System
.PushNotification
.executeCallbacks({returnValues
: keepAliveData
});
190 // store response data in local storage
192 window
.localStorage
.setItem(Core
.getStoragePrefix() + 'notification', JSON
.stringify(pollData
));
193 window
.localStorage
.setItem(Core
.getStoragePrefix() + 'keepAliveData', JSON
.stringify(keepAliveData
));
196 // storage is unavailable, e.g. in private mode, log error and disable polling
199 window
.console
.log(e
);
203 this._prepareNextRequest();
206 _lastRequestTimestamp
= data
.returnValues
.lastRequestTimestamp
;
208 EventHandler
.fire('com.woltlab.wcf.notification', 'afterPoll', pollData
);
210 this._showNotification(pollData
);
214 * Displays a desktop notification.
216 * @param {Object} pollData
219 _showNotification: function(pollData
) {
220 if (!_allowNotification
) {
224 //noinspection JSUnresolvedVariable
225 if (typeof pollData
.notification
=== 'object' && typeof pollData
.notification
.message
=== 'string') {
226 //noinspection JSUnresolvedVariable
227 var notification
= new window
.Notification(pollData
.notification
.title
, {
228 body
: StringUtil
.unescapeHTML(pollData
.notification
.message
).replace(/ /g, "\u202F"),
231 notification
.onclick = function () {
233 notification
.close();
235 //noinspection JSUnresolvedVariable
236 window
.location
= pollData
.notification
.link
;
241 _ajaxSetup: function() {
242 //noinspection JSUnresolvedVariable
246 className
: 'wcf\\data\\session\\SessionAction'
248 ignoreError
: !window
.ENABLE_DEBUG_MODE
,
249 silent
: !window
.ENABLE_DEBUG_MODE