diff --git a/lib/push/apns-notif-creators.js b/lib/push/apns-notif-creators.js index 29eef1cca..ab87660bf 100644 --- a/lib/push/apns-notif-creators.js +++ b/lib/push/apns-notif-creators.js @@ -1,579 +1,579 @@ // @flow import invariant from 'invariant'; import t, { type TInterface } from 'tcomb'; import { type CommonNativeNotifInputData, commonNativeNotifInputDataValidator, } from './android-notif-creators.js'; import { prepareEncryptedAPNsVisualNotifications, prepareEncryptedAPNsSilentNotifications, prepareLargeNotifData, type LargeNotifData, type LargeNotifEncryptionResult, generateBlobHolders, } from './crypto.js'; import { getAPNsNotificationTopic } from '../shared/notif-utils.js'; import { hasMinCodeVersion } from '../shared/version-utils.js'; import type { PlatformDetails } from '../types/device-types.js'; import { messageTypes } from '../types/message-types-enum.js'; import { type NotificationTargetDevice, type EncryptedNotifUtilsAPI, type TargetedAPNsNotification, type APNsVisualNotification, type APNsNotificationHeaders, type SenderDeviceDescriptor, } from '../types/notif-types.js'; import { tShape } from '../utils/validation-utils.js'; export const apnMaxNotificationPayloadByteSize = 4096; export type APNsNotifInputData = $ReadOnly<{ ...CommonNativeNotifInputData, +uniqueID: string, }>; export const apnsNotifInputDataValidator: TInterface = tShape({ ...commonNativeNotifInputDataValidator.meta.props, uniqueID: t.String, }); async function createAPNsVisualNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, inputData: APNsNotifInputData, devices: $ReadOnlyArray, largeNotifToEncryptionResultPromises?: { [string]: Promise, }, ): Promise<{ +targetedNotifications: $ReadOnlyArray, +largeNotifData?: LargeNotifData, }> { const { senderDeviceDescriptor, notifTexts, newRawMessageInfos, threadID, collapseKey, badgeOnly, unreadCount, platformDetails, uniqueID, } = inputData; const canDecryptNonCollapsibleTextIOSNotifs = hasMinCodeVersion( platformDetails, { native: 222 }, ); const isNonCollapsibleTextNotification = newRawMessageInfos.every( newRawMessageInfo => newRawMessageInfo.type === messageTypes.TEXT, ) && !collapseKey; const canDecryptAllIOSNotifs = hasMinCodeVersion(platformDetails, { native: 267, }); const canDecryptIOSNotif = platformDetails.platform === 'ios' && (canDecryptAllIOSNotifs || (isNonCollapsibleTextNotification && canDecryptNonCollapsibleTextIOSNotifs)); const canDecryptMacOSNotifs = platformDetails.platform === 'macos' && hasMinCodeVersion(platformDetails, { web: 47, majorDesktop: 9, }); let apsDictionary = { 'thread-id': threadID, }; if (unreadCount !== undefined && unreadCount !== null) { apsDictionary = { ...apsDictionary, badge: unreadCount, }; } const { merged, ...rest } = notifTexts; // We don't include alert's body on macos because we // handle displaying the notification ourselves and // we don't want macOS to display it automatically. if (!badgeOnly && platformDetails.platform !== 'macos') { apsDictionary = { ...apsDictionary, alert: merged, sound: 'default', }; } if (hasMinCodeVersion(platformDetails, { native: 198 })) { apsDictionary = { ...apsDictionary, 'mutable-content': 1, }; } let notificationPayload = { ...rest, id: uniqueID, threadID, }; let notificationHeaders: APNsNotificationHeaders = { 'apns-topic': getAPNsNotificationTopic(platformDetails), 'apns-id': uniqueID, 'apns-push-type': 'alert', }; if (collapseKey && (canDecryptAllIOSNotifs || canDecryptMacOSNotifs)) { notificationPayload = { ...notificationPayload, collapseID: collapseKey, }; } else if (collapseKey) { notificationHeaders = { ...notificationHeaders, 'apns-collapse-id': collapseKey, }; } const notification = { ...notificationPayload, headers: notificationHeaders, aps: apsDictionary, }; const messageInfos = JSON.stringify(newRawMessageInfos); const copyWithMessageInfos = { ...notification, messageInfos, }; - const notificationSizeValidator = (notif: APNsVisualNotification) => { + const isNotificationSizeValid = (notif: APNsVisualNotification) => { const { headers, ...notifSansHeaders } = notif; return ( encryptedNotifUtilsAPI.getNotifByteSize( JSON.stringify(notifSansHeaders), ) <= apnMaxNotificationPayloadByteSize ); }; const serializeAPNsNotif = (notif: APNsVisualNotification) => { const { headers, ...notifSansHeaders } = notif; return JSON.stringify(notifSansHeaders); }; const shouldBeEncrypted = canDecryptIOSNotif || canDecryptMacOSNotifs; if (!shouldBeEncrypted) { - const notificationToSend = notificationSizeValidator(copyWithMessageInfos) + const notificationToSend = isNotificationSizeValid(copyWithMessageInfos) ? copyWithMessageInfos : notification; const targetedNotifications = devices.map(({ deliveryID }) => ({ notification: notificationToSend, deliveryID, })); return { targetedNotifications, }; } // The `messageInfos` field in notification payload is // not used on MacOS so we can return early. if (platformDetails.platform === 'macos') { const macOSNotifsWithoutMessageInfos = await prepareEncryptedAPNsVisualNotifications( encryptedNotifUtilsAPI, senderDeviceDescriptor, devices, notification, platformDetails.codeVersion, ); const targetedNotifications = macOSNotifsWithoutMessageInfos.map( ({ notification: notif, deliveryID }) => ({ notification: notif, deliveryID, }), ); return { targetedNotifications }; } const notifsWithMessageInfos = await prepareEncryptedAPNsVisualNotifications( encryptedNotifUtilsAPI, senderDeviceDescriptor, devices, copyWithMessageInfos, platformDetails.codeVersion, - notificationSizeValidator, + isNotificationSizeValid, ); const devicesWithExcessiveSizeNoHolders = notifsWithMessageInfos .filter(({ payloadSizeExceeded }) => payloadSizeExceeded) .map(({ cryptoID, deliveryID }) => ({ cryptoID, deliveryID, })); if (devicesWithExcessiveSizeNoHolders.length === 0) { const targetedNotifications = notifsWithMessageInfos.map( ({ notification: notif, deliveryID, encryptedPayloadHash, encryptionOrder, }) => ({ notification: notif, deliveryID, encryptedPayloadHash, encryptionOrder, }), ); return { targetedNotifications, }; } const canQueryBlobService = hasMinCodeVersion(platformDetails, { native: 331, }); let blobHash, blobHolders, encryptionKey, blobUploadError, encryptedCopyWithMessageInfos; const copyWithMessageInfosBlob = serializeAPNsNotif(copyWithMessageInfos); if ( canQueryBlobService && largeNotifToEncryptionResultPromises && largeNotifToEncryptionResultPromises[copyWithMessageInfosBlob] ) { const largeNotifData = await largeNotifToEncryptionResultPromises[copyWithMessageInfosBlob]; blobHash = largeNotifData.blobHash; encryptionKey = largeNotifData.encryptionKey; blobHolders = generateBlobHolders(devicesWithExcessiveSizeNoHolders.length); encryptedCopyWithMessageInfos = largeNotifData.encryptedCopyWithMessageInfos; } else if (canQueryBlobService && largeNotifToEncryptionResultPromises) { largeNotifToEncryptionResultPromises[copyWithMessageInfosBlob] = prepareLargeNotifData( copyWithMessageInfosBlob, encryptedNotifUtilsAPI, ); const largeNotifData = await largeNotifToEncryptionResultPromises[copyWithMessageInfosBlob]; blobHash = largeNotifData.blobHash; encryptionKey = largeNotifData.encryptionKey; blobHolders = generateBlobHolders(devicesWithExcessiveSizeNoHolders.length); encryptedCopyWithMessageInfos = largeNotifData.encryptedCopyWithMessageInfos; } else if (canQueryBlobService) { ({ blobHash, blobHolders, encryptionKey, blobUploadError } = await encryptedNotifUtilsAPI.uploadLargeNotifPayload( copyWithMessageInfosBlob, devicesWithExcessiveSizeNoHolders.length, )); } if (blobUploadError) { console.warn( `Failed to upload payload of notification: ${uniqueID} ` + `due to error: ${blobUploadError}`, ); } let devicesWithExcessiveSize = devicesWithExcessiveSizeNoHolders; let notificationWithBlobMetadata = notification; if ( blobHash && encryptionKey && blobHolders && blobHolders.length === devicesWithExcessiveSize.length ) { notificationWithBlobMetadata = { ...notification, blobHash, encryptionKey, }; devicesWithExcessiveSize = blobHolders.map((holder, idx) => ({ ...devicesWithExcessiveSize[idx], blobHolder: holder, })); } const notifsWithoutMessageInfos = await prepareEncryptedAPNsVisualNotifications( encryptedNotifUtilsAPI, senderDeviceDescriptor, devicesWithExcessiveSize, notificationWithBlobMetadata, platformDetails.codeVersion, ); const targetedNotifsWithMessageInfos = notifsWithMessageInfos .filter(({ payloadSizeExceeded }) => !payloadSizeExceeded) .map( ({ notification: notif, deliveryID, encryptedPayloadHash, encryptionOrder, }) => ({ notification: notif, deliveryID, encryptedPayloadHash, encryptionOrder, }), ); const targetedNotifsWithoutMessageInfos = notifsWithoutMessageInfos.map( ({ notification: notif, deliveryID, encryptedPayloadHash, encryptionOrder, }) => ({ notification: notif, deliveryID, encryptedPayloadHash, encryptionOrder, }), ); const targetedNotifications = [ ...targetedNotifsWithMessageInfos, ...targetedNotifsWithoutMessageInfos, ]; if ( !encryptedCopyWithMessageInfos || !blobHolders || !blobHash || !encryptionKey ) { return { targetedNotifications }; } return { targetedNotifications, largeNotifData: { blobHash, blobHolders, encryptionKey, encryptedCopyWithMessageInfos, }, }; } type APNsNotificationRescindInputData = { +senderDeviceDescriptor: SenderDeviceDescriptor, +rescindID?: string, +badge?: number, +threadID: string, +platformDetails: PlatformDetails, }; async function createAPNsNotificationRescind( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, inputData: APNsNotificationRescindInputData, devices: $ReadOnlyArray, ): Promise<{ +targetedNotifications: $ReadOnlyArray, }> { const { badge, rescindID, threadID, platformDetails, senderDeviceDescriptor, } = inputData; const apnsTopic = getAPNsNotificationTopic(platformDetails); let notification; if ( rescindID && badge !== null && badge !== undefined && hasMinCodeVersion(platformDetails, { native: 198 }) ) { notification = { headers: { 'apns-topic': apnsTopic, 'apns-push-type': 'alert', }, aps: { 'mutable-content': 1, 'badge': badge, }, threadID, notificationId: rescindID, backgroundNotifType: 'CLEAR', setUnreadStatus: true, }; } else if (rescindID && badge !== null && badge !== undefined) { notification = { headers: { 'apns-topic': apnsTopic, 'apns-push-type': 'background', 'apns-priority': 5, }, aps: { 'mutable-content': 1, 'badge': badge, }, threadID, notificationId: rescindID, backgroundNotifType: 'CLEAR', setUnreadStatus: true, }; } else { notification = { headers: { 'apns-topic': apnsTopic, 'apns-push-type': 'alert', }, aps: { 'mutable-content': 1, }, threadID, backgroundNotifType: 'CLEAR', setUnreadStatus: true, }; } const shouldBeEncrypted = hasMinCodeVersion(platformDetails, { native: 233 }); if (!shouldBeEncrypted) { return { targetedNotifications: devices.map(({ deliveryID }) => ({ notification, deliveryID, })), }; } const notifications = await prepareEncryptedAPNsSilentNotifications( encryptedNotifUtilsAPI, senderDeviceDescriptor, devices, notification, platformDetails.codeVersion, ); return { targetedNotifications: notifications.map( ({ deliveryID, notification: notif }) => ({ deliveryID, notification: notif, }), ), }; } type SenderDescriptorWithPlatformDetails = { +senderDeviceDescriptor: SenderDeviceDescriptor, +platformDetails: PlatformDetails, }; type APNsBadgeOnlyNotificationInputData = $ReadOnly< | { ...SenderDescriptorWithPlatformDetails, +badge: string, } | { ...SenderDescriptorWithPlatformDetails, +threadID: string }, >; async function createAPNsBadgeOnlyNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, inputData: APNsBadgeOnlyNotificationInputData, devices: $ReadOnlyArray, ): Promise<{ +targetedNotifications: $ReadOnlyArray, +largeNotifData?: LargeNotifData, }> { const { senderDeviceDescriptor, platformDetails, threadID, badge } = inputData; const shouldBeEncrypted = hasMinCodeVersion(platformDetails, { native: 222, web: 47, majorDesktop: 9, }); const headers: APNsNotificationHeaders = { 'apns-topic': getAPNsNotificationTopic(platformDetails), 'apns-push-type': 'alert', }; let notification; if (shouldBeEncrypted && threadID) { notification = { headers, threadID, aps: { 'mutable-content': 1, }, }; } else if (shouldBeEncrypted && badge !== undefined && badge !== null) { notification = { headers, aps: { 'badge': badge, 'mutable-content': 1, }, }; } else { invariant( badge !== null && badge !== undefined, 'badge update must contain either badge count or threadID', ); notification = { headers, aps: { badge, }, }; } if (!shouldBeEncrypted) { return { targetedNotifications: devices.map(({ deliveryID }) => ({ deliveryID, notification, })), }; } const notifications = await prepareEncryptedAPNsSilentNotifications( encryptedNotifUtilsAPI, senderDeviceDescriptor, devices, notification, platformDetails.codeVersion, ); return { targetedNotifications: notifications.map( ({ deliveryID, notification: notif }) => ({ deliveryID, notification: notif, }), ), }; } export { createAPNsBadgeOnlyNotification, createAPNsNotificationRescind, createAPNsVisualNotification, }; diff --git a/lib/push/crypto.js b/lib/push/crypto.js index 659ef3e45..ff4a6f72a 100644 --- a/lib/push/crypto.js +++ b/lib/push/crypto.js @@ -1,660 +1,660 @@ // @flow import invariant from 'invariant'; import uuid from 'uuid'; import type { PlainTextWebNotification, PlainTextWebNotificationPayload, WebNotification, PlainTextWNSNotification, WNSNotification, AndroidVisualNotification, AndroidVisualNotificationPayload, AndroidBadgeOnlyNotification, AndroidNotificationRescind, NotificationTargetDevice, SenderDeviceDescriptor, EncryptedNotifUtilsAPI, APNsVisualNotification, APNsNotificationRescind, APNsBadgeOnlyNotification, } from '../types/notif-types.js'; import { toBase64URL } from '../utils/base64.js'; async function encryptAndroidNotificationPayload( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, unencryptedPayload: T, - payloadSizeValidator?: ( + isPayloadSizeValid?: ( | T | $ReadOnly<{ ...SenderDeviceDescriptor, +encryptedPayload: string, +type: '1' | '0', }>, ) => boolean, ): Promise<{ +resultPayload: | T | $ReadOnly<{ ...SenderDeviceDescriptor, +encryptedPayload: string, +type: '1' | '0', }>, +payloadSizeExceeded: boolean, +encryptionOrder?: number, }> { try { const unencryptedSerializedPayload = JSON.stringify(unencryptedPayload); if (!unencryptedSerializedPayload) { return { resultPayload: unencryptedPayload, - payloadSizeExceeded: payloadSizeValidator - ? !payloadSizeValidator(unencryptedPayload) + payloadSizeExceeded: isPayloadSizeValid + ? !isPayloadSizeValid(unencryptedPayload) : false, }; } let dbPersistCondition; - if (payloadSizeValidator) { + if (isPayloadSizeValid) { dbPersistCondition = (serializedPayload: string, type: '1' | '0') => - payloadSizeValidator({ + isPayloadSizeValid({ encryptedPayload: serializedPayload, type, ...senderDeviceDescriptor, }); } const { encryptedData: serializedPayload, sizeLimitViolated: dbPersistConditionViolated, encryptionOrder, } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cryptoID, unencryptedSerializedPayload, dbPersistCondition, ); return { resultPayload: { ...senderDeviceDescriptor, encryptedPayload: serializedPayload.body, type: serializedPayload.type ? '1' : '0', }, payloadSizeExceeded: !!dbPersistConditionViolated, encryptionOrder, }; } catch (e) { console.log('Notification encryption failed', e); const resultPayload = { encryptionFailed: '1', ...unencryptedPayload, }; return { resultPayload, - payloadSizeExceeded: payloadSizeValidator - ? !payloadSizeValidator(resultPayload) + payloadSizeExceeded: isPayloadSizeValid + ? !isPayloadSizeValid(resultPayload) : false, }; } } async function encryptAPNsVisualNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, notification: APNsVisualNotification, - notificationSizeValidator?: APNsVisualNotification => boolean, + isNotificationSizeValid?: APNsVisualNotification => boolean, codeVersion?: ?number, blobHolder?: ?string, ): Promise<{ +notification: APNsVisualNotification, +payloadSizeExceeded: boolean, +encryptedPayloadHash?: string, +encryptionOrder?: number, }> { const { id, headers, aps: { badge, alert, sound }, ...rest } = notification; invariant( !headers['apns-collapse-id'], `Collapse ID can't be directly stored in APNsVisualNotification object due ` + `to security reasons. Please put it in payload property`, ); let unencryptedPayload = { ...rest, merged: alert, badge, }; if (blobHolder) { unencryptedPayload = { ...unencryptedPayload, blobHolder }; } try { const unencryptedSerializedPayload = JSON.stringify(unencryptedPayload); let encryptedNotifAps = { 'mutable-content': 1, sound }; if (codeVersion && codeVersion >= 254 && codeVersion % 2 === 0) { encryptedNotifAps = { ...encryptedNotifAps, alert: { body: 'ENCRYPTED' }, }; } let dbPersistCondition; - if (notificationSizeValidator) { + if (isNotificationSizeValid) { dbPersistCondition = (encryptedPayload: string, type: '0' | '1') => - notificationSizeValidator({ + isNotificationSizeValid({ ...senderDeviceDescriptor, id, headers, encryptedPayload, type, aps: encryptedNotifAps, }); } const { encryptedData: serializedPayload, sizeLimitViolated: dbPersistConditionViolated, encryptionOrder, } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cryptoID, unencryptedSerializedPayload, dbPersistCondition, ); const encryptedPayloadHash = await encryptedNotifUtilsAPI.getEncryptedNotifHash( serializedPayload.body, ); return { notification: { ...senderDeviceDescriptor, id, headers, encryptedPayload: serializedPayload.body, type: serializedPayload.type ? '1' : '0', aps: encryptedNotifAps, }, payloadSizeExceeded: !!dbPersistConditionViolated, encryptedPayloadHash, encryptionOrder, }; } catch (e) { console.log('Notification encryption failed', e); const unencryptedNotification = { ...notification, encryptionFailed: '1' }; return { notification: unencryptedNotification, - payloadSizeExceeded: notificationSizeValidator - ? !notificationSizeValidator(unencryptedNotification) + payloadSizeExceeded: isNotificationSizeValid + ? !isNotificationSizeValid(unencryptedNotification) : false, }; } } async function encryptAPNsSilentNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, notification: APNsNotificationRescind | APNsBadgeOnlyNotification, codeVersion?: ?number, ): Promise<{ +notification: APNsNotificationRescind | APNsBadgeOnlyNotification, +encryptedPayloadHash?: string, +encryptionOrder?: number, }> { const { headers, aps: { badge }, ...rest } = notification; let unencryptedPayload = { ...rest, }; if (badge !== null && badge !== undefined) { unencryptedPayload = { ...unencryptedPayload, badge, aps: {} }; } try { const unencryptedSerializedPayload = JSON.stringify(unencryptedPayload); let encryptedNotifAps = { 'mutable-content': 1 }; if (codeVersion && codeVersion >= 254 && codeVersion % 2 === 0) { encryptedNotifAps = { ...encryptedNotifAps, alert: { body: 'ENCRYPTED' }, }; } const { encryptedData: serializedPayload, encryptionOrder } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cryptoID, unencryptedSerializedPayload, ); const encryptedPayloadHash = await encryptedNotifUtilsAPI.getEncryptedNotifHash( serializedPayload.body, ); return { notification: { ...senderDeviceDescriptor, headers, encryptedPayload: serializedPayload.body, type: serializedPayload.type ? '1' : '0', aps: encryptedNotifAps, }, encryptedPayloadHash, encryptionOrder, }; } catch (e) { console.log('Notification encryption failed', e); const unencryptedNotification = { ...notification, encryptionFailed: '1' }; return { notification: unencryptedNotification, }; } } async function encryptAndroidVisualNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceDescriptor: SenderDeviceDescriptor, cryptoID: string, notification: AndroidVisualNotification, - notificationSizeValidator?: AndroidVisualNotification => boolean, + isNotificationSizeValid?: AndroidVisualNotification => boolean, blobHolder?: ?string, ): Promise<{ +notification: AndroidVisualNotification, +payloadSizeExceeded: boolean, +encryptionOrder?: number, }> { const { id, ...rest } = notification.data; let unencryptedData = {}; if (id) { unencryptedData = { id }; } let unencryptedPayload = rest; if (blobHolder) { unencryptedPayload = { ...unencryptedPayload, blobHolder }; } - let payloadSizeValidator; - if (notificationSizeValidator) { - payloadSizeValidator = ( + let isPayloadSizeValid; + if (isNotificationSizeValid) { + isPayloadSizeValid = ( payload: | AndroidVisualNotificationPayload | $ReadOnly<{ ...SenderDeviceDescriptor, +encryptedPayload: string, +type: '0' | '1', }>, ) => { - return notificationSizeValidator({ + return isNotificationSizeValid({ data: { ...unencryptedData, ...payload }, }); }; } const { resultPayload, payloadSizeExceeded, encryptionOrder } = await encryptAndroidNotificationPayload( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, unencryptedPayload, - payloadSizeValidator, + isPayloadSizeValid, ); return { notification: { data: { ...unencryptedData, ...resultPayload, }, }, payloadSizeExceeded, encryptionOrder, }; } async function encryptAndroidSilentNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, notification: AndroidNotificationRescind | AndroidBadgeOnlyNotification, ): Promise { // We don't validate payload size for rescind // since they are expected to be small and // never exceed any FCM limit const { ...unencryptedPayload } = notification.data; const { resultPayload } = await encryptAndroidNotificationPayload( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, unencryptedPayload, ); if (resultPayload.encryptedPayload) { return { data: { ...resultPayload }, }; } if (resultPayload.rescind) { return { data: { ...resultPayload }, }; } return { data: { ...resultPayload, }, }; } async function encryptBasicPayload( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, basicPayload: T, ): Promise< | $ReadOnly<{ ...SenderDeviceDescriptor, +encryptedPayload: string, +type: '1' | '0', +encryptionOrder?: number, }> | { ...T, +encryptionFailed: '1' }, > { const unencryptedSerializedPayload = JSON.stringify(basicPayload); if (!unencryptedSerializedPayload) { return { ...basicPayload, encryptionFailed: '1' }; } try { const { encryptedData: serializedPayload, encryptionOrder } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cryptoID, unencryptedSerializedPayload, ); return { ...senderDeviceDescriptor, encryptedPayload: serializedPayload.body, type: serializedPayload.type ? '1' : '0', encryptionOrder, }; } catch (e) { console.log('Notification encryption failed', e); return { ...basicPayload, encryptionFailed: '1', }; } } async function encryptWebNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, notification: PlainTextWebNotification, ): Promise<{ +notification: WebNotification, +encryptionOrder?: number }> { const { id, ...payloadSansId } = notification; const { encryptionOrder, ...encryptionResult } = await encryptBasicPayload( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, payloadSansId, ); return { notification: { id, ...encryptionResult }, encryptionOrder, }; } async function encryptWNSNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, notification: PlainTextWNSNotification, ): Promise<{ +notification: WNSNotification, +encryptionOrder?: number }> { const { encryptionOrder, ...encryptionResult } = await encryptBasicPayload( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, notification, ); return { notification: { ...encryptionResult }, encryptionOrder, }; } function prepareEncryptedAPNsVisualNotifications( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: APNsVisualNotification, codeVersion?: ?number, - notificationSizeValidator?: APNsVisualNotification => boolean, + isNotificationSizeValid?: APNsVisualNotification => boolean, ): Promise< $ReadOnlyArray<{ +cryptoID: string, +deliveryID: string, +notification: APNsVisualNotification, +payloadSizeExceeded: boolean, +encryptedPayloadHash?: string, +encryptionOrder?: number, }>, > { const notificationPromises = devices.map( async ({ cryptoID, deliveryID, blobHolder }) => { const notif = await encryptAPNsVisualNotification( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, notification, - notificationSizeValidator, + isNotificationSizeValid, codeVersion, blobHolder, ); return { cryptoID, deliveryID, ...notif }; }, ); return Promise.all(notificationPromises); } function prepareEncryptedAPNsSilentNotifications( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: APNsNotificationRescind | APNsBadgeOnlyNotification, codeVersion?: ?number, ): Promise< $ReadOnlyArray<{ +cryptoID: string, +deliveryID: string, +notification: APNsNotificationRescind | APNsBadgeOnlyNotification, }>, > { const notificationPromises = devices.map(async ({ deliveryID, cryptoID }) => { const { notification: notif } = await encryptAPNsSilentNotification( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, notification, codeVersion, ); return { cryptoID, deliveryID, notification: notif }; }); return Promise.all(notificationPromises); } function prepareEncryptedAndroidVisualNotifications( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: AndroidVisualNotification, - notificationSizeValidator?: ( + isNotificationSizeValid?: ( notification: AndroidVisualNotification, ) => boolean, ): Promise< $ReadOnlyArray<{ +cryptoID: string, +deliveryID: string, +notification: AndroidVisualNotification, +payloadSizeExceeded: boolean, +encryptionOrder?: number, }>, > { const notificationPromises = devices.map( async ({ deliveryID, cryptoID, blobHolder }) => { const notif = await encryptAndroidVisualNotification( encryptedNotifUtilsAPI, senderDeviceDescriptor, cryptoID, notification, - notificationSizeValidator, + isNotificationSizeValid, blobHolder, ); return { deliveryID, cryptoID, ...notif }; }, ); return Promise.all(notificationPromises); } function prepareEncryptedAndroidSilentNotifications( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: AndroidNotificationRescind | AndroidBadgeOnlyNotification, ): Promise< $ReadOnlyArray<{ +cryptoID: string, +deliveryID: string, +notification: AndroidNotificationRescind | AndroidBadgeOnlyNotification, +encryptionOrder?: number, }>, > { const notificationPromises = devices.map(async ({ deliveryID, cryptoID }) => { const notif = await encryptAndroidSilentNotification( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, notification, ); return { deliveryID, cryptoID, notification: notif }; }); return Promise.all(notificationPromises); } function prepareEncryptedWebNotifications( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: PlainTextWebNotification, ): Promise< $ReadOnlyArray<{ +deliveryID: string, +notification: WebNotification, +encryptionOrder?: number, }>, > { const notificationPromises = devices.map(async ({ deliveryID, cryptoID }) => { const notif = await encryptWebNotification( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, notification, ); return { ...notif, deliveryID }; }); return Promise.all(notificationPromises); } function prepareEncryptedWNSNotifications( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: PlainTextWNSNotification, ): Promise< $ReadOnlyArray<{ +deliveryID: string, +notification: WNSNotification, +encryptionOrder?: number, }>, > { const notificationPromises = devices.map(async ({ deliveryID, cryptoID }) => { const notif = await encryptWNSNotification( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, notification, ); return { ...notif, deliveryID }; }); return Promise.all(notificationPromises); } export type LargeNotifEncryptionResult = { +blobHash: string, +encryptionKey: string, +encryptedCopyWithMessageInfos: Uint8Array, }; export type LargeNotifData = $ReadOnly<{ ...LargeNotifEncryptionResult, +blobHolders: $ReadOnlyArray, }>; function generateBlobHolders(numberOfDevices: number): $ReadOnlyArray { return Array.from({ length: numberOfDevices }, () => uuid.v4()); } async function prepareLargeNotifData( copyWithMessageInfos: string, encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, ): Promise { const encryptionKey = await encryptedNotifUtilsAPI.generateAESKey(); const encryptedCopyWithMessageInfos = await encryptedNotifUtilsAPI.encryptWithAESKey( encryptionKey, copyWithMessageInfos, ); const blobHash = await encryptedNotifUtilsAPI.getBlobHash( encryptedCopyWithMessageInfos, ); const blobHashBase64url = toBase64URL(blobHash); return { blobHash: blobHashBase64url, encryptedCopyWithMessageInfos, encryptionKey, }; } export { prepareEncryptedAPNsVisualNotifications, prepareEncryptedAPNsSilentNotifications, prepareEncryptedAndroidVisualNotifications, prepareEncryptedAndroidSilentNotifications, prepareEncryptedWebNotifications, prepareEncryptedWNSNotifications, prepareLargeNotifData, generateBlobHolders, };