diff --git a/lib/push/crypto.js b/lib/push/crypto.js index 08ab18e2d..5adba10da 100644 --- a/lib/push/crypto.js +++ b/lib/push/crypto.js @@ -1,621 +1,620 @@ // @flow import invariant from 'invariant'; import type { PlainTextWebNotification, PlainTextWebNotificationPayload, WebNotification, PlainTextWNSNotification, WNSNotification, AndroidVisualNotification, AndroidVisualNotificationPayload, AndroidBadgeOnlyNotification, AndroidNotificationRescind, NotificationTargetDevice, SenderDeviceDescriptor, EncryptedNotifUtilsAPI, APNsVisualNotification, APNsNotificationRescind, APNsBadgeOnlyNotification, } from '../types/notif-types.js'; async function encryptAndroidNotificationPayload( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, unencryptedPayload: T, payloadSizeValidator?: ( | 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) : false, }; } let dbPersistCondition; if (payloadSizeValidator) { dbPersistCondition = (serializedPayload: string, type: '1' | '0') => payloadSizeValidator({ 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) : false, }; } } async function encryptAPNsVisualNotification( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cryptoID: string, senderDeviceDescriptor: SenderDeviceDescriptor, notification: APNsVisualNotification, notificationSizeValidator?: 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, - aps: { sound }, merged: alert, badge, }; if (blobHolder) { unencryptedPayload = { ...unencryptedPayload, blobHolder }; } try { const unencryptedSerializedPayload = JSON.stringify(unencryptedPayload); - let encryptedNotifAps = { 'mutable-content': 1 }; + let encryptedNotifAps = { 'mutable-content': 1, sound }; if (codeVersion && codeVersion >= 254 && codeVersion % 2 === 0) { encryptedNotifAps = { ...encryptedNotifAps, alert: { body: 'ENCRYPTED' }, }; } let dbPersistCondition; if (notificationSizeValidator) { dbPersistCondition = (encryptedPayload: string, type: '0' | '1') => notificationSizeValidator({ ...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) : 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, 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 = ( payload: | AndroidVisualNotificationPayload | $ReadOnly<{ ...SenderDeviceDescriptor, +encryptedPayload: string, +type: '0' | '1', }>, ) => { return notificationSizeValidator({ data: { ...unencryptedData, ...payload }, }); }; } const { resultPayload, payloadSizeExceeded, encryptionOrder } = await encryptAndroidNotificationPayload( encryptedNotifUtilsAPI, cryptoID, senderDeviceDescriptor, unencryptedPayload, payloadSizeValidator, ); 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, ): 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, 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?: ( 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, 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 { prepareEncryptedAPNsVisualNotifications, prepareEncryptedAPNsSilentNotifications, prepareEncryptedAndroidVisualNotifications, prepareEncryptedAndroidSilentNotifications, prepareEncryptedWebNotifications, prepareEncryptedWNSNotifications, }; diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js index 5b71ce9d6..29401b86c 100644 --- a/lib/types/notif-types.js +++ b/lib/types/notif-types.js @@ -1,400 +1,411 @@ // @flow import type { EncryptResult } from '@commapp/olm'; import t, { type TInterface, type TUnion } from 'tcomb'; import type { Platform } from './device-types.js'; import type { EntityText, ThreadEntity } from '../utils/entity-text.js'; import { tShape } from '../utils/validation-utils.js'; export type NotifTexts = { +merged: string | EntityText, +body: string | EntityText, +title: string | ThreadEntity, +prefix?: string | EntityText, }; export type ResolvedNotifTexts = { +merged: string, +body: string, +title: string, +prefix?: string, }; export const resolvedNotifTextsValidator: TInterface = tShape({ merged: t.String, body: t.String, title: t.String, prefix: t.maybe(t.String), }); export type SenderDeviceDescriptor = | { +keyserverID: string } | { +senderDeviceID: string }; export const senderDeviceDescriptorValidator: TUnion = t.union([ tShape({ keyserverID: t.String }), tShape({ senderDeviceID: t.String }), ]); // Web notifs types export type PlainTextWebNotificationPayload = { +body: string, +prefix?: string, +title: string, +unreadCount?: number, +threadID: string, +encryptionFailed?: '1', }; export type PlainTextWebNotification = $ReadOnly<{ +id: string, ...PlainTextWebNotificationPayload, }>; export type EncryptedWebNotification = $ReadOnly<{ ...SenderDeviceDescriptor, +id: string, +encryptedPayload: string, +type: '0' | '1', }>; export type WebNotification = | PlainTextWebNotification | EncryptedWebNotification; // WNS notifs types export type PlainTextWNSNotification = { +body: string, +prefix?: string, +title: string, +unreadCount?: number, +threadID: string, +encryptionFailed?: '1', }; export type EncryptedWNSNotification = $ReadOnly<{ ...SenderDeviceDescriptor, +encryptedPayload: string, +type: '0' | '1', }>; export type WNSNotification = | PlainTextWNSNotification | EncryptedWNSNotification; // Android notifs types export type AndroidVisualNotificationPayloadBase = $ReadOnly<{ +badge?: string, +body: string, +title: string, +prefix?: string, +threadID: string, +collapseID?: string, +badgeOnly?: '0' | '1', +encryptionFailed?: '1', }>; type AndroidSmallVisualNotificationPayload = $ReadOnly<{ ...AndroidVisualNotificationPayloadBase, +messageInfos?: string, }>; type AndroidLargeVisualNotificationPayload = $ReadOnly<{ ...AndroidVisualNotificationPayloadBase, +blobHash: string, +encryptionKey: string, }>; export type AndroidVisualNotificationPayload = | AndroidSmallVisualNotificationPayload | AndroidLargeVisualNotificationPayload; type EncryptedThinThreadPayload = { +keyserverID: string, +encryptedPayload: string, +type: '0' | '1', }; type EncryptedThickThreadPayload = { +senderDeviceID: string, +encryptedPayload: string, +type: '0' | '1', }; export type AndroidVisualNotification = { +data: $ReadOnly<{ +id?: string, ... | AndroidSmallVisualNotificationPayload | AndroidLargeVisualNotificationPayload | EncryptedThinThreadPayload | EncryptedThickThreadPayload, }>, }; type AndroidThinThreadRescindPayload = { +badge: string, +rescind: 'true', +rescindID?: string, +setUnreadStatus: 'true', +threadID: string, +encryptionFailed?: string, }; type AndroidThickThreadRescindPayload = { +rescind: 'true', +setUnreadStatus: 'true', +threadID: string, +encryptionFailed?: string, }; export type AndroidNotificationRescind = { +data: | AndroidThinThreadRescindPayload | AndroidThickThreadRescindPayload | EncryptedThinThreadPayload | EncryptedThickThreadPayload, }; type AndroidKeyserverBadgeOnlyPayload = { +badge: string, +badgeOnly: '1', +encryptionFailed?: string, }; type AndroidThickThreadBadgeOnlyPayload = { +threadID: string, +badgeOnly: '1', +encryptionFailed?: string, }; export type AndroidBadgeOnlyNotification = { +data: | AndroidKeyserverBadgeOnlyPayload | AndroidThickThreadBadgeOnlyPayload | EncryptedThinThreadPayload | EncryptedThickThreadPayload, }; type AndroidNotificationWithPriority = | { +notification: AndroidVisualNotification, +priority: 'high', } | { +notification: AndroidBadgeOnlyNotification | AndroidNotificationRescind, +priority: 'normal', }; // APNs notifs types export type APNsNotificationTopic = | 'app.comm.macos' | 'app.comm' | 'org.squadcal.app'; export type APNsNotificationHeaders = { +'apns-priority'?: 1 | 5 | 10, +'apns-id'?: string, +'apns-expiration'?: number, +'apns-topic': APNsNotificationTopic, +'apns-collapse-id'?: string, +'apns-push-type': 'background' | 'alert' | 'voip', }; +type EncryptedAPNsSilentNotificationsAps = { + +'mutable-content': number, + +'alert'?: { body: 'ENCRYPTED' }, +}; + export type EncryptedAPNsSilentNotification = $ReadOnly<{ ...SenderDeviceDescriptor, +headers: APNsNotificationHeaders, +encryptedPayload: string, +type: '1' | '0', - +aps: { +'mutable-content': number, +'alert'?: { body: 'ENCRYPTED' } }, + +aps: EncryptedAPNsSilentNotificationsAps, +}>; + +type EncryptedAPNsVisualNotificationAps = $ReadOnly<{ + ...EncryptedAPNsSilentNotificationsAps, + +sound?: string, }>; export type EncryptedAPNsVisualNotification = $ReadOnly<{ ...EncryptedAPNsSilentNotification, + +aps: EncryptedAPNsVisualNotificationAps, +id: string, }>; type APNsVisualNotificationPayloadBase = { +aps: { +'badge'?: string | number, +'alert'?: string | { +body?: string, ... }, +'thread-id': string, +'mutable-content'?: number, +'sound'?: string, }, +body: string, +title: string, +prefix?: string, +threadID: string, +collapseID?: string, +encryptionFailed?: '1', }; type APNsSmallVisualNotificationPayload = $ReadOnly<{ ...APNsVisualNotificationPayloadBase, +messageInfos?: string, }>; type APNsLargeVisualNotificationPayload = $ReadOnly<{ ...APNsVisualNotificationPayloadBase, +blobHash: string, +encryptionKey: string, }>; export type APNsVisualNotification = | $ReadOnly<{ +headers: APNsNotificationHeaders, +id: string, ... | APNsSmallVisualNotificationPayload | APNsLargeVisualNotificationPayload, }> | EncryptedAPNsVisualNotification; type APNsLegacyRescindPayload = { +backgroundNotifType: 'CLEAR', +notificationId: string, +setUnreadStatus: true, +threadID: string, +aps: { +'badge': string | number, +'content-available': number, }, }; type APNsKeyserverRescindPayload = { +backgroundNotifType: 'CLEAR', +notificationId: string, +setUnreadStatus: true, +threadID: string, +aps: { +'badge': string | number, +'mutable-content': number, }, }; type APNsThickThreadRescindPayload = { +backgroundNotifType: 'CLEAR', +setUnreadStatus: true, +threadID: string, +aps: { +'mutable-content': number, }, }; export type APNsNotificationRescind = | $ReadOnly<{ +headers: APNsNotificationHeaders, +encryptionFailed?: '1', ... | APNsLegacyRescindPayload | APNsKeyserverRescindPayload | APNsThickThreadRescindPayload, }> | EncryptedAPNsSilentNotification; type APNsLegacyBadgeOnlyNotification = { +aps: { +badge: string | number, }, }; type APNsKeyserverBadgeOnlyNotification = { +aps: { +'badge': string | number, +'mutable-content': number, }, }; type APNsThickThreadBadgeOnlyNotification = { +aps: { +'mutable-content': number, }, +threadID: string, }; export type APNsBadgeOnlyNotification = | $ReadOnly<{ +headers: APNsNotificationHeaders, +encryptionFailed?: '1', ... | APNsLegacyBadgeOnlyNotification | APNsKeyserverBadgeOnlyNotification | APNsThickThreadBadgeOnlyNotification, }> | EncryptedAPNsSilentNotification; export type APNsNotification = | APNsVisualNotification | APNsNotificationRescind | APNsBadgeOnlyNotification; export type TargetedAPNsNotification = { +notification: APNsNotification, +deliveryID: string, +encryptedPayloadHash?: string, +encryptionOrder?: number, }; export type TargetedAndroidNotification = $ReadOnly<{ ...AndroidNotificationWithPriority, +deliveryID: string, +encryptionOrder?: number, }>; export type TargetedWebNotification = { +notification: WebNotification, +deliveryID: string, +encryptionOrder?: number, }; export type TargetedWNSNotification = { +notification: WNSNotification, +deliveryID: string, +encryptionOrder?: number, }; export type NotificationTargetDevice = { +cryptoID: string, +deliveryID: string, +blobHolder?: string, }; export type TargetedNotificationWithPlatform = { +platform: Platform, +targetedNotification: | TargetedAPNsNotification | TargetedWNSNotification | TargetedWebNotification | TargetedAndroidNotification, }; export type EncryptedNotifUtilsAPI = { +encryptSerializedNotifPayload: ( cryptoID: string, unencryptedPayload: string, encryptedPayloadSizeValidator?: ( encryptedPayload: string, type: '1' | '0', ) => boolean, ) => Promise<{ +encryptedData: EncryptResult, +sizeLimitViolated?: boolean, +encryptionOrder?: number, }>, +uploadLargeNotifPayload: ( payload: string, numberOfHolders: number, ) => Promise< | { +blobHolders: $ReadOnlyArray, +blobHash: string, +encryptionKey: string, } | { +blobUploadError: string }, >, +getNotifByteSize: (serializedNotification: string) => number, +getEncryptedNotifHash: (serializedNotification: string) => Promise, };