diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 0ba16913b9..b654b087ab 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -95,7 +95,7 @@ EXEMPT_FILES = make_set( "web/src/demo_organizations_ui.js", "web/src/deprecated_feature_notice.ts", "web/src/desktop_integration.js", - "web/src/desktop_notifications.js", + "web/src/desktop_notifications.ts", "web/src/dialog_widget.ts", "web/src/drafts.ts", "web/src/drafts_overlay_ui.js", diff --git a/web/src/desktop_notifications.js b/web/src/desktop_notifications.js deleted file mode 100644 index a8899305b6..0000000000 --- a/web/src/desktop_notifications.js +++ /dev/null @@ -1,79 +0,0 @@ -import $ from "jquery"; - -export const notice_memory = new Map(); - -export let NotificationAPI; - -export function set_notification_api(n) { - NotificationAPI = n; -} - -if (window.electron_bridge && window.electron_bridge.new_notification) { - class ElectronBridgeNotification extends EventTarget { - constructor(title, options) { - super(); - Object.assign( - this, - window.electron_bridge.new_notification(title, options, (type, eventInit) => - this.dispatchEvent(new Event(type, eventInit)), - ), - ); - } - - static get permission() { - return Notification.permission; - } - - static async requestPermission(callback) { - if (callback) { - callback(await Promise.resolve(Notification.permission)); - } - return Notification.permission; - } - } - - NotificationAPI = ElectronBridgeNotification; -} else if (window.Notification) { - NotificationAPI = window.Notification; -} - -export function get_notifications() { - return notice_memory; -} - -export function initialize() { - $(window).on("focus", () => { - for (const notice_mem_entry of notice_memory.values()) { - notice_mem_entry.obj.close(); - } - notice_memory.clear(); - }); -} - -export function permission_state() { - if (NotificationAPI === undefined) { - // act like notifications are blocked if they do not have access to - // the notification API. - return "denied"; - } - return NotificationAPI.permission; -} - -export function close_notification(message) { - for (const [key, notice_mem_entry] of notice_memory) { - if (notice_mem_entry.message_id === message.id) { - notice_mem_entry.obj.close(); - notice_memory.delete(key); - } - } -} - -export function granted_desktop_notifications_permission() { - return NotificationAPI && NotificationAPI.permission === "granted"; -} - -export function request_desktop_notifications_permission() { - if (NotificationAPI) { - NotificationAPI.requestPermission(); - } -} diff --git a/web/src/desktop_notifications.ts b/web/src/desktop_notifications.ts new file mode 100644 index 0000000000..94a8a4680e --- /dev/null +++ b/web/src/desktop_notifications.ts @@ -0,0 +1,111 @@ +import $ from "jquery"; +import assert from "minimalistic-assert"; + +import type {Message} from "./message_store"; + +type NoticeMemory = Map< + string, + { + obj: Notification | ElectronBridgeNotification; + msg_count: number; + message_id: number; + } +>; + +export const notice_memory: NoticeMemory = new Map(); + +export let NotificationAPI: typeof ElectronBridgeNotification | typeof Notification | undefined; + +// Used for testing +export function set_notification_api(n: typeof NotificationAPI): void { + NotificationAPI = n; +} + +class ElectronBridgeNotification extends EventTarget { + title: string; + dir: NotificationDirection; + lang: string; + body: string; + tag: string; + icon: string; + data: unknown; + close: () => void; + + constructor(title: string, options: NotificationOptions) { + super(); + assert(window.electron_bridge?.new_notification !== undefined); + const notification_data = window.electron_bridge.new_notification( + title, + options, + (type, eventInit) => this.dispatchEvent(new Event(type, eventInit)), + ); + this.title = notification_data.title; + this.dir = notification_data.dir; + this.lang = notification_data.lang; + this.body = notification_data.body; + this.tag = notification_data.tag; + this.icon = notification_data.icon; + this.data = notification_data.data; + this.close = notification_data.close; + } + + static get permission(): NotificationPermission { + return Notification.permission; + } + + static async requestPermission( + callback?: (permission: NotificationPermission) => void, + ): Promise { + if (callback) { + callback(await Promise.resolve(Notification.permission)); + } + return Notification.permission; + } +} + +if (window.electron_bridge?.new_notification) { + NotificationAPI = ElectronBridgeNotification; +} else if (window.Notification) { + NotificationAPI = window.Notification; +} + +export function get_notifications(): NoticeMemory { + return notice_memory; +} + +export function initialize(): void { + $(window).on("focus", () => { + for (const notice_mem_entry of notice_memory.values()) { + notice_mem_entry.obj.close(); + } + notice_memory.clear(); + }); +} + +export function permission_state(): string { + if (NotificationAPI === undefined) { + // act like notifications are blocked if they do not have access to + // the notification API. + return "denied"; + } + return NotificationAPI.permission; +} + +export function close_notification(message: Message): void { + for (const [key, notice_mem_entry] of notice_memory) { + if (notice_mem_entry.message_id === message.id) { + notice_mem_entry.obj.close(); + notice_memory.delete(key); + } + } +} + +export function granted_desktop_notifications_permission(): boolean { + return NotificationAPI?.permission === "granted"; +} + +export function request_desktop_notifications_permission(): void { + if (NotificationAPI) { + void NotificationAPI.requestPermission(); + } +}