diff --git a/tools/test-js-with-node b/tools/test-js-with-node index d785de7631..2ad2d2f5ca 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -151,7 +151,7 @@ EXEMPT_FILES = make_set( "web/src/message_list_view.ts", "web/src/message_lists.ts", "web/src/message_live_update.ts", - "web/src/message_notifications.js", + "web/src/message_notifications.ts", "web/src/message_scroll.js", "web/src/message_scroll_state.ts", "web/src/message_util.ts", diff --git a/web/src/desktop_notifications.ts b/web/src/desktop_notifications.ts index 94a8a4680e..7f2ca66be8 100644 --- a/web/src/desktop_notifications.ts +++ b/web/src/desktop_notifications.ts @@ -21,7 +21,7 @@ export function set_notification_api(n: typeof NotificationAPI): void { NotificationAPI = n; } -class ElectronBridgeNotification extends EventTarget { +export class ElectronBridgeNotification extends EventTarget { title: string; dir: NotificationDirection; lang: string; diff --git a/web/src/message_notifications.js b/web/src/message_notifications.ts similarity index 84% rename from web/src/message_notifications.js rename to web/src/message_notifications.ts index 3cff30ee68..e35d197020 100644 --- a/web/src/message_notifications.js +++ b/web/src/message_notifications.ts @@ -3,8 +3,10 @@ import $ from "jquery"; import * as alert_words from "./alert_words"; import * as blueslip from "./blueslip"; import * as desktop_notifications from "./desktop_notifications"; +import type {ElectronBridgeNotification} from "./desktop_notifications"; import {$t} from "./i18n"; import * as message_parser from "./message_parser"; +import type {Message} from "./message_store"; import * as message_view from "./message_view"; import * as people from "./people"; import * as spoilers from "./spoilers"; @@ -14,7 +16,27 @@ import {user_settings} from "./user_settings"; import * as user_topics from "./user_topics"; import * as util from "./util"; -function get_notification_content(message) { +type TestNotificationMessage = { + id: number; + type: "test-notification"; + sender_email: string; + sender_full_name: string; + display_reply_to: string; + content: string; + unread: boolean; + notification_sent?: boolean; + // Needed for functions that handle Message + is_me_message?: undefined; + sent_by_me?: undefined; + mentioned_me_directly?: undefined; +}; + +function small_avatar_url_for_test_notification(message: TestNotificationMessage): string { + // this is a heavily simplified version of people.small_avatar_url + return people.gravatar_url_for_email(message.sender_email); +} + +function get_notification_content(message: Message | TestNotificationMessage): string { let content; // Convert the content to plain text, replacing emoji with their alt text const $content = $("
").html(message.content); @@ -50,7 +72,7 @@ function get_notification_content(message) { return content; } -function debug_notification_source_value(message) { +function debug_notification_source_value(message: Message | TestNotificationMessage): void { let notification_source; if (message.type === "private" || message.type === "test-notification") { @@ -66,7 +88,7 @@ function debug_notification_source_value(message) { blueslip.debug("Desktop notification from source " + notification_source); } -function get_notification_key(message) { +function get_notification_key(message: Message | TestNotificationMessage): string { let key; if (message.type === "private" || message.type === "test-notification") { @@ -79,13 +101,16 @@ function get_notification_key(message) { return key; } -function remove_sender_from_list_of_recipients(message) { +function remove_sender_from_list_of_recipients(message: Message): string { return `, ${message.display_reply_to}, ` .replace(`, ${message.sender_full_name}, `, ", ") .slice(", ".length, -", ".length); } -function get_notification_title(message, msg_count) { +function get_notification_title( + message: Message | TestNotificationMessage, + msg_count: number, +): string { let title_prefix = message.sender_full_name; let title_suffix = ""; let other_recipients; @@ -139,29 +164,36 @@ function get_notification_title(message, msg_count) { return title_prefix + title_suffix; } -export function process_notification(notification) { +export function process_notification(notification: { + message: Message | TestNotificationMessage; + desktop_notify: boolean; +}): void { const message = notification.message; const content = get_notification_content(message); const key = get_notification_key(message); - let notification_object; + let notification_object: ElectronBridgeNotification | Notification; let msg_count = 1; debug_notification_source_value(message); - if (desktop_notifications.notice_memory.has(key)) { - msg_count = desktop_notifications.notice_memory.get(key).msg_count + 1; - notification_object = desktop_notifications.notice_memory.get(key).obj; + const notice_memory = desktop_notifications.notice_memory.get(key); + if (notice_memory) { + msg_count = notice_memory.msg_count + 1; + notification_object = notice_memory.obj; notification_object.close(); } const title = get_notification_title(message, msg_count); if (notification.desktop_notify && desktop_notifications.NotificationAPI !== undefined) { - const icon_url = people.small_avatar_url(message); + const icon_url = + message.type === "test-notification" + ? small_avatar_url_for_test_notification(message) + : people.small_avatar_url(message); notification_object = new desktop_notifications.NotificationAPI(title, { icon: icon_url, body: content, - tag: message.id, + tag: message.id.toString(), }); desktop_notifications.notice_memory.set(key, { obj: notification_object, @@ -188,7 +220,7 @@ export function process_notification(notification) { } } -export function message_is_notifiable(message) { +export function message_is_notifiable(message: Message | TestNotificationMessage): boolean { // Independent of the user's notification settings, are there // properties of the message that unconditionally mean we // shouldn't notify about it. @@ -235,7 +267,9 @@ export function message_is_notifiable(message) { return true; } -export function should_send_desktop_notification(message) { +export function should_send_desktop_notification( + message: Message | TestNotificationMessage, +): boolean { // Always notify for testing notifications. if (message.type === "test-notification") { return true; @@ -304,7 +338,9 @@ export function should_send_desktop_notification(message) { return false; } -export function should_send_audible_notification(message) { +export function should_send_audible_notification( + message: Message | TestNotificationMessage, +): boolean { // If `None` is selected as the notification sound, never send // audible notifications regardless of other configuration. if (user_settings.notification_sound === "none") { @@ -374,7 +410,7 @@ export function should_send_audible_notification(message) { return false; } -export function received_messages(messages) { +export function received_messages(messages: (Message | TestNotificationMessage)[]): void { for (const message of messages) { if (!message_is_notifiable(message)) { continue; @@ -393,12 +429,12 @@ export function received_messages(messages) { }); } if (should_send_audible_notification(message)) { - ui_util.play_audio($("#user-notification-sound-audio")[0]); + void ui_util.play_audio(util.the($("#user-notification-sound-audio"))); } } } -export function send_test_notification(content) { +export function send_test_notification(content: string): void { received_messages([ { id: Math.random(), diff --git a/web/src/message_store.ts b/web/src/message_store.ts index b1a38f5150..7fc4a214cf 100644 --- a/web/src/message_store.ts +++ b/web/src/message_store.ts @@ -173,6 +173,8 @@ export type Message = ( status_emoji_info?: UserStatusEmojiInfo | undefined; // Used in `message_body.hbs` local_edit_timestamp?: number; // Used for edited messages + + notification_sent?: boolean; // Used in message_notifications } & ( | { type: "private"; diff --git a/web/src/people.ts b/web/src/people.ts index 3bc6e66102..851b5a774a 100644 --- a/web/src/people.ts +++ b/web/src/people.ts @@ -800,7 +800,7 @@ export function user_can_direct_message(recipient_ids_string: string): boolean { return !other_human_recipients_exist; } -function gravatar_url_for_email(email: string): string { +export function gravatar_url_for_email(email: string): string { const hash = md5(email.toLowerCase()); return "https://secure.gravatar.com/avatar/" + hash + "?d=identicon"; } diff --git a/web/tests/notifications.test.js b/web/tests/notifications.test.js index 27c853c411..54ee383336 100644 --- a/web/tests/notifications.test.js +++ b/web/tests/notifications.test.js @@ -402,14 +402,14 @@ test("basic_notifications", () => { n = desktop_notifications.get_notifications(); assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.size, 1); - assert.equal(last_shown_message_id, message_1.id); + assert.equal(last_shown_message_id, message_1.id.toString()); // Remove notification. desktop_notifications.close_notification(message_1); n = desktop_notifications.get_notifications(); assert.equal(n.has("Jesse Pinkman to general > whatever"), false); assert.equal(n.size, 0); - assert.equal(last_closed_message_id, message_1.id); + assert.equal(last_closed_message_id, message_1.id.toString()); // Send notification. message_1.id = 1001; @@ -417,7 +417,7 @@ test("basic_notifications", () => { n = desktop_notifications.get_notifications(); assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.size, 1); - assert.equal(last_shown_message_id, message_1.id); + assert.equal(last_shown_message_id, message_1.id.toString()); // Process same message again. Notification count shouldn't increase. message_1.id = 1002; @@ -425,7 +425,7 @@ test("basic_notifications", () => { n = desktop_notifications.get_notifications(); assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.size, 1); - assert.equal(last_shown_message_id, message_1.id); + assert.equal(last_shown_message_id, message_1.id.toString()); // Send another message. Notification count should increase. message_notifications.process_notification({message: message_2, desktop_notify: true}); @@ -433,7 +433,7 @@ test("basic_notifications", () => { assert.equal(n.has("Gus Fring to general > lunch"), true); assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.size, 2); - assert.equal(last_shown_message_id, message_2.id); + assert.equal(last_shown_message_id, message_2.id.toString()); // Remove notifications. desktop_notifications.close_notification(message_1); @@ -441,5 +441,5 @@ test("basic_notifications", () => { n = desktop_notifications.get_notifications(); assert.equal(n.has("Jesse Pinkman to general > whatever"), false); assert.equal(n.size, 0); - assert.equal(last_closed_message_id, message_2.id); + assert.equal(last_closed_message_id, message_2.id.toString()); });