/* global Signal:false */ /* global Backbone: false */ /* global drawAttention: false */ /* global i18n: false */ /* global isFocused: false */ /* global Signal: false */ /* global storage: false */ /* global Whisper: false */ /* global _: false */ // eslint-disable-next-line func-names (function() { 'use strict'; window.Whisper = window.Whisper || {}; const { Settings } = Signal.Types; const SettingNames = { COUNT: 'count', NAME: 'name', MESSAGE: 'message', }; function filter(text) { return (text || '') .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); } Whisper.Notifications = new (Backbone.Collection.extend({ initialize() { this.isEnabled = false; this.lastNotification = null; // Testing indicated that trying to create/destroy notifications too quickly // resulted in notifications that stuck around forever, requiring the user // to manually close them. This introduces a minimum amount of time between calls, // and batches up the quick successive update() calls we get from an incoming // read sync, which might have a number of messages referenced inside of it. this.fastUpdate = this.update; this.update = _.debounce(this.update, 2000); // make those calls use the debounced function this.on('add', this.update); this.on('remove', this.onRemove); }, update() { if (this.lastNotification) { this.lastNotification.close(); this.lastNotification = null; } const { isEnabled } = this; const isAppFocused = isFocused(); const isAudioNotificationEnabled = storage.get('audio-notification') || false; const isAudioNotificationSupported = Settings.isAudioNotificationSupported(); // const isNotificationGroupingSupported = Settings.isNotificationGroupingSupported(); const numNotifications = this.length; const userSetting = this.getUserSetting(); const status = Signal.Notifications.getStatus({ isAppFocused, isAudioNotificationEnabled, isAudioNotificationSupported, isEnabled, numNotifications, userSetting, }); // window.log.info( // 'Update notifications:', // Object.assign({}, status, { // isNotificationGroupingSupported, // }) // ); if (status.type !== 'ok') { if (status.shouldClearNotifications) { this.reset([]); } return; } let title; let message; let iconUrl; const messagesNotificationCount = this.models.length; // NOTE: i18n has more complex rules for pluralization than just // distinguishing between zero (0) and other (non-zero), // e.g. Russian: // http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html const newMessageCountLabel = `${messagesNotificationCount} ${ messagesNotificationCount === 1 ? i18n('newMessage') : i18n('newMessages') }`; const last = this.last().toJSON(); switch (userSetting) { case SettingNames.COUNT: title = 'Session'; if (messagesNotificationCount > 0) { message = newMessageCountLabel; } else { return; } break; case SettingNames.NAME: { const lastMessageTitle = last.title; title = newMessageCountLabel; // eslint-disable-next-line prefer-destructuring iconUrl = last.iconUrl; if (messagesNotificationCount === 1) { message = `${i18n('notificationFrom')} ${lastMessageTitle}`; } else { message = i18n('notificationMostRecentFrom', lastMessageTitle); } break; } case SettingNames.MESSAGE: if (messagesNotificationCount === 1) { // eslint-disable-next-line prefer-destructuring title = last.title; // eslint-disable-next-line prefer-destructuring message = last.message; } else { title = newMessageCountLabel; message = `${i18n('notificationMostRecent')} ${last.message}`; } // eslint-disable-next-line prefer-destructuring iconUrl = last.iconUrl; break; default: window.log.error( `Error: Unknown user notification setting: '${userSetting}'` ); break; } const shouldHideExpiringMessageBody = last.isExpiringMessage && Signal.OS.isMacOS(); if (shouldHideExpiringMessageBody) { message = i18n('newMessage'); } drawAttention(); this.lastNotification = new Notification(title, { body: window.platform === 'linux' ? filter(message) : message, icon: iconUrl, silent: !status.shouldPlayNotificationSound, }); this.lastNotification.onclick = () => this.trigger('click', last.conversationId, last.id); // We continue to build up more and more messages for our notifications // until the user comes back to our app or closes the app. Then we’ll // clear everything out. The good news is that we'll have a maximum of // 1 notification in the Notification area (something like // ‘10 new messages’) assuming that `Notification::close` does its job. }, getUserSetting() { return storage.get('notification-setting') || SettingNames.MESSAGE; }, onRemove() { // window.log.info('Remove notification'); this.update(); }, clear() { // window.log.info('Remove all notifications'); this.reset([]); this.update(); }, // We don't usually call this, but when the process is shutting down, we should at // least try to remove the notification immediately instead of waiting for the // normal debounce. fastClear() { this.reset([]); this.fastUpdate(); }, enable() { const needUpdate = !this.isEnabled; this.isEnabled = true; if (needUpdate) { this.update(); } }, disable() { this.isEnabled = false; }, }))(); })();