|  |  |  | @ -0,0 +1,239 @@ | 
		
	
		
			
				|  |  |  |  | import _ from 'lodash'; | 
		
	
		
			
				|  |  |  |  | import { getStatus } from '../notifications'; | 
		
	
		
			
				|  |  |  |  | import { isMacOS } from '../OS'; | 
		
	
		
			
				|  |  |  |  | import { isAudioNotificationSupported } from '../types/Settings'; | 
		
	
		
			
				|  |  |  |  | import { isWindowFocused } from './focusListener'; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | const SettingNames = { | 
		
	
		
			
				|  |  |  |  |   COUNT: 'count', | 
		
	
		
			
				|  |  |  |  |   NAME: 'name', | 
		
	
		
			
				|  |  |  |  |   MESSAGE: 'message', | 
		
	
		
			
				|  |  |  |  | }; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function filter(text?: string) { | 
		
	
		
			
				|  |  |  |  |   return (text || '') | 
		
	
		
			
				|  |  |  |  |     .replace(/&/g, '&') | 
		
	
		
			
				|  |  |  |  |     .replace(/"/g, '"') | 
		
	
		
			
				|  |  |  |  |     .replace(/'/g, ''') | 
		
	
		
			
				|  |  |  |  |     .replace(/</g, '<') | 
		
	
		
			
				|  |  |  |  |     .replace(/>/g, '>'); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | export type SessionNotification = { | 
		
	
		
			
				|  |  |  |  |   conversationId: string; | 
		
	
		
			
				|  |  |  |  |   iconUrl: string; | 
		
	
		
			
				|  |  |  |  |   isExpiringMessage: boolean; | 
		
	
		
			
				|  |  |  |  |   message: string; | 
		
	
		
			
				|  |  |  |  |   messageId?: string; | 
		
	
		
			
				|  |  |  |  |   messageSentAt: number; | 
		
	
		
			
				|  |  |  |  |   title: string; | 
		
	
		
			
				|  |  |  |  | }; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | let isEnabled: boolean = false; | 
		
	
		
			
				|  |  |  |  | let lastNotificationDisplayed: null | Notification = null; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | let currentNotifications: Array<SessionNotification> = []; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // 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.
 | 
		
	
		
			
				|  |  |  |  | const debouncedUpdate = _.debounce(update, 2000); | 
		
	
		
			
				|  |  |  |  | const fastUpdate = update; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function clear() { | 
		
	
		
			
				|  |  |  |  |   // window.log.info('Remove all notifications');
 | 
		
	
		
			
				|  |  |  |  |   currentNotifications = []; | 
		
	
		
			
				|  |  |  |  |   debouncedUpdate(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // 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.
 | 
		
	
		
			
				|  |  |  |  | function fastClear() { | 
		
	
		
			
				|  |  |  |  |   currentNotifications = []; | 
		
	
		
			
				|  |  |  |  |   fastUpdate(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function enable() { | 
		
	
		
			
				|  |  |  |  |   const needUpdate = !isEnabled; | 
		
	
		
			
				|  |  |  |  |   isEnabled = true; | 
		
	
		
			
				|  |  |  |  |   if (needUpdate) { | 
		
	
		
			
				|  |  |  |  |     debouncedUpdate(); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function disable() { | 
		
	
		
			
				|  |  |  |  |   isEnabled = false; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function addNotification(notif: SessionNotification) { | 
		
	
		
			
				|  |  |  |  |   const alreadyThere = currentNotifications.find( | 
		
	
		
			
				|  |  |  |  |     n => n.conversationId === notif.conversationId && n.messageId === notif.messageId | 
		
	
		
			
				|  |  |  |  |   ); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if (alreadyThere) { | 
		
	
		
			
				|  |  |  |  |     return; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   currentNotifications.push(notif); | 
		
	
		
			
				|  |  |  |  |   debouncedUpdate(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function clearByConversationID(convoId: string) { | 
		
	
		
			
				|  |  |  |  |   const oldLength = currentNotifications.length; | 
		
	
		
			
				|  |  |  |  |   currentNotifications = currentNotifications.filter(n => n.conversationId === convoId); | 
		
	
		
			
				|  |  |  |  |   if (oldLength !== currentNotifications.length) { | 
		
	
		
			
				|  |  |  |  |     onRemove(); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function clearByMessageId(messageId: string) { | 
		
	
		
			
				|  |  |  |  |   if (!messageId) { | 
		
	
		
			
				|  |  |  |  |     return; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   const oldLength = currentNotifications.length; | 
		
	
		
			
				|  |  |  |  |   currentNotifications = currentNotifications.filter(n => n.messageId === messageId); | 
		
	
		
			
				|  |  |  |  |   if (oldLength !== currentNotifications.length) { | 
		
	
		
			
				|  |  |  |  |     onRemove(); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | function update() { | 
		
	
		
			
				|  |  |  |  |   if (lastNotificationDisplayed) { | 
		
	
		
			
				|  |  |  |  |     lastNotificationDisplayed.close(); | 
		
	
		
			
				|  |  |  |  |     lastNotificationDisplayed = null; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   const isAppFocused = isWindowFocused(); | 
		
	
		
			
				|  |  |  |  |   const isAudioNotificationEnabled = storage.get('audio-notification') || false; | 
		
	
		
			
				|  |  |  |  |   const audioNotificationSupported = isAudioNotificationSupported(); | 
		
	
		
			
				|  |  |  |  |   // const isNotificationGroupingSupported = Settings.isNotificationGroupingSupported();
 | 
		
	
		
			
				|  |  |  |  |   const numNotifications = currentNotifications.length; | 
		
	
		
			
				|  |  |  |  |   const userSetting = getUserSetting(); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   const status = getStatus({ | 
		
	
		
			
				|  |  |  |  |     isAppFocused, | 
		
	
		
			
				|  |  |  |  |     isAudioNotificationEnabled, | 
		
	
		
			
				|  |  |  |  |     isAudioNotificationSupported: audioNotificationSupported, | 
		
	
		
			
				|  |  |  |  |     isEnabled, | 
		
	
		
			
				|  |  |  |  |     numNotifications, | 
		
	
		
			
				|  |  |  |  |     userSetting, | 
		
	
		
			
				|  |  |  |  |   }); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   // window.log.info(
 | 
		
	
		
			
				|  |  |  |  |   //   'Update notifications:',
 | 
		
	
		
			
				|  |  |  |  |   //   Object.assign({}, status, {
 | 
		
	
		
			
				|  |  |  |  |   //     isNotificationGroupingSupported,
 | 
		
	
		
			
				|  |  |  |  |   //   })
 | 
		
	
		
			
				|  |  |  |  |   // );
 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if (status.type !== 'ok') { | 
		
	
		
			
				|  |  |  |  |     if (status.shouldClearNotifications) { | 
		
	
		
			
				|  |  |  |  |       currentNotifications = []; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     return; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   let title; | 
		
	
		
			
				|  |  |  |  |   let message; | 
		
	
		
			
				|  |  |  |  |   let iconUrl; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   const messagesNotificationCount = currentNotifications.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 ? window.i18n('newMessage') : window.i18n('newMessages') | 
		
	
		
			
				|  |  |  |  |   }`;
 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if (!currentNotifications.length) { | 
		
	
		
			
				|  |  |  |  |     return; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   const lastNotification = _.last(currentNotifications); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if (!lastNotification) { | 
		
	
		
			
				|  |  |  |  |     return; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   switch (userSetting) { | 
		
	
		
			
				|  |  |  |  |     case SettingNames.COUNT: | 
		
	
		
			
				|  |  |  |  |       title = 'Session'; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       if (messagesNotificationCount > 0) { | 
		
	
		
			
				|  |  |  |  |         message = newMessageCountLabel; | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         return; | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       break; | 
		
	
		
			
				|  |  |  |  |     case SettingNames.NAME: { | 
		
	
		
			
				|  |  |  |  |       const lastMessageTitle = lastNotification.title; | 
		
	
		
			
				|  |  |  |  |       title = newMessageCountLabel; | 
		
	
		
			
				|  |  |  |  |       // eslint-disable-next-line prefer-destructuring
 | 
		
	
		
			
				|  |  |  |  |       iconUrl = lastNotification.iconUrl; | 
		
	
		
			
				|  |  |  |  |       if (messagesNotificationCount === 1) { | 
		
	
		
			
				|  |  |  |  |         message = `${window.i18n('notificationFrom')} ${lastMessageTitle}`; | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         message = window.i18n('notificationMostRecentFrom', [lastMessageTitle]); | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       break; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     case SettingNames.MESSAGE: | 
		
	
		
			
				|  |  |  |  |       if (messagesNotificationCount === 1) { | 
		
	
		
			
				|  |  |  |  |         // eslint-disable-next-line prefer-destructuring
 | 
		
	
		
			
				|  |  |  |  |         title = lastNotification.title; | 
		
	
		
			
				|  |  |  |  |         // eslint-disable-next-line prefer-destructuring
 | 
		
	
		
			
				|  |  |  |  |         message = lastNotification.message; | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         title = newMessageCountLabel; | 
		
	
		
			
				|  |  |  |  |         message = `${window.i18n('notificationMostRecent')} ${lastNotification.message}`; | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       // eslint-disable-next-line prefer-destructuring
 | 
		
	
		
			
				|  |  |  |  |       iconUrl = lastNotification.iconUrl; | 
		
	
		
			
				|  |  |  |  |       break; | 
		
	
		
			
				|  |  |  |  |     default: | 
		
	
		
			
				|  |  |  |  |       window.log.error(`Error: Unknown user notification setting: '${userSetting}'`); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   const shouldHideExpiringMessageBody = lastNotification.isExpiringMessage && isMacOS(); | 
		
	
		
			
				|  |  |  |  |   if (shouldHideExpiringMessageBody) { | 
		
	
		
			
				|  |  |  |  |     message = window.i18n('newMessage'); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   window.drawAttention(); | 
		
	
		
			
				|  |  |  |  |   lastNotificationDisplayed = new Notification(title || '', { | 
		
	
		
			
				|  |  |  |  |     body: window.platform === 'linux' ? filter(message) : message, | 
		
	
		
			
				|  |  |  |  |     icon: iconUrl, | 
		
	
		
			
				|  |  |  |  |     silent: !status.shouldPlayNotificationSound, | 
		
	
		
			
				|  |  |  |  |   }); | 
		
	
		
			
				|  |  |  |  |   lastNotificationDisplayed.onclick = () => { | 
		
	
		
			
				|  |  |  |  |     window.openFromNotification(lastNotification.conversationId); | 
		
	
		
			
				|  |  |  |  |   }; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   // 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.
 | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | function getUserSetting() { | 
		
	
		
			
				|  |  |  |  |   return storage.get('notification-setting') || SettingNames.MESSAGE; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | function onRemove() { | 
		
	
		
			
				|  |  |  |  |   // window.log.info('Remove notification');
 | 
		
	
		
			
				|  |  |  |  |   debouncedUpdate(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | export const Notifications = { | 
		
	
		
			
				|  |  |  |  |   addNotification, | 
		
	
		
			
				|  |  |  |  |   disable, | 
		
	
		
			
				|  |  |  |  |   enable, | 
		
	
		
			
				|  |  |  |  |   clear, | 
		
	
		
			
				|  |  |  |  |   fastClear, | 
		
	
		
			
				|  |  |  |  |   clearByConversationID, | 
		
	
		
			
				|  |  |  |  |   clearByMessageId, | 
		
	
		
			
				|  |  |  |  | }; |