You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/util/notifications.ts

261 lines
7.4 KiB
TypeScript

import { debounce, last } from 'lodash';
import { SettingsKey } from '../data/settings-key';
import { getStatus } from '../notifications';
import { UserSetting } from '../notifications/getStatus';
import { isMacOS } from '../OS';
import { isAudioNotificationSupported } from '../types/Settings';
import { isWindowFocused } from './focusListener';
import { Storage } from './storage';
const SettingNames = {
COUNT: 'count',
NAME: 'name',
MESSAGE: 'message',
};
function filter(text?: string) {
return (text || '')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
let sound: any;
export type SessionNotification = {
conversationId: string;
iconUrl: string | null;
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;
}
/**
*
* @param forceRefresh Should only be set when the user triggers a test notification from the settings
*/
function addNotification(notif: SessionNotification, forceRefresh = false) {
const alreadyThere = currentNotifications.find(
n => n.conversationId === notif.conversationId && n.messageId === notif.messageId
);
if (alreadyThere) {
return;
}
currentNotifications.push(notif);
if (forceRefresh) {
update(true);
} else {
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();
}
}
// tslint:disable-next-line: cyclomatic-complexity
function update(forceRefresh = false) {
if (lastNotificationDisplayed) {
lastNotificationDisplayed.close();
lastNotificationDisplayed = null;
}
const isAppFocused = isWindowFocused();
const isAudioNotificationEnabled =
(Storage.get(SettingsKey.settingsAudioNotification) as boolean) || false;
const audioNotificationSupported = isAudioNotificationSupported();
// const isNotificationGroupingSupported = Settings.isNotificationGroupingSupported();
const numNotifications = currentNotifications.length;
const userSetting = getUserSetting();
const status = getStatus({
isAppFocused: forceRefresh ? false : 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();
if (status.shouldPlayNotificationSound) {
if (!sound) {
sound = new Audio('sound/new_message.mp3');
}
void sound.play();
}
lastNotificationDisplayed = new Notification(title || '', {
body: window.platform === 'linux' ? filter(message) : message,
icon: iconUrl || undefined,
silent: true,
});
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 well
// 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') as UserSetting) || SettingNames.MESSAGE;
}
function onRemove() {
// window.log.info('Remove notification');
debouncedUpdate();
}
export const Notifications = {
addNotification,
disable,
enable,
clear,
fastClear,
clearByConversationID,
clearByMessageId,
};