diff --git a/src/BasePlatform.js b/src/BasePlatform.js index 2b5e89942e..faad50618b 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -36,6 +36,32 @@ export default class BasePlatform { this.errorDidOccur = errorDidOccur; } + /** + * Returns true if the platform supports displaying + * notifications, otherwise false. + */ + supportsNotifications() : boolean { + return false; + } + + /** + * Returns true if the application currently has permission + * to display notifications. Otherwise false. + */ + maySendNotifications() : boolean { + return false; + } + + /** + * Requests permission to send notifications. Returns + * a promise that is resolved when the user has responded + * to the request. The promise has a single string argument + * that is 'granted' if the user allowed the request or + * 'denied' otherwise. + */ + requestNotificationPermission() : Promise { + } + displayNotification(title: string, msg: string, avatarUrl: string) { } } diff --git a/src/Notifier.js b/src/Notifier.js index 4390083129..b9260a046d 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -17,6 +17,7 @@ limitations under the License. 'use strict'; var MatrixClientPeg = require("./MatrixClientPeg"); +var PlatformPeg = require("./PlatformPeg"); var TextForEvent = require('./TextForEvent'); var Avatar = require('./Avatar'); var dis = require("./dispatcher"); @@ -30,13 +31,18 @@ var dis = require("./dispatcher"); */ var Notifier = { + notifsByRoom: {}, notificationMessageForEvent: function(ev) { return TextForEvent.textForEvent(ev); }, _displayPopupNotification: function(ev, room) { - if (!global.Notification || global.Notification.permission != 'granted') { + const plaf = PlatformPeg.get(); + if (!plaf) { + return; + } + if (!plaf.supportsNotifications() || !plaf.maySendNotifications()) { return; } if (global.document.hasFocus()) { @@ -67,26 +73,14 @@ var Notifier = { ev.sender, 40, 40, 'crop' ) : null; - var notification = new global.Notification( - title, - { - "body": msg, - "icon": avatarUrl, - "tag": "matrixreactsdk" - } - ); + const notif = plaf.displayNotification(title, msg, avatarUrl); - notification.onclick = function() { - dis.dispatch({ - action: 'view_room', - room_id: room.roomId - }); - global.focus(); - }; - - global.setTimeout(function() { - notification.close(); - }, 5 * 1000); + // if displayNotification returns non-null, the platform supports + // clearing notifications later, so keep track of this. + if (notif) { + if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = []; + this.notifsByRoom[ev.getRoomId()].push(notif); + } }, _playAudioNotification: function(ev, room) { @@ -100,7 +94,9 @@ var Notifier = { start: function() { this.boundOnRoomTimeline = this.onRoomTimeline.bind(this); this.boundOnSyncStateChange = this.onSyncStateChange.bind(this); + this.boundOnRoomReceipt = this.onRoomReceipt.bind(this); MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline); + MatrixClientPeg.get().on("Room.receipt", this.boundOnRoomReceipt); MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange); this.toolbarHidden = false; this.isPrepared = false; @@ -109,21 +105,20 @@ var Notifier = { stop: function() { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline); + MatrixClientPeg.get().removeListener("Room.receipt", this.boundOnRoomReceipt); MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange); } this.isPrepared = false; }, supportsDesktopNotifications: function() { - return !!global.Notification; - }, - - havePermission: function() { - if (!this.supportsDesktopNotifications()) return false; - return global.Notification.permission == 'granted'; + const plaf = PlatformPeg.get(); + return plaf && plaf.supportsNotifications(); }, setEnabled: function(enable, callback) { + const plaf = PlatformPeg.get(); + if (!plaf) return; // make sure that we persist the current setting audio_enabled setting // before changing anything if (global.localStorage) { @@ -134,7 +129,7 @@ var Notifier = { if (enable) { // Attempt to get permission from user - global.Notification.requestPermission(function(result) { + plaf.requestNotificationPermission().done((result) => { if (result !== 'granted') { // The permission request was dismissed or denied return; @@ -164,7 +159,10 @@ var Notifier = { }, isEnabled: function() { - if (!this.havePermission()) return false; + const plaf = PlatformPeg.get(); + if (!plaf) return false; + if (!plaf.supportsNotifications()) return false; + if (!plaf.maySendNotifications()) return false; if (!global.localStorage) return true; @@ -240,6 +238,24 @@ var Notifier = { this._playAudioNotification(ev, room); } } + }, + + onRoomReceipt: function(ev, room) { + if (room.getUnreadNotificationCount() == 0) { + // ideally we would clear each notification when it was read, + // but we have no way, given a read receipt, to know whether + // the receipt comes before or after an event, so we can't + // do this. Instead, clear all notifications for a room once + // there are no notifs left in that room., which is not quite + // as good but it's something. + const plaf = PlatformPeg.get(); + if (!plaf) return; + if (this.notifsByRoom[room.roomId] === undefined) return; + for (const notif of this.notifsByRoom[room.roomId]) { + plaf.clearNotification(notif); + } + delete this.notifsByRoom[room.roomId]; + } } };