Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/push/utils.js
// @flow | // @flow | |||||||||||||||||||||||||||||||||||
import apn from '@parse/node-apn'; | import apn from '@parse/node-apn'; | |||||||||||||||||||||||||||||||||||
import type { ResponseFailure } from '@parse/node-apn'; | import type { ResponseFailure } from '@parse/node-apn'; | |||||||||||||||||||||||||||||||||||
import type { FirebaseApp, FirebaseError } from 'firebase-admin'; | import type { FirebaseApp, FirebaseError } from 'firebase-admin'; | |||||||||||||||||||||||||||||||||||
import invariant from 'invariant'; | import invariant from 'invariant'; | |||||||||||||||||||||||||||||||||||
import webpush from 'web-push'; | ||||||||||||||||||||||||||||||||||||
import { threadSubscriptions } from 'lib/types/subscription-types.js'; | import { threadSubscriptions } from 'lib/types/subscription-types.js'; | |||||||||||||||||||||||||||||||||||
import { threadPermissions } from 'lib/types/thread-types.js'; | import { threadPermissions } from 'lib/types/thread-types.js'; | |||||||||||||||||||||||||||||||||||
import { | import { | |||||||||||||||||||||||||||||||||||
getAPNPushProfileForCodeVersion, | getAPNPushProfileForCodeVersion, | |||||||||||||||||||||||||||||||||||
getFCMPushProfileForCodeVersion, | getFCMPushProfileForCodeVersion, | |||||||||||||||||||||||||||||||||||
getAPNProvider, | getAPNProvider, | |||||||||||||||||||||||||||||||||||
getFCMProvider, | getFCMProvider, | |||||||||||||||||||||||||||||||||||
ensureWebPushInitialized, | ||||||||||||||||||||||||||||||||||||
} from './providers.js'; | } from './providers.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; | |||||||||||||||||||||||||||||||||||
const apnBadRequestErrorCode = 400; | const apnBadRequestErrorCode = 400; | |||||||||||||||||||||||||||||||||||
ashoat: We put the error code for APN up here. Should we put the error code for web in the same place? | ||||||||||||||||||||||||||||||||||||
const apnBadTokenErrorString = 'BadDeviceToken'; | const apnBadTokenErrorString = 'BadDeviceToken'; | |||||||||||||||||||||||||||||||||||
const apnMaxNotificationPayloadByteSize = 4096; | const apnMaxNotificationPayloadByteSize = 4096; | |||||||||||||||||||||||||||||||||||
type APNPushResult = | type APNPushResult = | |||||||||||||||||||||||||||||||||||
| { +success: true } | | { +success: true } | |||||||||||||||||||||||||||||||||||
| { | | { | |||||||||||||||||||||||||||||||||||
+errors: $ReadOnlyArray<ResponseFailure>, | +errors: $ReadOnlyArray<ResponseFailure>, | |||||||||||||||||||||||||||||||||||
+invalidTokens?: $ReadOnlyArray<string>, | +invalidTokens?: $ReadOnlyArray<string>, | |||||||||||||||||||||||||||||||||||
▲ Show 20 Lines • Show All 159 Lines • ▼ Show 20 Lines | ): Promise<{ [userID: string]: number }> { | |||||||||||||||||||||||||||||||||||
for (const userID of userIDs) { | for (const userID of userIDs) { | |||||||||||||||||||||||||||||||||||
if (usersToUnreadCounts[userID] === undefined) { | if (usersToUnreadCounts[userID] === undefined) { | |||||||||||||||||||||||||||||||||||
usersToUnreadCounts[userID] = 0; | usersToUnreadCounts[userID] = 0; | |||||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||||
return usersToUnreadCounts; | return usersToUnreadCounts; | |||||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||||
type WebPushResult = { | ||||||||||||||||||||||||||||||||||||
+success?: true, | ||||||||||||||||||||||||||||||||||||
+errors?: $ReadOnlyArray<Object>, | ||||||||||||||||||||||||||||||||||||
+invalidTokens?: $ReadOnlyArray<string>, | ||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||
async function webPush({ | ||||||||||||||||||||||||||||||||||||
notification, | ||||||||||||||||||||||||||||||||||||
deviceTokens, | ||||||||||||||||||||||||||||||||||||
}: { | ||||||||||||||||||||||||||||||||||||
+notification: Object, | ||||||||||||||||||||||||||||||||||||
ashoatUnsubmitted Not Done Inline ActionsCan we avoid Object? ashoat: Can we avoid `Object`? | ||||||||||||||||||||||||||||||||||||
+deviceTokens: $ReadOnlyArray<string>, | ||||||||||||||||||||||||||||||||||||
}): Promise<WebPushResult> { | ||||||||||||||||||||||||||||||||||||
await ensureWebPushInitialized(); | ||||||||||||||||||||||||||||||||||||
const notificationString = JSON.stringify(notification); | ||||||||||||||||||||||||||||||||||||
const promises = []; | ||||||||||||||||||||||||||||||||||||
for (const deviceTokenString of deviceTokens) { | ||||||||||||||||||||||||||||||||||||
const deviceToken: PushSubscriptionJSON = JSON.parse(deviceTokenString); | ||||||||||||||||||||||||||||||||||||
promises.push( | ||||||||||||||||||||||||||||||||||||
(async () => { | ||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||
await webpush.sendNotification(deviceToken, notificationString); | ||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||
return { error }; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
return {}; | ||||||||||||||||||||||||||||||||||||
})(), | ||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
const pushResults = await Promise.all(promises); | ||||||||||||||||||||||||||||||||||||
ashoatUnsubmitted Not Done Inline Actions
I think this would be cleaner using .map ashoat: I think this would be cleaner using `.map` | ||||||||||||||||||||||||||||||||||||
const errors = []; | ||||||||||||||||||||||||||||||||||||
const invalidTokens = []; | ||||||||||||||||||||||||||||||||||||
for (let i = 0; i < pushResults.length; i++) { | ||||||||||||||||||||||||||||||||||||
const pushResult = pushResults[i]; | ||||||||||||||||||||||||||||||||||||
if (pushResult.error) { | ||||||||||||||||||||||||||||||||||||
errors.push(pushResult.error); | ||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||
pushResult.error.statusCode === 404 || | ||||||||||||||||||||||||||||||||||||
pushResult.error.statusCode === 410 | ||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||
invalidTokens.push(deviceTokens[i]); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
const result = {}; | ||||||||||||||||||||||||||||||||||||
if (errors.length > 0) { | ||||||||||||||||||||||||||||||||||||
result.errors = errors; | ||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||
result.success = true; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
if (invalidTokens.length > 0) { | ||||||||||||||||||||||||||||||||||||
result.invalidTokens = invalidTokens; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
return { ...result }; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
export { | export { | |||||||||||||||||||||||||||||||||||
apnPush, | apnPush, | |||||||||||||||||||||||||||||||||||
fcmPush, | fcmPush, | |||||||||||||||||||||||||||||||||||
webPush, | ||||||||||||||||||||||||||||||||||||
getUnreadCounts, | getUnreadCounts, | |||||||||||||||||||||||||||||||||||
apnMaxNotificationPayloadByteSize, | apnMaxNotificationPayloadByteSize, | |||||||||||||||||||||||||||||||||||
fcmMaxNotificationPayloadByteSize, | fcmMaxNotificationPayloadByteSize, | |||||||||||||||||||||||||||||||||||
}; | }; |
We put the error code for APN up here. Should we put the error code for web in the same place?