move notifications.js to ts

pull/2239/head
Audric Ackermann 3 years ago
parent 6334f7cb45
commit 747bcb766c
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -35,11 +35,6 @@
<script type="text/javascript" src="js/storage.js"></script>
<script type="text/javascript" src="js/libtextsecure.js"></script>
<script type="text/javascript" src="js/notifications.js"></script>
<script type="text/javascript" src="js/read_syncs.js"></script>
<script type="text/javascript" src="js/registration.js"></script>
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>
<script type="text/javascript" src="js/views/whisper_view.js"></script>

@ -319,7 +319,7 @@
window.setCallMediaPermissions(enabled);
};
Whisper.Notifications.on('click', async conversationKey => {
window.openFromNotification = async conversationKey => {
window.showWindow();
if (conversationKey) {
// do not put the messageId here so the conversation is loaded on the last unread instead
@ -331,6 +331,7 @@
}
});
Whisper.events.on('openInbox', () => {
appView.openInbox({
initialLoadComplete,

@ -2,7 +2,6 @@
const Crypto = require('./crypto');
const Data = require('../../ts/data/data');
const Notifications = require('../../ts/notifications');
const OS = require('../../ts/OS');
const Settings = require('./settings');
const Util = require('../../ts/util');
@ -37,7 +36,6 @@ exports.setup = () => {
Crypto,
Data,
LinkPreviews,
Notifications,
OS,
Settings,
Types,

@ -1,194 +0,0 @@
/* 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, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
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 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.
},
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;
},
}))();
})();

@ -63,6 +63,7 @@ import {
} from '../types/MessageAttachment';
import { getOurPubKeyStrFromCache } from '../session/utils/User';
import { MessageRequestResponse } from '../session/messages/outgoing/controlMessage/MessageRequestResponse';
import { Notifications } from '../util/notifications';
export enum ConversationTypeEnum {
GROUP = 'group',
@ -1066,11 +1067,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
_.defaults(options, { sendReadReceipts: true });
const conversationId = this.id;
window.Whisper.Notifications.remove(
window.Whisper.Notifications.where({
conversationId,
})
);
Notifications.clearByConversationID(conversationId);
let allUnreadMessagesInConvo = (await this.getUnread()).models;
const oldUnreadNowRead = allUnreadMessagesInConvo.filter(
@ -1538,7 +1535,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// isExpiringMessage,
// messageSentAt,
// });
window.Whisper.Notifications.add({
Notifications.addNotification({
conversationId,
iconUrl,
isExpiringMessage,
@ -1569,7 +1566,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const now = Date.now();
const iconUrl = await this.getNotificationIcon();
window.Whisper.Notifications.add({
Notifications.addNotification({
conversationId,
iconUrl,
isExpiringMessage: false,

@ -61,6 +61,7 @@ import {
loadQuoteData,
} from '../types/MessageAttachment';
import { ExpirationTimerOptions } from '../util/expiringMessages';
import { Notifications } from '../util/notifications';
// tslint:disable: cyclomatic-complexity
/**
@ -1088,11 +1089,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
this.set({ expirationStartTimestamp });
}
window.Whisper.Notifications.remove(
window.Whisper.Notifications.where({
messageId: this.id,
})
);
Notifications.clearByMessageId(this.id);
}
public isExpiring() {

@ -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, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
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 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') || SettingNames.MESSAGE;
}
function onRemove() {
// window.log.info('Remove notification');
debouncedUpdate();
}
export const Notifications = {
addNotification,
disable,
enable,
clear,
fastClear,
clearByConversationID,
clearByMessageId,
};

4
ts/window.d.ts vendored

@ -60,8 +60,12 @@ declare global {
versionInfo: any;
getConversations: () => ConversationCollection;
readyForUpdates: () => void;
drawAttention: () => void;
MediaRecorder: any;
platform: string;
openFromNotification: (convoId: string) => void;
contextMenuShown: boolean;
inboxStore?: Store;
openConversationWithMessages: (args: {

Loading…
Cancel
Save