Changeset View
Changeset View
Standalone 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) { | const shouldAddMessageInfos = | ||||
copyWithMessageInfos.length() <= apnMaxNotificationPayloadByteSize; | |||||
ashoat: We need to be comparing the encrypted size to the max payload limit. Otherwise we may still… | |||||
if (shouldAddMessageInfos && shouldBeEncrypted && cookieIDs) { | |||||
notification.payload.messageInfos = messageInfos; | notification.payload.messageInfos = messageInfos; | ||||
return notification; | return prepareEncryptedIOSNotifications(cookieIDs, notification); | ||||
} else if (shouldAddMessageInfos) { | |||||
notification.payload.messageInfos = messageInfos; | |||||
return [notification]; | |||||
} | } | ||||
const notificationCopy = _cloneDeep(notification); | const notificationCopy = _cloneDeep(notification); | ||||
if (notificationCopy.length() > apnMaxNotificationPayloadByteSize) { | if (notificationCopy.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; | |||||
if (shouldBeEncrypted && cookieIDs) { | |||||
return prepareEncryptedIOSNotifications(cookieIDs, notification); | |||||
} | |||||
return [notification]; | |||||
} | } | ||||
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 ActionsWe should generate an APNsDelivery for every delivered notif ashoat: We should generate an `APNsDelivery` for every delivered notif | |||||
marcinAuthorUnsubmitted Done Inline ActionsWhy do we need different APNsDelivery object for each notification? The APNsDelivery object looks like this: type APNsDelivery = { source: $PropertyType<NotificationInfo, 'source'>, deviceType: 'ios' | 'macos', iosID: string, deviceTokens: $ReadOnlyArray<string>, codeVersion: number, errors?: $ReadOnlyArray<ResponseFailure>, }; Regardless of whether notifications are encrypted or not, one APNsDelivery gives us complete information on which deviceToken received notifications and which didn't. Why having many APNsDelivery objects with only one deviceToken and at most one error is better for encrypted notifications than one APNsDelivery with many deviceTokens and possibly may errors? marcin: Why do we need different `APNsDelivery` object for each notification? The `APNsDelivery` object… | |||||
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 }; |
We need to be comparing the encrypted size to the max payload limit. Otherwise we may still exceed the limit after encryption, which will cause the notif to be ignored