diff --git a/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js b/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js --- a/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js +++ b/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js @@ -27,11 +27,23 @@ pushType: NotificationPushType; threadId: string; payload: any; - badge: number; + badge: ?number; sound: string; contentAvailable: boolean; mutableContent: boolean; urlArgs: string[]; + // Detailed explanation of this field can be found here: + // https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification?language=objc#2943363 + // More fields can be added here, if they ever need to + // be accessed from apn.Notification instance + aps: { + +badge: string | number, + +alert: string, + +'thread-id': string, + +'mutable-content': boolean, + +sound: string, + ... + }; } declare type ProviderToken = {| diff --git a/keyserver/src/push/crypto.js b/keyserver/src/push/crypto.js new file mode 100644 --- /dev/null +++ b/keyserver/src/push/crypto.js @@ -0,0 +1,78 @@ +// @flow + +import apn from '@parse/node-apn'; +import invariant from 'invariant'; + +import { encryptAndUpdateOlmSession } from '../updaters/olm-session-updater.js'; + +async function encryptIOSNotification( + cookieID: string, + notification: apn.Notification, +): Promise { + invariant( + !notification.collapseId, + 'Collapsible notifications encryption currently not implemented', + ); + + const encryptedNotification = new apn.Notification(); + + encryptedNotification.id = notification.id; + encryptedNotification.payload.id = notification.id; + encryptedNotification.topic = notification.topic; + encryptedNotification.sound = notification.aps.sound; + encryptedNotification.pushType = 'alert'; + encryptedNotification.mutableContent = true; + + const { id, ...payloadSansId } = notification.payload; + const unencryptedPayload = { + ...payloadSansId, + badge: notification.aps.badge.toString(), + merged: notification.body, + }; + + let encryptedSerializedPayload; + try { + const unencryptedSerializedPayload = JSON.stringify(unencryptedPayload); + const { serializedPayload } = await encryptAndUpdateOlmSession( + cookieID, + 'notifications', + { + serializedPayload: unencryptedSerializedPayload, + }, + ); + encryptedSerializedPayload = serializedPayload; + } catch (e) { + console.log('Notification encryption failed: ' + e); + + encryptedNotification.body = notification.body; + encryptedNotification.threadId = notification.payload.threadID; + invariant( + typeof notification.aps.badge === 'number', + 'Unencrypted notification must have badge as a number', + ); + encryptedNotification.badge = notification.aps.badge; + + encryptedNotification.payload = { + ...encryptedNotification.payload, + ...notification.payload, + encryptionFailed: 1, + }; + return encryptedNotification; + } + + encryptedNotification.payload.encryptedPayload = + encryptedSerializedPayload.body; + return encryptedNotification; +} + +function prepareEncryptedIOSNotifications( + cookieIDs: $ReadOnlyArray, + notification: apn.Notification, +): Promise> { + const notificationPromises = cookieIDs.map(cookieID => + encryptIOSNotification(cookieID, notification), + ); + return Promise.all(notificationPromises); +} + +export { prepareEncryptedIOSNotifications };