diff --git a/keyserver/src/creators/message-creator.js b/keyserver/src/creators/message-creator.js --- a/keyserver/src/creators/message-creator.js +++ b/keyserver/src/creators/message-creator.js @@ -4,11 +4,8 @@ import _pickBy from 'lodash/fp/pickBy.js'; import { permissionLookup } from 'lib/permissions/thread-permissions.js'; -import { - generateNotifUserInfoPromise, - type Device, - type PushUserInfo, -} from 'lib/push/send-utils.js'; +import { type Device, type PushUserInfo } from 'lib/push/send-utils.js'; +import { generateNotifUserInfoPromise } from 'lib/push/utils.js'; import { rawMessageInfoFromMessageData, shimUnsupportedRawMessageInfos, 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 @@ -19,7 +19,8 @@ createAndroidBadgeOnlyNotification, } from 'lib/push/android-notif-creators.js'; import { apnMaxNotificationPayloadByteSize } from 'lib/push/apns-notif-creators.js'; -import type { Device, PushUserInfo, PushInfo } from 'lib/push/send-utils.js'; +import type { PushUserInfo, PushInfo, Device } from 'lib/push/send-utils.js'; +import { stringToVersionKey, getDevicesByPlatform } from 'lib/push/utils.js'; import { type WebNotifInputData, webNotifInputDataValidator, @@ -825,76 +826,6 @@ return await createIDs('notifications', numIDsNeeded); } -type VersionKey = { - +codeVersion: number, - +stateVersion: number, - +majorDesktopVersion?: number, -}; -const versionKeyRegex: RegExp = new RegExp(/^-?\d+\|-?\d+(\|-?\d+)?$/); -function versionKeyToString(versionKey: VersionKey): string { - const baseStringVersionKey = `${versionKey.codeVersion}|${versionKey.stateVersion}`; - if (!versionKey.majorDesktopVersion) { - return baseStringVersionKey; - } - return `${baseStringVersionKey}|${versionKey.majorDesktopVersion}`; -} - -function stringToVersionKey(versionKeyString: string): VersionKey { - invariant( - versionKeyRegex.test(versionKeyString), - 'should pass correct version key string', - ); - const [codeVersion, stateVersion, majorDesktopVersion] = versionKeyString - .split('|') - .map(Number); - return { codeVersion, stateVersion, majorDesktopVersion }; -} - -function getDevicesByPlatform( - devices: $ReadOnlyArray, -): Map>> { - const byPlatform = new Map< - Platform, - Map>, - >(); - for (const device of devices) { - let innerMap = byPlatform.get(device.platformDetails.platform); - if (!innerMap) { - innerMap = new Map>(); - byPlatform.set(device.platformDetails.platform, innerMap); - } - const codeVersion: number = - device.platformDetails.codeVersion !== null && - device.platformDetails.codeVersion !== undefined - ? device.platformDetails.codeVersion - : -1; - const stateVersion: number = device.platformDetails.stateVersion ?? -1; - - let versionsObject = { codeVersion, stateVersion }; - if (device.platformDetails.majorDesktopVersion) { - versionsObject = { - ...versionsObject, - majorDesktopVersion: device.platformDetails.majorDesktopVersion, - }; - } - - const versionKey = versionKeyToString(versionsObject); - let innerMostArrayTmp: ?Array = - innerMap.get(versionKey); - if (!innerMostArrayTmp) { - innerMostArrayTmp = []; - innerMap.set(versionKey, innerMostArrayTmp); - } - const innerMostArray = innerMostArrayTmp; - - innerMostArray.push({ - cryptoID: device.cryptoID, - deliveryID: device.deliveryID, - }); - } - return byPlatform; -} - type CommonNativeNotifInputData = { +keyserverID: string, +notifTexts: ResolvedNotifTexts, @@ -1518,14 +1449,16 @@ thisKeyserverID(), ]); const unreadCount = unreadCounts[userID]; - const devices = deviceTokenResult.map(row => { + const devices: $ReadOnlyArray = deviceTokenResult.map(row => { const versions = JSON.parse(row.versions); return { - platform: row.platform, - cookieID: row.id, - deviceToken: row.device_token, - codeVersion: versions?.codeVersion, - stateVersion: versions?.stateVersion, + deliveryID: row.device_token, + cryptoID: row.id, + platformDetails: { + platform: row.platform, + codeVersion: versions?.codeVersion, + stateVersion: versions?.stateVersion, + }, }; }); const byPlatform = getDevicesByPlatform(devices); diff --git a/lib/push/send-utils.js b/lib/push/send-utils.js --- a/lib/push/send-utils.js +++ b/lib/push/send-utils.js @@ -1,15 +1,15 @@ // @flow -import invariant from 'invariant'; import _pickBy from 'lodash/fp/pickBy.js'; import uuidv4 from 'uuid/v4.js'; +import { generateNotifUserInfoPromise } from './utils.js'; import { hasPermission } from '../permissions/minimally-encoded-thread-permissions.js'; import { rawMessageInfoFromMessageData, createMessageInfo, } from '../shared/message-utils.js'; -import { type PushType, pushTypes } from '../shared/messages/message-spec.js'; +import { pushTypes } from '../shared/messages/message-spec.js'; import { messageSpecs } from '../shared/messages/message-specs.js'; import { notifTextsForMessageInfo } from '../shared/notif-utils.js'; import { @@ -64,98 +64,6 @@ }; } -type GenerateNotifUserInfoPromiseInputData = { - +pushType: PushType, - +devices: $ReadOnlyArray, - +newMessageInfos: $ReadOnlyArray, - +messageDatas: $ReadOnlyArray, - +threadsToMessageIndices: $ReadOnlyMap, - +threadIDs: $ReadOnlySet, - +userNotMemberOfSubthreads: Set, - +fetchMessageInfoByID: (messageID: string) => Promise, - +userID: string, -}; - -async function generateNotifUserInfoPromise( - inputData: GenerateNotifUserInfoPromiseInputData, -): Promise, - messageDatas: Array, - messageInfos: Array, -}> { - const { - pushType, - devices, - newMessageInfos, - messageDatas, - threadsToMessageIndices, - threadIDs, - userNotMemberOfSubthreads, - userID, - fetchMessageInfoByID, - } = inputData; - - const promises: Array< - Promise, - > = []; - - for (const threadID of threadIDs) { - const messageIndices = threadsToMessageIndices.get(threadID); - invariant(messageIndices, `indices should exist for thread ${threadID}`); - - promises.push( - ...messageIndices.map(async messageIndex => { - const messageInfo = newMessageInfos[messageIndex]; - if (messageInfo.creatorID === userID) { - return undefined; - } - - const { type } = messageInfo; - const { generatesNotifs } = messageSpecs[type]; - - if (!generatesNotifs) { - return undefined; - } - - const messageData = messageDatas[messageIndex]; - const doesGenerateNotif = await generatesNotifs( - messageInfo, - messageData, - { - notifTargetUserID: userID, - userNotMemberOfSubthreads, - fetchMessageInfoByID, - }, - ); - - return doesGenerateNotif === pushType - ? { messageInfo, messageData } - : undefined; - }), - ); - } - - const messagesToNotify = await Promise.all(promises); - const filteredMessagesToNotify = messagesToNotify.filter(Boolean); - - if (filteredMessagesToNotify.length === 0) { - return undefined; - } - - return { - devices, - messageInfos: filteredMessagesToNotify.map( - ({ messageInfo }) => messageInfo, - ), - messageDatas: filteredMessagesToNotify.map( - ({ messageData }) => messageData, - ), - }; -} - async function getPushUserInfo( messageInfos: { +[id: string]: RawMessageInfo }, rawThreadInfos: RawThreadInfos, diff --git a/lib/push/utils.js b/lib/push/utils.js new file mode 100644 --- /dev/null +++ b/lib/push/utils.js @@ -0,0 +1,183 @@ +// @flow + +import invariant from 'invariant'; + +import type { Device } from './send-utils.js'; +import { type PushType } from '../shared/messages/message-spec.js'; +import { messageSpecs } from '../shared/messages/message-specs.js'; +import type { Platform } from '../types/device-types.js'; +import { + type MessageData, + type RawMessageInfo, +} from '../types/message-types.js'; +import type { NotificationTargetDevice } from '../types/notif-types.js'; + +export type VersionKey = { + +codeVersion: number, + +stateVersion: number, + +majorDesktopVersion?: number, +}; +export const versionKeyRegex: RegExp = new RegExp(/^-?\d+\|-?\d+(\|-?\d+)?$/); + +function versionKeyToString(versionKey: VersionKey): string { + const baseStringVersionKey = `${versionKey.codeVersion}|${versionKey.stateVersion}`; + if (!versionKey.majorDesktopVersion) { + return baseStringVersionKey; + } + return `${baseStringVersionKey}|${versionKey.majorDesktopVersion}`; +} + +function stringToVersionKey(versionKeyString: string): VersionKey { + invariant( + versionKeyRegex.test(versionKeyString), + 'should pass correct version key string', + ); + const [codeVersion, stateVersion, majorDesktopVersion] = versionKeyString + .split('|') + .map(Number); + return { codeVersion, stateVersion, majorDesktopVersion }; +} + +function getDevicesByPlatform( + devices: $ReadOnlyArray, +): Map>> { + const byPlatform = new Map< + Platform, + Map>, + >(); + for (const device of devices) { + let innerMap = byPlatform.get(device.platformDetails.platform); + if (!innerMap) { + innerMap = new Map>(); + byPlatform.set(device.platformDetails.platform, innerMap); + } + const codeVersion: number = + device.platformDetails.codeVersion !== null && + device.platformDetails.codeVersion !== undefined + ? device.platformDetails.codeVersion + : -1; + const stateVersion: number = device.platformDetails.stateVersion ?? -1; + + let versionsObject = { codeVersion, stateVersion }; + if (device.platformDetails.majorDesktopVersion) { + versionsObject = { + ...versionsObject, + majorDesktopVersion: device.platformDetails.majorDesktopVersion, + }; + } + + const versionKey = versionKeyToString(versionsObject); + let innerMostArrayTmp: ?Array = + innerMap.get(versionKey); + if (!innerMostArrayTmp) { + innerMostArrayTmp = []; + innerMap.set(versionKey, innerMostArrayTmp); + } + const innerMostArray = innerMostArrayTmp; + + innerMostArray.push({ + cryptoID: device.cryptoID, + deliveryID: device.deliveryID, + }); + } + return byPlatform; +} + +type GenerateNotifUserInfoPromiseInputData = { + +pushType: PushType, + +devices: $ReadOnlyArray, + +newMessageInfos: $ReadOnlyArray, + +messageDatas: $ReadOnlyArray, + +threadsToMessageIndices: $ReadOnlyMap, + +threadIDs: $ReadOnlySet, + +userNotMemberOfSubthreads: Set, + +fetchMessageInfoByID: (messageID: string) => Promise, + +userID: string, +}; + +async function generateNotifUserInfoPromise( + inputData: GenerateNotifUserInfoPromiseInputData, +): Promise, + messageDatas: Array, + messageInfos: Array, +}> { + const { + pushType, + devices, + newMessageInfos, + messageDatas, + threadsToMessageIndices, + threadIDs, + userNotMemberOfSubthreads, + userID, + fetchMessageInfoByID, + } = inputData; + + const promises: Array< + Promise, + > = []; + + for (const threadID of threadIDs) { + const messageIndices = threadsToMessageIndices.get(threadID); + invariant(messageIndices, `indices should exist for thread ${threadID}`); + + promises.push( + ...messageIndices.map(async messageIndex => { + const messageInfo = newMessageInfos[messageIndex]; + if (messageInfo.creatorID === userID) { + return undefined; + } + + const { type } = messageInfo; + const { generatesNotifs } = messageSpecs[type]; + + if (!generatesNotifs) { + return undefined; + } + + const messageData = messageDatas[messageIndex]; + const doesGenerateNotif = await generatesNotifs( + messageInfo, + messageData, + { + notifTargetUserID: userID, + userNotMemberOfSubthreads, + fetchMessageInfoByID, + }, + ); + + return doesGenerateNotif === pushType + ? { messageInfo, messageData } + : undefined; + }), + ); + } + + const messagesToNotify = await Promise.all(promises); + const filteredMessagesToNotify = messagesToNotify.filter(Boolean); + + if (filteredMessagesToNotify.length === 0) { + return undefined; + } + + return { + devices, + messageInfos: filteredMessagesToNotify.map( + ({ messageInfo }) => messageInfo, + ), + messageDatas: filteredMessagesToNotify.map( + ({ messageData }) => messageData, + ), + }; +} + +export { + stringToVersionKey, + versionKeyToString, + getDevicesByPlatform, + generateNotifUserInfoPromise, +};