Changeset View
Standalone View
keyserver/src/push/send.js
Show All 31 Lines | import type { | ||||
WebNotification, | WebNotification, | ||||
WNSNotification, | WNSNotification, | ||||
ResolvedNotifTexts, | ResolvedNotifTexts, | ||||
} from 'lib/types/notif-types.js'; | } from 'lib/types/notif-types.js'; | ||||
import type { ServerThreadInfo } from 'lib/types/thread-types.js'; | import type { ServerThreadInfo } from 'lib/types/thread-types.js'; | ||||
import { updateTypes } from 'lib/types/update-types-enum.js'; | import { updateTypes } from 'lib/types/update-types-enum.js'; | ||||
import { promiseAll } from 'lib/utils/promises.js'; | import { promiseAll } from 'lib/utils/promises.js'; | ||||
import { prepareEncryptedIOSNotifications } from './crypto.js'; | |||||
import { getAPNsNotificationTopic } from './providers.js'; | import { getAPNsNotificationTopic } from './providers.js'; | ||||
import { rescindPushNotifs } from './rescind.js'; | import { rescindPushNotifs } from './rescind.js'; | ||||
import { | import { | ||||
apnPush, | apnPush, | ||||
fcmPush, | fcmPush, | ||||
getUnreadCounts, | getUnreadCounts, | ||||
apnMaxNotificationPayloadByteSize, | apnMaxNotificationPayloadByteSize, | ||||
fcmMaxNotificationPayloadByteSize, | fcmMaxNotificationPayloadByteSize, | ||||
Show All 11 Lines | |||||
import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; | import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; | ||||
import { fetchUserInfos } from '../fetchers/user-fetchers.js'; | import { fetchUserInfos } from '../fetchers/user-fetchers.js'; | ||||
import type { Viewer } from '../session/viewer.js'; | import type { Viewer } from '../session/viewer.js'; | ||||
import { getENSNames } from '../utils/ens-cache.js'; | import { getENSNames } from '../utils/ens-cache.js'; | ||||
type Device = { | type Device = { | ||||
+platform: Platform, | +platform: Platform, | ||||
+deviceToken: string, | +deviceToken: string, | ||||
+cookieID: string, | |||||
+codeVersion: ?number, | +codeVersion: ?number, | ||||
}; | }; | ||||
type PushUserInfo = { | type PushUserInfo = { | ||||
+devices: Device[], | +devices: Device[], | ||||
// messageInfos and messageDatas have the same key | // messageInfos and messageDatas have the same key | ||||
+messageInfos: RawMessageInfo[], | +messageInfos: RawMessageInfo[], | ||||
+messageDatas: MessageData[], | +messageDatas: MessageData[], | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 110 Lines • ▼ Show 20 Lines | for (const notifInfo of usersToCollapsableNotifInfo[userID]) { | ||||
userID, | userID, | ||||
threadID, | threadID, | ||||
messageID: firstMessageID, | messageID: firstMessageID, | ||||
collapseKey: notifInfo.collapseKey, | collapseKey: notifInfo.collapseKey, | ||||
}; | }; | ||||
const iosVersionsToTokens = byPlatform.get('ios'); | const iosVersionsToTokens = byPlatform.get('ios'); | ||||
if (iosVersionsToTokens) { | if (iosVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of iosVersionsToTokens) { | for (const [ | ||||
codeVersion, | |||||
{ cookieIDs, deviceTokens }, | |||||
] of iosVersionsToTokens) { | |||||
const platformDetails = { platform: 'ios', codeVersion }; | const platformDetails = { platform: 'ios', codeVersion }; | ||||
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
platformDetails, | platformDetails, | ||||
); | ); | ||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
const notification = await prepareAPNsNotification({ | const notificationsArray = await prepareAPNsNotification( | ||||
{ | |||||
notifTexts, | notifTexts, | ||||
newRawMessageInfos: shimmedNewRawMessageInfos, | newRawMessageInfos: shimmedNewRawMessageInfos, | ||||
threadID: threadInfo.id, | threadID: threadInfo.id, | ||||
collapseKey: notifInfo.collapseKey, | collapseKey: notifInfo.collapseKey, | ||||
badgeOnly, | badgeOnly, | ||||
unreadCount: unreadCounts[userID], | unreadCount: unreadCounts[userID], | ||||
platformDetails, | platformDetails, | ||||
}); | }, | ||||
[...cookieIDs], | |||||
); | |||||
return await sendAPNsNotification( | return await sendAPNsNotification( | ||||
'ios', | 'ios', | ||||
notification, | notificationsArray, | ||||
[...deviceTokens], | [...deviceTokens], | ||||
{ | { | ||||
...notificationInfo, | ...notificationInfo, | ||||
codeVersion, | codeVersion, | ||||
}, | }, | ||||
); | ); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
} | } | ||||
const androidVersionsToTokens = byPlatform.get('android'); | const androidVersionsToTokens = byPlatform.get('android'); | ||||
if (androidVersionsToTokens) { | if (androidVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of androidVersionsToTokens) { | for (const [codeVersion, { deviceTokens }] of androidVersionsToTokens) { | ||||
const platformDetails = { platform: 'android', codeVersion }; | const platformDetails = { platform: 'android', codeVersion }; | ||||
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
platformDetails, | platformDetails, | ||||
); | ); | ||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
const notification = await prepareAndroidNotification({ | const notification = await prepareAndroidNotification({ | ||||
notifTexts, | notifTexts, | ||||
Show All 14 Lines | for (const notifInfo of usersToCollapsableNotifInfo[userID]) { | ||||
}, | }, | ||||
); | ); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
} | } | ||||
const webVersionsToTokens = byPlatform.get('web'); | const webVersionsToTokens = byPlatform.get('web'); | ||||
if (webVersionsToTokens) { | if (webVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of webVersionsToTokens) { | for (const [codeVersion, { deviceTokens }] of webVersionsToTokens) { | ||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
const notification = await prepareWebNotification({ | const notification = await prepareWebNotification({ | ||||
notifTexts, | notifTexts, | ||||
threadID: threadInfo.id, | threadID: threadInfo.id, | ||||
unreadCount: unreadCounts[userID], | unreadCount: unreadCounts[userID], | ||||
}); | }); | ||||
return await sendWebNotification(notification, [...deviceTokens], { | return await sendWebNotification(notification, [...deviceTokens], { | ||||
...notificationInfo, | ...notificationInfo, | ||||
codeVersion, | codeVersion, | ||||
}); | }); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
} | } | ||||
const macosVersionsToTokens = byPlatform.get('macos'); | const macosVersionsToTokens = byPlatform.get('macos'); | ||||
if (macosVersionsToTokens) { | if (macosVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of macosVersionsToTokens) { | for (const [codeVersion, { deviceTokens }] of macosVersionsToTokens) { | ||||
const platformDetails = { platform: 'macos', codeVersion }; | const platformDetails = { platform: 'macos', codeVersion }; | ||||
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
platformDetails, | platformDetails, | ||||
); | ); | ||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
const notification = await prepareAPNsNotification({ | const notification = await prepareAPNsNotification({ | ||||
notifTexts, | notifTexts, | ||||
Show All 14 Lines | for (const notifInfo of usersToCollapsableNotifInfo[userID]) { | ||||
}, | }, | ||||
); | ); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
} | } | ||||
const windowsVersionsToTokens = byPlatform.get('windows'); | const windowsVersionsToTokens = byPlatform.get('windows'); | ||||
if (windowsVersionsToTokens) { | if (windowsVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of windowsVersionsToTokens) { | for (const [codeVersion, { deviceTokens }] of windowsVersionsToTokens) { | ||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
const notification = await prepareWNSNotification({ | const notification = await prepareWNSNotification({ | ||||
notifTexts, | notifTexts, | ||||
threadID: threadInfo.id, | threadID: threadInfo.id, | ||||
unreadCount: unreadCounts[userID], | unreadCount: unreadCounts[userID], | ||||
}); | }); | ||||
return await sendWNSNotification(notification, [...deviceTokens], { | return await sendWNSNotification(notification, [...deviceTokens], { | ||||
...notificationInfo, | ...notificationInfo, | ||||
▲ Show 20 Lines • Show All 260 Lines • ▼ Show 20 Lines | async function createDBIDs(pushInfo: PushInfo): Promise<string[]> { | ||||
let numIDsNeeded = 0; | let numIDsNeeded = 0; | ||||
for (const userID in pushInfo) { | for (const userID in pushInfo) { | ||||
numIDsNeeded += pushInfo[userID].messageInfos.length; | numIDsNeeded += pushInfo[userID].messageInfos.length; | ||||
} | } | ||||
return await createIDs('notifications', numIDsNeeded); | return await createIDs('notifications', numIDsNeeded); | ||||
} | } | ||||
function getDevicesByPlatform( | function getDevicesByPlatform( | ||||
devices: Device[], | devices: $ReadOnlyArray<Device>, | ||||
): Map<Platform, Map<number, Set<string>>> { | ): Map< | ||||
Platform, | |||||
Map<number, { cookieIDs: Set<string>, deviceTokens: Set<string> }>, | |||||
> { | |||||
const byPlatform = new Map(); | const byPlatform = new Map(); | ||||
for (const device of devices) { | for (const device of devices) { | ||||
let innerMap = byPlatform.get(device.platform); | let innerMap = byPlatform.get(device.platform); | ||||
if (!innerMap) { | if (!innerMap) { | ||||
innerMap = new Map(); | innerMap = new Map(); | ||||
byPlatform.set(device.platform, innerMap); | byPlatform.set(device.platform, innerMap); | ||||
} | } | ||||
const codeVersion: number = | const codeVersion: number = | ||||
device.codeVersion !== null && device.codeVersion !== undefined | device.codeVersion !== null && device.codeVersion !== undefined | ||||
? device.codeVersion | ? device.codeVersion | ||||
: -1; | : -1; | ||||
let innerMostSet = innerMap.get(codeVersion); | let innerMostPair = innerMap.get(codeVersion); | ||||
if (!innerMostSet) { | if (!innerMostPair) { | ||||
innerMostSet = new Set(); | innerMostPair = { cookieIDs: new Set(), deviceTokens: new Set() }; | ||||
innerMap.set(codeVersion, innerMostSet); | innerMap.set(codeVersion, innerMostPair); | ||||
} | } | ||||
innerMostSet.add(device.deviceToken); | const { cookieIDs, deviceTokens } = innerMostPair; | ||||
cookieIDs.add(device.cookieID); | |||||
deviceTokens.add(device.deviceToken); | |||||
} | } | ||||
return byPlatform; | return byPlatform; | ||||
} | } | ||||
type APNsNotifInputData = { | type APNsNotifInputData = { | ||||
+notifTexts: ResolvedNotifTexts, | +notifTexts: ResolvedNotifTexts, | ||||
+newRawMessageInfos: RawMessageInfo[], | +newRawMessageInfos: RawMessageInfo[], | ||||
+threadID: string, | +threadID: string, | ||||
+collapseKey: ?string, | +collapseKey: ?string, | ||||
+badgeOnly: boolean, | +badgeOnly: boolean, | ||||
+unreadCount: number, | +unreadCount: number, | ||||
+platformDetails: PlatformDetails, | +platformDetails: PlatformDetails, | ||||
}; | }; | ||||
async function prepareAPNsNotification( | async function prepareAPNsNotification( | ||||
inputData: APNsNotifInputData, | inputData: APNsNotifInputData, | ||||
): Promise<apn.Notification> { | cookieIDs?: $ReadOnlyArray<string>, | ||||
): Promise<Array<apn.Notification>> { | |||||
const { | const { | ||||
notifTexts, | notifTexts, | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
threadID, | threadID, | ||||
collapseKey, | collapseKey, | ||||
badgeOnly, | badgeOnly, | ||||
unreadCount, | unreadCount, | ||||
platformDetails, | platformDetails, | ||||
} = inputData; | } = inputData; | ||||
const isTextNotification = newRawMessageInfos.every( | |||||
newRawMessageInfo => newRawMessageInfo.type === messageTypes.TEXT, | |||||
); | |||||
const shouldBeEncrypted = | |||||
platformDetails.platform === 'ios' && !collapseKey && isTextNotification; | |||||
const uniqueID = uuidv4(); | const uniqueID = uuidv4(); | ||||
const notification = new apn.Notification(); | const notification = new apn.Notification(); | ||||
notification.topic = getAPNsNotificationTopic(platformDetails); | notification.topic = getAPNsNotificationTopic(platformDetails); | ||||
const { merged, ...rest } = notifTexts; | const { merged, ...rest } = notifTexts; | ||||
// We don't include alert's body on macos because we | // We don't include alert's body on macos because we | ||||
// handle displaying the notification ourselves and | // handle displaying the notification ourselves and | ||||
// we don't want macOS to display it automatically. | // we don't want macOS to display it automatically. | ||||
Show All 24 Lines | ): Promise<Array<apn.Notification>> { | ||||
// length compiles the notification and makes it immutable. Further | // length compiles the notification and makes it immutable. Further | ||||
// changes to its properties won't be reflected in the final plaintext | // changes to its properties won't be reflected in the final plaintext | ||||
// data that is sent. | // data that is sent. | ||||
const copyWithMessageInfos = _cloneDeep(notification); | const copyWithMessageInfos = _cloneDeep(notification); | ||||
copyWithMessageInfos.payload = { | copyWithMessageInfos.payload = { | ||||
...copyWithMessageInfos.payload, | ...copyWithMessageInfos.payload, | ||||
messageInfos, | messageInfos, | ||||
}; | }; | ||||
if (copyWithMessageInfos.length() <= apnMaxNotificationPayloadByteSize) { | |||||
notification.payload.messageInfos = messageInfos; | let notifications, notificationsWithMessageInfos; | ||||
return notification; | if (shouldBeEncrypted && cookieIDs) { | ||||
[notifications, notificationsWithMessageInfos] = await Promise.all([ | |||||
prepareEncryptedIOSNotifications(cookieIDs, notification), | |||||
prepareEncryptedIOSNotifications(cookieIDs, copyWithMessageInfos), | |||||
]); | |||||
} else { | |||||
notifications = [notification]; | |||||
notificationsWithMessageInfos = [copyWithMessageInfos]; | |||||
} | } | ||||
const notificationCopy = _cloneDeep(notification); | |||||
if (notificationCopy.length() > apnMaxNotificationPayloadByteSize) { | const shouldAddMessageInfos = notificationsWithMessageInfos.map(notif => { | ||||
const copy = _cloneDeep(notif); | |||||
return copy.length() <= apnMaxNotificationPayloadByteSize; | |||||
}); | |||||
const notificationsToSend = shouldAddMessageInfos.map( | |||||
(addMessageInfos, idx) => { | |||||
if (addMessageInfos) { | |||||
return notificationsWithMessageInfos[idx]; | |||||
} | |||||
return notifications[idx]; | |||||
}, | |||||
); | |||||
for (const notifToSend of notificationsToSend) { | |||||
const copy = _cloneDeep(notifToSend); | |||||
if (copy.length() > apnMaxNotificationPayloadByteSize) { | |||||
console.warn( | console.warn( | ||||
`${platformDetails.platform} notification ${uniqueID} ` + | `${platformDetails.platform} notification ${uniqueID} ` + | ||||
`exceeds size limit, even with messageInfos omitted`, | `exceeds size limit, even with messageInfos omitted`, | ||||
); | ); | ||||
} | } | ||||
return notification; | } | ||||
ashoat: Instead of doing three passes here, we can simply combine them:
```lang=js
const… | |||||
return notificationsToSend; | |||||
} | } | ||||
type AndroidNotifInputData = { | type AndroidNotifInputData = { | ||||
...APNsNotifInputData, | ...APNsNotifInputData, | ||||
+dbID: string, | +dbID: string, | ||||
}; | }; | ||||
async function prepareAndroidNotification( | async function prepareAndroidNotification( | ||||
inputData: AndroidNotifInputData, | inputData: AndroidNotifInputData, | ||||
▲ Show 20 Lines • Show All 129 Lines • ▼ Show 20 Lines | |||||
}; | }; | ||||
type APNsResult = { | type APNsResult = { | ||||
info: NotificationInfo, | info: NotificationInfo, | ||||
delivery: APNsDelivery, | delivery: APNsDelivery, | ||||
invalidTokens?: $ReadOnlyArray<string>, | invalidTokens?: $ReadOnlyArray<string>, | ||||
}; | }; | ||||
async function sendAPNsNotification( | async function sendAPNsNotification( | ||||
platform: 'ios' | 'macos', | platform: 'ios' | 'macos', | ||||
notification: apn.Notification, | notifications: $ReadOnlyArray<apn.Notification>, | ||||
deviceTokens: $ReadOnlyArray<string>, | deviceTokens: $ReadOnlyArray<string>, | ||||
notificationInfo: NotificationInfo, | notificationInfo: NotificationInfo, | ||||
): Promise<APNsResult> { | ): Promise<APNsResult> { | ||||
const { source, codeVersion } = notificationInfo; | const { source, codeVersion } = notificationInfo; | ||||
const response = await apnPush({ | const response = await apnPush({ | ||||
notification, | notifications, | ||||
deviceTokens, | deviceTokens, | ||||
platformDetails: { platform, codeVersion }, | platformDetails: { platform, codeVersion }, | ||||
}); | }); | ||||
const [notification] = notifications; | |||||
ashoatUnsubmitted Not Done Inline Actions
Let me be more specific: by only taking the first notification here, and ignoring the rest, you are failing to record in the notifications DB the full list of notification IDs that have been delivered. This has a number of negative effects, most notably that are you breaking rescinding, which relies on the iosID being present. (I didn't realize this in the initial feedback, but I certainly suspected that something sketchy was happening here, since you are throwing away all but the first notification.) Perhaps the easiest solution would be to go back to the old behavior, where there is one delivery for each iosID. That was my initial suggestion. However, I am open to other solutions that do not break notif rescinding. Separately, some feedback: this sort of thing frankly should not have to be discussed in code review, and certainly should not take two "Request Changes" for you to look into. When you make a change like this (making it so we only look at the first iosID), you should think like a reviewer, and treat this as a "red flag" that warrants significant investigation. It is a sketchy decision and is likely to lead to sketchy consequences, so you should be very sure that it is a correct direction before putting up the diff (and certainly before ignoring review feedback). ashoat: > Why do we need different `APNsDelivery` object for each notification? The `APNsDelivery`… | |||||
ashoatUnsubmitted Not Done Inline ActionsIn retrospect, this whole analysis may be incorrect – I am not sure if iosID is perhaps the exact same for every single one of these notifications. Apologies for my aggressive note... I'll review this and get back. ashoat: In retrospect, this whole analysis may be incorrect – I am not sure if `iosID` is perhaps the… | |||||
ashoatUnsubmitted Not Done Inline ActionsIndeed, after rereading this I can see that my analysis was incorrect. The notification.id used here is actually the same for each of the notifications. Again, apologies for my aggressive note. That said, I think this is a case of a brittle assumption that is not being enforced, similar to the case of the assumption regarding the length of the SQLCipher encryption key. When we have assumptions like this, we should make sure the assumptions are enforced. Can you add some code that checks over the list of notifications and guarantees that they have the same notification.id? ashoat: Indeed, after rereading this I can see that my analysis was incorrect. The `notification.id`… | |||||
marcinAuthorUnsubmitted Done Inline ActionsSure, I will do it. marcin: Sure, I will do it. | |||||
const delivery: APNsDelivery = { | const delivery: APNsDelivery = { | ||||
source, | source, | ||||
deviceType: platform, | deviceType: platform, | ||||
iosID: notification.id, | iosID: notification.id, | ||||
deviceTokens, | deviceTokens, | ||||
codeVersion, | codeVersion, | ||||
}; | }; | ||||
if (response.errors) { | if (response.errors) { | ||||
▲ Show 20 Lines • Show All 233 Lines • ▼ Show 20 Lines | const devices = deviceTokenResult.map(row => ({ | ||||
codeVersion: JSON.parse(row.versions)?.codeVersion, | codeVersion: JSON.parse(row.versions)?.codeVersion, | ||||
})); | })); | ||||
const byPlatform = getDevicesByPlatform(devices); | const byPlatform = getDevicesByPlatform(devices); | ||||
const deliveryPromises = []; | const deliveryPromises = []; | ||||
const iosVersionsToTokens = byPlatform.get('ios'); | const iosVersionsToTokens = byPlatform.get('ios'); | ||||
if (iosVersionsToTokens) { | if (iosVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of iosVersionsToTokens) { | for (const [ | ||||
codeVersion, | |||||
{ cookieIDs, deviceTokens }, | |||||
] of iosVersionsToTokens) { | |||||
const notification = new apn.Notification(); | const notification = new apn.Notification(); | ||||
notification.topic = getAPNsNotificationTopic({ | notification.topic = getAPNsNotificationTopic({ | ||||
platform: 'ios', | platform: 'ios', | ||||
codeVersion, | codeVersion, | ||||
}); | }); | ||||
notification.badge = unreadCount; | notification.badge = unreadCount; | ||||
notification.pushType = 'alert'; | notification.pushType = 'alert'; | ||||
const notificationsArray = await prepareEncryptedIOSNotifications( | |||||
[...cookieIDs], | |||||
notification, | |||||
); | |||||
deliveryPromises.push( | deliveryPromises.push( | ||||
sendAPNsNotification('ios', notification, [...deviceTokens], { | sendAPNsNotification('ios', notificationsArray, [...deviceTokens], { | ||||
source, | source, | ||||
dbID, | dbID, | ||||
userID, | userID, | ||||
codeVersion, | codeVersion, | ||||
}), | }), | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
const androidVersionsToTokens = byPlatform.get('android'); | const androidVersionsToTokens = byPlatform.get('android'); | ||||
if (androidVersionsToTokens) { | if (androidVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of androidVersionsToTokens) { | for (const [codeVersion, { deviceTokens }] of androidVersionsToTokens) { | ||||
const notificationData = | const notificationData = | ||||
codeVersion < 69 | codeVersion < 69 | ||||
? { badge: unreadCount.toString() } | ? { badge: unreadCount.toString() } | ||||
: { badge: unreadCount.toString(), badgeOnly: '1' }; | : { badge: unreadCount.toString(), badgeOnly: '1' }; | ||||
const notification = { data: notificationData }; | const notification = { data: notificationData }; | ||||
deliveryPromises.push( | deliveryPromises.push( | ||||
sendAndroidNotification(notification, [...deviceTokens], { | sendAndroidNotification(notification, [...deviceTokens], { | ||||
source, | source, | ||||
dbID, | dbID, | ||||
userID, | userID, | ||||
codeVersion, | codeVersion, | ||||
}), | }), | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
const macosVersionsToTokens = byPlatform.get('macos'); | const macosVersionsToTokens = byPlatform.get('macos'); | ||||
if (macosVersionsToTokens) { | if (macosVersionsToTokens) { | ||||
for (const [codeVersion, deviceTokens] of macosVersionsToTokens) { | for (const [codeVersion, { deviceTokens }] of macosVersionsToTokens) { | ||||
const notification = new apn.Notification(); | const notification = new apn.Notification(); | ||||
notification.topic = getAPNsNotificationTopic({ | notification.topic = getAPNsNotificationTopic({ | ||||
platform: 'macos', | platform: 'macos', | ||||
codeVersion, | codeVersion, | ||||
}); | }); | ||||
notification.badge = unreadCount; | notification.badge = unreadCount; | ||||
notification.pushType = 'alert'; | notification.pushType = 'alert'; | ||||
deliveryPromises.push( | deliveryPromises.push( | ||||
sendAPNsNotification('macos', notification, [...deviceTokens], { | sendAPNsNotification('macos', [notification], [...deviceTokens], { | ||||
source, | source, | ||||
dbID, | dbID, | ||||
userID, | userID, | ||||
codeVersion, | codeVersion, | ||||
}), | }), | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
const deliveryResults = await Promise.all(deliveryPromises); | const deliveryResults = await Promise.all(deliveryPromises); | ||||
await saveNotifResults(deliveryResults, new Map(), false); | await saveNotifResults(deliveryResults, new Map(), false); | ||||
} | } | ||||
export { sendPushNotifs, sendRescindNotifs, updateBadgeCount }; | export { sendPushNotifs, sendRescindNotifs, updateBadgeCount }; |
Instead of doing three passes here, we can simply combine them: