Abstract desktop notifications to a class
[KiwiIRC.git] / client / src / helpers / desktopnotifications.js
diff --git a/client/src/helpers/desktopnotifications.js b/client/src/helpers/desktopnotifications.js
new file mode 100644 (file)
index 0000000..bf1de23
--- /dev/null
@@ -0,0 +1,126 @@
+_kiwi.global.utils.notifications = (function () {
+    if (!window.Notification) {
+        return {
+            allowed: _.constant(false),
+            requestPermission: _.constant($.Deferred().reject())
+        };
+    }
+
+    var notifications = {
+        /**
+         * Check if desktop notifications have been allowed by the user.
+         *
+         * @returns {?Boolean} `true`  - they have been allowed.
+         *                     `false` - they have been blocked.
+         *                     `null`  - the user hasn't answered yet.
+         */
+        allowed: function () {
+            return Notification.permission === 'granted' ? true
+                 : Notification.permission === 'denied' ? false
+                 : null;
+        },
+
+        /**
+         * Ask the user their permission to display desktop notifications.
+         * This will return a promise which will be resolved if the user allows notifications, or rejected if they blocked
+         * notifictions or simply closed the dialog. If the user had previously given their preference, the promise will be
+         * immediately resolved or rejected with their previous answer.
+         *
+         * @example
+         *   notifications.requestPermission().then(function () { 'allowed' }, function () { 'not allowed' });
+         *
+         * @returns {Promise}
+         */
+        requestPermission: function () {
+            var deferred = $.Deferred();
+            Notification.requestPermission(function (permission) {
+                deferred[(permission === 'granted') ? 'resolve' : 'reject']();
+            });
+            return deferred.promise();
+        },
+
+        /**
+         * Create a new notification. If the user has not yet given permission to display notifications, they will be asked
+         * to confirm first. The notification will show afterwards if they allow it.
+         *
+         * Notifications implement Backbone.Events (so you can use `on` and `off`). They trigger four different events:
+         *   - 'click'
+         *   - 'close'
+         *   - 'error'
+         *   - 'show'
+         *
+         * @example
+         *   notifications
+         *     .create('Cool notification', { icon: 'logo.png' })
+         *     .on('click', function () {
+         *       window.focus();
+         *     })
+         *     .closeAfter(5000);
+         *
+         * @param   {String}  title
+         * @param   {Object}  options
+         * @param   {String=} options.body  A string representing an extra content to display within the notification
+         * @param   {String=} options.dir   The direction of the notification; it can be auto, ltr, or rtl
+         * @param   {String=} options.lang  Specify the lang used within the notification. This string must be a valid BCP
+         *                                  47 language tag.
+         * @param   {String=} options.tag   An ID for a given notification that allows to retrieve, replace or remove it if necessary
+         * @param   {String=} options.icon  The URL of an image to be used as an icon by the notification
+         * @returns {Notifier}
+         */
+        create: function (title, options) {
+            return new Notifier(title, options);
+        }
+    };
+
+    function Notifier(title, options) {
+        createNotification.call(this, title, options);
+    }
+    _.extend(Notifier.prototype, Backbone.Events, {
+        closed: false,
+        _closeTimeout: null,
+
+        /**
+         * Close the notification after a given number of milliseconds.
+         * @param   {Number} timeout
+         * @returns {this}
+         */
+        closeAfter: function (timeout) {
+            if (!this.closed) {
+                if (this.notification) {
+                    this._closeTimeout = this._closeTimeout || setTimeout(_.bind(this.close, this), timeout);
+                } else {
+                    this.once('show', _.bind(this.closeAfter, this, timeout));
+                }
+            }
+            return this;
+        },
+
+        /**
+         * Close the notification immediately.
+         * @returns {this}
+         */
+        close: function () {
+            if (this.notification && !this.closed) {
+                this.notification.close();
+                this.closed = true;
+            }
+            return this;
+        }
+    });
+
+    function createNotification(title, options) {
+        switch (notifications.allowed()) {
+            case true:
+                this.notification = new Notification(title, options);
+                _.each(['click', 'close', 'error', 'show'], function (eventName) {
+                    this.notification['on' + eventName] = _.bind(this.trigger, this, eventName);
+                }, this);
+                break;
+            case null:
+                notifications.requestPermission().done(_.bind(createNotification, this, title, options));
+                break;
+        }
+    }
+
+    return notifications;
+}());