diff --git a/desktop/flow-typed/@commapp/windowspush_vx.x.x.js b/desktop/flow-typed/@commapp/windowspush_vx.x.x.js new file mode 100644 --- /dev/null +++ b/desktop/flow-typed/@commapp/windowspush_vx.x.x.js @@ -0,0 +1,5 @@ +// @flow + +declare module '@commapp/windowspush' { + declare module.exports: any; +} diff --git a/desktop/flow-typed/npm/electron_v22.0.0.js b/desktop/flow-typed/npm/electron_v22.0.0.js --- a/desktop/flow-typed/npm/electron_v22.0.0.js +++ b/desktop/flow-typed/npm/electron_v22.0.0.js @@ -10,6 +10,7 @@ hide(): void, show(): void, setName: (name: string) => void, + setAppUserModelId: (id: string) => void, getVersion: () => string, dock: Dock, isPackaged: boolean, @@ -26,6 +27,7 @@ declare type AppEvents = { 'window-all-closed': () => void, 'activate': (event: Event, hasVisibleWindows: boolean) => void, + 'quit': (event: Event, exitCode: number) => void, }; declare export class BrowserWindow { @@ -333,6 +335,7 @@ +actions?: NotificationAction[], +closeButtonText?: string, +toastXml?: string, + +icon?: string, }): void; static isSupported(): boolean; show(): void; diff --git a/desktop/src/handle-squirrel-event.js b/desktop/src/handle-squirrel-event.js --- a/desktop/src/handle-squirrel-event.js +++ b/desktop/src/handle-squirrel-event.js @@ -44,3 +44,8 @@ return false; } + +export function isNormalStartup(): boolean { + const squirrelEvent = process.argv[1]; + return !squirrelEvent || squirrelEvent === '--squirrel-firstrun'; +} diff --git a/desktop/src/main.js b/desktop/src/main.js --- a/desktop/src/main.js +++ b/desktop/src/main.js @@ -255,6 +255,9 @@ const run = () => { app.setName('Comm'); + if (process.platform === 'win32') { + app.setAppUserModelId('Comm'); + } setApplicationMenu(); (async () => { diff --git a/desktop/src/push-notifications.js b/desktop/src/push-notifications.js --- a/desktop/src/push-notifications.js +++ b/desktop/src/push-notifications.js @@ -1,18 +1,83 @@ // @flow // eslint-disable-next-line import/extensions -import { pushNotifications, Notification } from 'electron/main'; +import { app, pushNotifications, Notification } from 'electron/main'; +import EventEmitter from 'events'; +import { resolve } from 'path'; + +import { isNormalStartup } from './handle-squirrel-event.js'; + +let windowsPushNotificationManager; +const windowsPushNotifEventEmitter = new EventEmitter(); +if (process.platform === 'win32' && app.isPackaged && isNormalStartup()) { + (async () => { + try { + const { PushNotificationManager } = await import('@commapp/windowspush'); + + if (!PushNotificationManager.isSupported()) { + return; + } + + windowsPushNotificationManager = PushNotificationManager.default; + + const handleEvent = (manager, event) => { + const byteArray = []; + for (let i = 0; i < event.payload.length; i++) { + byteArray.push(event.payload[i]); + } + const payload = Buffer.from(byteArray).toString('utf-8'); + windowsPushNotifEventEmitter.emit( + 'received-wns-notification', + JSON.parse(payload), + ); + }; + + // Windows requires that this must be called before the call to `register` + windowsPushNotificationManager.addListener('PushReceived', handleEvent); + + windowsPushNotificationManager.register(); + + app.on('quit', () => { + windowsPushNotificationManager.removeListener( + 'PushReceived', + handleEvent, + ); + windowsPushNotificationManager.unregisterAll(); + }); + } catch (err) { + console.error( + `Error while loading windows push notifications ${err.message}`, + ); + } + })(); +} async function registerForNotifications(): Promise { - if (process.platform !== 'darwin') { - return null; - } + if (process.platform === 'darwin') { + try { + const token = await pushNotifications.registerForAPNSNotifications(); + return token; + } catch (err) { + console.error(err); + } + } else if (process.platform === 'win32' && windowsPushNotificationManager) { + try { + const token = await new Promise((resolvePromise, reject) => { + windowsPushNotificationManager.createChannelAsync( + 'f09f4211-a998-40c1-a515-689e3faecb62', + (error, result) => { + if (error) { + reject(error); + } + resolvePromise(result.channel.uri); + }, + ); + }); - try { - const token = await pushNotifications.registerForAPNSNotifications(); - return token; - } catch (err) { - console.error(err); + return token; + } catch (err) { + console.error(err); + } } return null; @@ -30,21 +95,25 @@ return; } const { title, body, threadID } = payload; + const windowsIconPath = resolve(__dirname, '../icons/icon.ico'); const notif = new Notification({ title, body, + icon: process.platform === 'win32' ? windowsIconPath : undefined, }); notif.on('click', () => handleClick(threadID)); notif.show(); } function listenForNotifications(handleClick: (threadID: string) => void) { - if (process.platform !== 'darwin') { - return; + if (process.platform === 'darwin') { + pushNotifications.on('received-apns-notification', (event, userInfo) => { + showNewNotification(userInfo, handleClick); + }); + } else if (process.platform === 'win32') { + windowsPushNotifEventEmitter.on('received-wns-notification', payload => { + showNewNotification(payload, handleClick); + }); } - pushNotifications.on('received-apns-notification', (event, userInfo) => { - showNewNotification(userInfo, handleClick); - }); } - export { listenForNotifications, registerForNotifications };