diff --git a/keyserver/src/push/rescind.js b/keyserver/src/push/rescind.js --- a/keyserver/src/push/rescind.js +++ b/keyserver/src/push/rescind.js @@ -5,7 +5,7 @@ import type { FirebaseError } from 'firebase-admin'; import invariant from 'invariant'; -import { prepareEncryptedAndroidSilentNotifications } from 'lib/push/crypto.js'; +import { createAndroidNotificationRescind } from 'lib/push/android-notif-creators.js'; import type { PlatformDetails } from 'lib/types/device-types.js'; import type { NotificationTargetDevice, @@ -375,29 +375,17 @@ devices: $ReadOnlyArray, ): Promise<$ReadOnlyArray> { threadID = await validateOutput(platformDetails, tID, threadID); - const { codeVersion } = platformDetails; - - const notification = { - data: { + return await createAndroidNotificationRescind( + encryptedNotifUtilsAPI, + { + senderDeviceDescriptor: { keyserverID }, badge: unreadCount.toString(), - rescind: 'true', + platformDetails, rescindID: notifID, - setUnreadStatus: 'true', threadID, }, - }; - const targetedRescinds = await conditionallyEncryptNotification( - encryptedNotifUtilsAPI, - { keyserverID }, - notification, - codeVersion, devices, - prepareEncryptedAndroidSilentNotifications, ); - return targetedRescinds.map(targetedRescind => ({ - ...targetedRescind, - priority: 'normal', - })); } export { rescindPushNotifs }; diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js --- a/keyserver/src/push/send.js +++ b/keyserver/src/push/send.js @@ -16,8 +16,8 @@ type AndroidNotifInputData, androidNotifInputDataValidator, createAndroidVisualNotification, + createAndroidBadgeOnlyNotification, } from 'lib/push/android-notif-creators.js'; -import { prepareEncryptedAndroidSilentNotifications } from 'lib/push/crypto.js'; import { type WebNotifInputData, webNotifInputDataValidator, @@ -1608,39 +1608,21 @@ if (androidVersionsToTokens) { for (const [versionKey, deviceInfos] of androidVersionsToTokens) { const { codeVersion, stateVersion } = stringToVersionKey(versionKey); - const notificationData = { - badge: unreadCount.toString(), - badgeOnly: '1', - }; - const notification = { - data: { ...notificationData }, - }; const preparePromise: Promise = (async () => { - let targetedNotifications: $ReadOnlyArray; - const priority = 'normal'; - if (codeVersion > 222) { - const notificationsArray = - await prepareEncryptedAndroidSilentNotifications( - encryptedNotifUtilsAPI, - { keyserverID }, - deviceInfos, - notification, - ); - targetedNotifications = notificationsArray.map( - ({ notification: notif, deliveryID, encryptionOrder }) => ({ - priority, - notification: notif, - deliveryID, - encryptionOrder, - }), + const targetedNotifications: $ReadOnlyArray = + await createAndroidBadgeOnlyNotification( + encryptedNotifUtilsAPI, + { + senderDeviceDescriptor: { keyserverID }, + badge: unreadCount.toString(), + platformDetails: { + codeVersion, + stateVersion, + platform: 'android', + }, + }, + deviceInfos, ); - } else { - targetedNotifications = deviceInfos.map(({ deliveryID }) => ({ - priority, - deliveryID, - notification, - })); - } return targetedNotifications.map(targetedNotification => ({ notification: targetedNotification, platform: 'android', diff --git a/lib/push/android-notif-creators.js b/lib/push/android-notif-creators.js --- a/lib/push/android-notif-creators.js +++ b/lib/push/android-notif-creators.js @@ -1,9 +1,16 @@ // @flow +import invariant from 'invariant'; import t, { type TInterface } from 'tcomb'; -import { prepareEncryptedAndroidVisualNotifications } from './crypto.js'; -import { hasMinCodeVersion } from '../shared/version-utils.js'; +import { + prepareEncryptedAndroidVisualNotifications, + prepareEncryptedAndroidSilentNotifications, +} from './crypto.js'; +import { + hasMinCodeVersion, + FUTURE_CODE_VERSION, +} from '../shared/version-utils.js'; import type { PlatformDetails } from '../types/device-types.js'; import { messageTypes } from '../types/message-types-enum.js'; import { @@ -19,6 +26,7 @@ type SenderDeviceDescriptor, senderDeviceDescriptorValidator, type EncryptedNotifUtilsAPI, + type AndroidBadgeOnlyNotification, } from '../types/notif-types.js'; import { tID, tPlatformDetails, tShape } from '../utils/validation-utils.js'; @@ -247,5 +255,146 @@ ...targetedNotifsWithoutMessageInfos, ]; } +type AndroidNotificationRescindInputData = { + +senderDeviceDescriptor: SenderDeviceDescriptor, + +threadID: string, + +rescindID?: string, + +badge?: string, + +platformDetails: PlatformDetails, +}; + +async function createAndroidNotificationRescind( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + inputData: AndroidNotificationRescindInputData, + devices: $ReadOnlyArray, +): Promise<$ReadOnlyArray> { + const { + senderDeviceDescriptor, + platformDetails, + threadID, + rescindID, + badge, + } = inputData; + + let notification = { + data: { + rescind: 'true', + setUnreadStatus: 'true', + threadID, + }, + }; + + invariant( + (rescindID && badge) || + hasMinCodeVersion(platformDetails, { native: FUTURE_CODE_VERSION }), + 'thick thread rescind not support for this client version', + ); + + if (rescindID && badge) { + notification = { + ...notification, + data: { + ...notification.data, + badge, + rescindID, + }, + }; + } + + const shouldBeEncrypted = hasMinCodeVersion(platformDetails, { native: 233 }); + if (!shouldBeEncrypted) { + return devices.map(({ deliveryID }) => ({ + notification, + deliveryID, + priority: 'normal', + })); + } + + const notifications = await prepareEncryptedAndroidSilentNotifications( + encryptedNotifUtilsAPI, + senderDeviceDescriptor, + devices, + notification, + ); + + return notifications.map(({ deliveryID, notification: notif }) => ({ + deliveryID, + notification: notif, + priority: 'normal', + })); +} -export { createAndroidVisualNotification }; +type AndroidBadgeOnlyNotificationInputData = $ReadOnly<{ + +senderDeviceDescriptor: SenderDeviceDescriptor, + +platformDetails: PlatformDetails, + ... + | { + +badge: string, + } + | { threadID: string }, +}>; + +async function createAndroidBadgeOnlyNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + inputData: AndroidBadgeOnlyNotificationInputData, + devices: $ReadOnlyArray, +): Promise<$ReadOnlyArray> { + const { senderDeviceDescriptor, platformDetails, badge, threadID } = + inputData; + + invariant( + (!threadID && badge) || + hasMinCodeVersion(platformDetails, { native: FUTURE_CODE_VERSION }), + 'thick thread badge updates not support for this client version', + ); + + let notificationData = { badgeOnly: '1' }; + if (badge) { + notificationData = { + ...notificationData, + badge, + }; + } else { + invariant( + threadID, + 'Either badge or threadID must be present in badge only notif', + ); + notificationData = { + ...notificationData, + threadID, + }; + } + + const notification: AndroidBadgeOnlyNotification = { data: notificationData }; + const shouldBeEncrypted = hasMinCodeVersion(platformDetails, { native: 222 }); + + if (!shouldBeEncrypted) { + return devices.map(({ deliveryID }) => ({ + notification, + deliveryID, + priority: 'normal', + })); + } + + const notifications = await prepareEncryptedAndroidSilentNotifications( + encryptedNotifUtilsAPI, + senderDeviceDescriptor, + devices, + notification, + ); + + return notifications.map( + ({ notification: notif, deliveryID, encryptionOrder }) => ({ + priority: 'normal', + notification: notif, + deliveryID, + encryptionOrder, + }), + ); +} + +export { + createAndroidVisualNotification, + createAndroidBadgeOnlyNotification, + createAndroidNotificationRescind, +}; diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js --- a/lib/types/notif-types.js +++ b/lib/types/notif-types.js @@ -126,30 +126,49 @@ }>, }; +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: $ReadOnly<{ ... - | { - +badge: string, - +rescind: 'true', - +rescindID: string, - +setUnreadStatus: 'true', - +threadID: string, - +encryptionFailed?: string, - } + | AndroidThinThreadRescindPayload + | AndroidThickThreadRescindPayload | EncryptedThinThreadPayload | EncryptedThickThreadPayload, }>, }; +type AndroidKeyserverBadgeOnlyPayload = { + +badge: string, + +badgeOnly: '1', + +encryptionFailed?: string, +}; + +type AndroidThickThreadBadgeOnlyPayload = { + +threadID: string, + +badgeOnly: '1', + +encryptionFailed?: string, +}; + export type AndroidBadgeOnlyNotification = { +data: $ReadOnly<{ ... - | { - +badge: string, - +badgeOnly: '1', - +encryptionFailed?: string, - } + | AndroidKeyserverBadgeOnlyPayload + | AndroidThickThreadBadgeOnlyPayload | EncryptedThinThreadPayload | EncryptedThickThreadPayload, }>,