Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/push/send.js
Show All 17 Lines | import { | ||||
shimUnsupportedRawMessageInfos, | shimUnsupportedRawMessageInfos, | ||||
} from 'lib/shared/message-utils.js'; | } from 'lib/shared/message-utils.js'; | ||||
import { messageSpecs } from 'lib/shared/messages/message-specs.js'; | import { messageSpecs } from 'lib/shared/messages/message-specs.js'; | ||||
import { notifTextsForMessageInfo } from 'lib/shared/notif-utils.js'; | import { notifTextsForMessageInfo } from 'lib/shared/notif-utils.js'; | ||||
import { | import { | ||||
rawThreadInfoFromServerThreadInfo, | rawThreadInfoFromServerThreadInfo, | ||||
threadInfoFromRawThreadInfo, | threadInfoFromRawThreadInfo, | ||||
} from 'lib/shared/thread-utils.js'; | } from 'lib/shared/thread-utils.js'; | ||||
import { FUTURE_CODE_VERSION } from 'lib/shared/version-utils.js'; | |||||
import type { Platform, PlatformDetails } from 'lib/types/device-types.js'; | import type { Platform, PlatformDetails } from 'lib/types/device-types.js'; | ||||
import { messageTypes } from 'lib/types/message-types-enum.js'; | import { messageTypes } from 'lib/types/message-types-enum.js'; | ||||
import { | import { | ||||
type RawMessageInfo, | type RawMessageInfo, | ||||
type MessageData, | type MessageData, | ||||
} from 'lib/types/message-types.js'; | } from 'lib/types/message-types.js'; | ||||
import { rawMessageInfoValidator } from 'lib/types/message-types.js'; | import { rawMessageInfoValidator } from 'lib/types/message-types.js'; | ||||
import type { | import type { | ||||
WebNotification, | WebNotification, | ||||
WNSNotification, | WNSNotification, | ||||
ResolvedNotifTexts, | ResolvedNotifTexts, | ||||
} from 'lib/types/notif-types.js'; | } from 'lib/types/notif-types.js'; | ||||
import { resolvedNotifTextsValidator } from 'lib/types/notif-types.js'; | import { resolvedNotifTextsValidator } 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 { tID, tPlatformDetails, tShape } from 'lib/utils/validation-utils.js'; | import { tID, tPlatformDetails, tShape } from 'lib/utils/validation-utils.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 12 Lines | |||||
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'; | ||||
import { validateOutput } from '../utils/validation-utils.js'; | import { validateOutput } from '../utils/validation-utils.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 platformDetails = { platform: 'web', codeVersion }; | const platformDetails = { platform: 'web', codeVersion }; | ||||
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], | ||||
platformDetails, | platformDetails, | ||||
}); | }); | ||||
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 platformDetails = { platform: 'windows', codeVersion }; | const platformDetails = { platform: 'windows', codeVersion }; | ||||
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], | ||||
platformDetails, | platformDetails, | ||||
}); | }); | ||||
▲ Show 20 Lines • Show All 262 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, | ||||
}; | }; | ||||
const apnsNotifInputDataValidator = tShape<APNsNotifInputData>({ | const apnsNotifInputDataValidator = tShape<APNsNotifInputData>({ | ||||
notifTexts: resolvedNotifTextsValidator, | notifTexts: resolvedNotifTextsValidator, | ||||
newRawMessageInfos: t.list(rawMessageInfoValidator), | newRawMessageInfos: t.list(rawMessageInfoValidator), | ||||
threadID: tID, | threadID: tID, | ||||
collapseKey: t.maybe(t.String), | collapseKey: t.maybe(t.String), | ||||
badgeOnly: t.Boolean, | badgeOnly: t.Boolean, | ||||
unreadCount: t.Number, | unreadCount: t.Number, | ||||
platformDetails: tPlatformDetails, | platformDetails: tPlatformDetails, | ||||
}); | }); | ||||
async function prepareAPNsNotification( | async function prepareAPNsNotification( | ||||
inputData: APNsNotifInputData, | inputData: APNsNotifInputData, | ||||
): Promise<apn.Notification> { | cookieIDs?: $ReadOnlyArray<string>, | ||||
): Promise<Array<apn.Notification>> { | |||||
const convertedData = validateOutput( | const convertedData = validateOutput( | ||||
inputData.platformDetails, | inputData.platformDetails, | ||||
apnsNotifInputDataValidator, | apnsNotifInputDataValidator, | ||||
inputData, | inputData, | ||||
); | ); | ||||
const { | const { | ||||
notifTexts, | notifTexts, | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
threadID, | threadID, | ||||
collapseKey, | collapseKey, | ||||
badgeOnly, | badgeOnly, | ||||
unreadCount, | unreadCount, | ||||
platformDetails, | platformDetails, | ||||
} = convertedData; | } = convertedData; | ||||
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 && | |||||
platformDetails.codeVersion && | |||||
platformDetails.codeVersion > FUTURE_CODE_VERSION | |||||
) { | |||||
[notifications, notificationsWithMessageInfos] = await Promise.all([ | |||||
prepareEncryptedIOSNotifications(cookieIDs, notification), | |||||
prepareEncryptedIOSNotifications(cookieIDs, copyWithMessageInfos), | |||||
]); | |||||
} else { | |||||
notifications = [notification]; | |||||
notificationsWithMessageInfos = [copyWithMessageInfos]; | |||||
} | |||||
const notificationsToSend = notificationsWithMessageInfos.map( | |||||
(notif, idx) => { | |||||
const notifCopyWithMessageInfos = _cloneDeep(notif); | |||||
if ( | |||||
notifCopyWithMessageInfos.length() <= apnMaxNotificationPayloadByteSize | |||||
) { | |||||
return notifCopyWithMessageInfos; | |||||
} | } | ||||
const notificationCopy = _cloneDeep(notification); | const notifWithoutMessageInfos = notifications[idx]; | ||||
if (notificationCopy.length() > apnMaxNotificationPayloadByteSize) { | const copyWithoutMessageInfos = _cloneDeep(notifWithoutMessageInfos); | ||||
if ( | |||||
copyWithoutMessageInfos.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; | return notifWithoutMessageInfos; | ||||
}, | |||||
); | |||||
return notificationsToSend; | |||||
} | } | ||||
type AndroidNotifInputData = { | type AndroidNotifInputData = { | ||||
...APNsNotifInputData, | ...APNsNotifInputData, | ||||
+dbID: string, | +dbID: string, | ||||
}; | }; | ||||
const androidNotifInputDataValidator = tShape<AndroidNotifInputData>({ | const androidNotifInputDataValidator = tShape<AndroidNotifInputData>({ | ||||
...apnsNotifInputDataValidator.meta.props, | ...apnsNotifInputDataValidator.meta.props, | ||||
▲ Show 20 Lines • Show All 162 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 }, | ||||
}); | }); | ||||
invariant( | |||||
new Set(notifications.map(notif => notif.id)).size === 1, | |||||
'Encrypted versions of the same notification must share id value', | |||||
); | |||||
const [notification] = notifications; | |||||
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 }; |