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 @@ -39,6 +39,7 @@ } from '../fetchers/message-fetchers.js'; import { fetchOtherSessionsForViewer } from '../fetchers/session-fetchers.js'; import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; +import type { Device } from '../push/send'; import { sendPushNotifs, sendRescindNotifs } from '../push/send.js'; import { handleAsyncPromise } from '../responders/handlers.js'; import type { Viewer } from '../session/viewer.js'; @@ -47,15 +48,7 @@ import { creationString } from '../utils/idempotent.js'; type UserThreadInfo = { - +devices: Map< - string, - { - +platform: string, - +deviceToken: string, - +cookieID: string, - +codeVersion: ?string, - }, - >, + +devices: Map, +threadIDs: Set, +notFocusedThreadIDs: Set, +userNotMemberOfSubthreads: Set, @@ -391,6 +384,7 @@ deviceToken, cookieID: cookieID.toString(), codeVersion: versions ? versions.codeVersion : null, + stateVersion: versions ? versions.stateVersion : null, }); } thisUserInfo.threadIDs.add(threadID); 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 @@ -75,11 +75,12 @@ import { getENSNames } from '../utils/ens-cache.js'; import { validateOutput } from '../utils/validation-utils.js'; -type Device = { +export type Device = { +platform: Platform, +deviceToken: string, +cookieID: string, +codeVersion: ?number, + +stateVersion: ?number, }; type PushUserInfo = { @@ -206,8 +207,14 @@ const iosVersionsToTokens = byPlatform.get('ios'); if (iosVersionsToTokens) { - for (const [codeVersion, devices] of iosVersionsToTokens) { - const platformDetails = { platform: 'ios', codeVersion }; + for (const [versionKey, devices] of iosVersionsToTokens) { + const { codeVersion, stateVersion } = stringToVersionKey(versionKey); + + const platformDetails: PlatformDetails = { + platform: 'ios', + codeVersion, + stateVersion, + }; const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( newRawMessageInfos, platformDetails, @@ -235,8 +242,13 @@ } const androidVersionsToTokens = byPlatform.get('android'); if (androidVersionsToTokens) { - for (const [codeVersion, devices] of androidVersionsToTokens) { - const platformDetails = { platform: 'android', codeVersion }; + for (const [versionKey, devices] of androidVersionsToTokens) { + const { codeVersion, stateVersion } = stringToVersionKey(versionKey); + const platformDetails = { + platform: 'android', + codeVersion, + stateVersion, + }; const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( newRawMessageInfos, platformDetails, @@ -265,8 +277,14 @@ } const webVersionsToTokens = byPlatform.get('web'); if (webVersionsToTokens) { - for (const [codeVersion, devices] of webVersionsToTokens) { - const platformDetails = { platform: 'web', codeVersion }; + for (const [versionKey, devices] of webVersionsToTokens) { + const { codeVersion, stateVersion } = stringToVersionKey(versionKey); + const platformDetails = { + platform: 'web', + codeVersion, + stateVersion, + }; + const deliveryPromise = (async () => { const notification = await prepareWebNotification({ notifTexts, @@ -285,8 +303,13 @@ } const macosVersionsToTokens = byPlatform.get('macos'); if (macosVersionsToTokens) { - for (const [codeVersion, devices] of macosVersionsToTokens) { - const platformDetails = { platform: 'macos', codeVersion }; + for (const [versionKey, devices] of macosVersionsToTokens) { + const { codeVersion, stateVersion } = stringToVersionKey(versionKey); + const platformDetails = { + platform: 'macos', + codeVersion, + stateVersion, + }; const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( newRawMessageInfos, platformDetails, @@ -314,8 +337,14 @@ } const windowsVersionsToTokens = byPlatform.get('windows'); if (windowsVersionsToTokens) { - for (const [codeVersion, devices] of windowsVersionsToTokens) { - const platformDetails = { platform: 'windows', codeVersion }; + for (const [versionKey, devices] of windowsVersionsToTokens) { + const { codeVersion, stateVersion } = stringToVersionKey(versionKey); + const platformDetails = { + platform: 'windows', + codeVersion, + stateVersion, + }; + const deliveryPromise = (async () => { const notification = await prepareWNSNotification({ notifTexts, @@ -591,9 +620,24 @@ return await createIDs('notifications', numIDsNeeded); } +type VersionKey = { codeVersion: number, stateVersion: number }; +const versionKeyRegex: RegExp = new RegExp(/^-?\d+\|-?\d+$/); +function versionKeyToString(versionKey: VersionKey): string { + return `${versionKey.codeVersion}|${versionKey.stateVersion}`; +} + +function stringToVersionKey(versionKeyString: string): VersionKey { + invariant( + versionKeyRegex.test(versionKeyString), + 'should pass correct version key string', + ); + const [codeVersion, stateVersion] = versionKeyString.split('|').map(Number); + return { codeVersion, stateVersion }; +} + function getDevicesByPlatform( devices: $ReadOnlyArray, -): Map>> { +): Map>> { const byPlatform = new Map(); for (const device of devices) { let innerMap = byPlatform.get(device.platform); @@ -608,10 +652,16 @@ device.platform !== 'macos' ? device.codeVersion : -1; - let innerMostArray = innerMap.get(codeVersion); + const stateVersion: number = device.stateVersion ?? -1; + + const versionKey = versionKeyToString({ + codeVersion, + stateVersion, + }); + let innerMostArray = innerMap.get(versionKey); if (!innerMostArray) { innerMostArray = []; - innerMap.set(codeVersion, innerMostArray); + innerMap.set(versionKey, innerMostArray); } innerMostArray.push({ @@ -1211,24 +1261,29 @@ createIDs('notifications', 1), ]); const unreadCount = unreadCounts[userID]; - - const devices = deviceTokenResult.map(row => ({ - platform: row.platform, - cookieID: row.id, - deviceToken: row.device_token, - codeVersion: JSON.parse(row.versions)?.codeVersion, - })); + const devices = 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, + }; + }); const byPlatform = getDevicesByPlatform(devices); const deliveryPromises = []; const iosVersionsToTokens = byPlatform.get('ios'); if (iosVersionsToTokens) { - for (const [codeVersion, deviceInfos] of iosVersionsToTokens) { + for (const [versionKey, deviceInfos] of iosVersionsToTokens) { + const { codeVersion, stateVersion } = stringToVersionKey(versionKey); const notification = new apn.Notification(); notification.topic = getAPNsNotificationTopic({ platform: 'ios', codeVersion, + stateVersion, }); notification.badge = unreadCount; notification.pushType = 'alert'; @@ -1263,7 +1318,8 @@ const androidVersionsToTokens = byPlatform.get('android'); if (androidVersionsToTokens) { - for (const [codeVersion, deviceInfos] of androidVersionsToTokens) { + for (const [versionKey, deviceInfos] of androidVersionsToTokens) { + const { codeVersion } = stringToVersionKey(versionKey); const notificationData = codeVersion < 69 ? { badge: unreadCount.toString() } @@ -1299,11 +1355,13 @@ const macosVersionsToTokens = byPlatform.get('macos'); if (macosVersionsToTokens) { - for (const [codeVersion, deviceInfos] of macosVersionsToTokens) { + for (const [versionKey, deviceInfos] of macosVersionsToTokens) { + const { codeVersion, stateVersion } = stringToVersionKey(versionKey); const notification = new apn.Notification(); notification.topic = getAPNsNotificationTopic({ platform: 'macos', codeVersion, + stateVersion, }); notification.badge = unreadCount; notification.pushType = 'alert';