Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/push/utils.js
Show All 17 Lines | |||||
import { | import { | ||||
getAPNPushProfileForCodeVersion, | getAPNPushProfileForCodeVersion, | ||||
getFCMPushProfileForCodeVersion, | getFCMPushProfileForCodeVersion, | ||||
getAPNProvider, | getAPNProvider, | ||||
getFCMProvider, | getFCMProvider, | ||||
ensureWebPushInitialized, | ensureWebPushInitialized, | ||||
getWNSToken, | getWNSToken, | ||||
} from './providers.js'; | } from './providers.js'; | ||||
import type { TargetedAPNsNotification } from './types.js'; | import type { TargetedAPNsNotification, AndroidNotification } from './types.js'; | ||||
import { dbQuery, SQL } from '../database/database.js'; | import { dbQuery, SQL } from '../database/database.js'; | ||||
const fcmTokenInvalidationErrors = new Set([ | const fcmTokenInvalidationErrors = new Set([ | ||||
'messaging/registration-token-not-registered', | 'messaging/registration-token-not-registered', | ||||
'messaging/invalid-registration-token', | 'messaging/invalid-registration-token', | ||||
]); | ]); | ||||
const fcmMaxNotificationPayloadByteSize = 4000; | const fcmMaxNotificationPayloadByteSize = 4000; | ||||
const apnTokenInvalidationErrorCode = 410; | const apnTokenInvalidationErrorCode = 410; | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | |||||
type FCMPushResult = { | type FCMPushResult = { | ||||
+success?: true, | +success?: true, | ||||
+fcmIDs?: $ReadOnlyArray<string>, | +fcmIDs?: $ReadOnlyArray<string>, | ||||
+errors?: $ReadOnlyArray<FirebaseError>, | +errors?: $ReadOnlyArray<FirebaseError>, | ||||
+invalidTokens?: $ReadOnlyArray<string>, | +invalidTokens?: $ReadOnlyArray<string>, | ||||
}; | }; | ||||
async function fcmPush({ | async function fcmPush({ | ||||
notification, | targetedNotifications, | ||||
deviceTokens, | |||||
collapseKey, | collapseKey, | ||||
codeVersion, | codeVersion, | ||||
}: { | }: { | ||||
+notification: Object, | +targetedNotifications: $ReadOnlyArray<{ | ||||
+deviceTokens: $ReadOnlyArray<string>, | +notification: AndroidNotification, | ||||
+deviceToken: string, | |||||
}>, | |||||
+codeVersion: ?number, | +codeVersion: ?number, | ||||
+collapseKey?: ?string, | +collapseKey?: ?string, | ||||
}): Promise<FCMPushResult> { | }): Promise<FCMPushResult> { | ||||
const pushProfile = getFCMPushProfileForCodeVersion(codeVersion); | const pushProfile = getFCMPushProfileForCodeVersion(codeVersion); | ||||
const fcmProvider = await getFCMProvider(pushProfile); | const fcmProvider = await getFCMProvider(pushProfile); | ||||
if (!fcmProvider && process.env.NODE_ENV === 'development') { | if (!fcmProvider && process.env.NODE_ENV === 'development') { | ||||
console.log(`no keyserver/secrets/${pushProfile}.json so ignoring notifs`); | console.log(`no keyserver/secrets/${pushProfile}.json so ignoring notifs`); | ||||
return { success: true }; | return { success: true }; | ||||
} | } | ||||
invariant(fcmProvider, `keyserver/secrets/${pushProfile}.json should exist`); | invariant(fcmProvider, `keyserver/secrets/${pushProfile}.json should exist`); | ||||
const options: Object = { | const options: Object = { | ||||
priority: 'high', | priority: 'high', | ||||
}; | }; | ||||
if (collapseKey) { | if (collapseKey) { | ||||
options.collapseKey = collapseKey; | options.collapseKey = collapseKey; | ||||
} | } | ||||
// firebase-admin is extremely barebones and has a lot of missing or poorly | // firebase-admin is extremely barebones and has a lot of missing or poorly | ||||
// thought-out functionality. One of the issues is that if you send a | // thought-out functionality. One of the issues is that if you send a | ||||
// multicast messages and one of the device tokens is invalid, the resultant | // multicast messages and one of the device tokens is invalid, the resultant | ||||
// won't explain which of the device tokens is invalid. So we're forced to | // won't explain which of the device tokens is invalid. So we're forced to | ||||
// avoid the multicast functionality and call it once per deviceToken. | // avoid the multicast functionality and call it once per deviceToken. | ||||
const promises = []; | const promises = []; | ||||
for (const deviceToken of deviceTokens) { | for (const { notification, deviceToken } of targetedNotifications) { | ||||
promises.push( | promises.push( | ||||
fcmSinglePush(fcmProvider, notification, deviceToken, options), | fcmSinglePush(fcmProvider, notification, deviceToken, options), | ||||
); | ); | ||||
} | } | ||||
const pushResults = await Promise.all(promises); | const pushResults = await Promise.all(promises); | ||||
const errors = []; | const errors = []; | ||||
const ids = []; | const ids = []; | ||||
const invalidTokens = []; | const invalidTokens = []; | ||||
for (let i = 0; i < pushResults.length; i++) { | for (let i = 0; i < pushResults.length; i++) { | ||||
const pushResult = pushResults[i]; | const pushResult = pushResults[i]; | ||||
for (const error of pushResult.errors) { | for (const error of pushResult.errors) { | ||||
errors.push(error); | errors.push(error); | ||||
if (fcmTokenInvalidationErrors.has(error.errorInfo.code)) { | if (fcmTokenInvalidationErrors.has(error.errorInfo.code)) { | ||||
invalidTokens.push(deviceTokens[i]); | invalidTokens.push(targetedNotifications[i].deviceToken); | ||||
} | } | ||||
} | } | ||||
for (const id of pushResult.fcmIDs) { | for (const id of pushResult.fcmIDs) { | ||||
ids.push(id); | ids.push(id); | ||||
} | } | ||||
} | } | ||||
const result = {}; | const result = {}; | ||||
▲ Show 20 Lines • Show All 228 Lines • Show Last 20 Lines |