Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/push/send.js
Show First 20 Lines • Show All 369 Lines • ▼ Show 20 Lines | for (const [versionKey, devices] of androidVersionsToTokens) { | ||||
(async () => { | (async () => { | ||||
const targetedNotifications = await prepareAndroidNotification( | const targetedNotifications = await prepareAndroidNotification( | ||||
{ | { | ||||
keyserverID, | keyserverID, | ||||
notifTexts, | notifTexts, | ||||
newRawMessageInfos: shimmedNewRawMessageInfos, | newRawMessageInfos: shimmedNewRawMessageInfos, | ||||
threadID: threadInfo.id, | threadID: threadInfo.id, | ||||
collapseKey: notifInfo.collapseKey, | collapseKey: notifInfo.collapseKey, | ||||
badgeOnly, | |||||
unreadCount, | unreadCount, | ||||
platformDetails, | platformDetails, | ||||
dbID, | dbID, | ||||
}, | }, | ||||
devices, | devices, | ||||
); | ); | ||||
return targetedNotifications.map(notification => ({ | return targetedNotifications.map(notification => ({ | ||||
notification, | notification, | ||||
▲ Show 20 Lines • Show All 508 Lines • ▼ Show 20 Lines | for (const device of devices) { | ||||
innerMostArray.push({ | innerMostArray.push({ | ||||
cookieID: device.cookieID, | cookieID: device.cookieID, | ||||
deviceToken: device.deviceToken, | deviceToken: device.deviceToken, | ||||
}); | }); | ||||
} | } | ||||
return byPlatform; | return byPlatform; | ||||
} | } | ||||
type APNsNotifInputData = { | type CommonNativeNotifInputData = { | ||||
+keyserverID: string, | +keyserverID: string, | ||||
+notifTexts: ResolvedNotifTexts, | +notifTexts: ResolvedNotifTexts, | ||||
+newRawMessageInfos: RawMessageInfo[], | +newRawMessageInfos: RawMessageInfo[], | ||||
+threadID: string, | +threadID: string, | ||||
+collapseKey: ?string, | +collapseKey: ?string, | ||||
+badgeOnly: boolean, | |||||
+unreadCount: number, | +unreadCount: number, | ||||
+platformDetails: PlatformDetails, | +platformDetails: PlatformDetails, | ||||
}; | }; | ||||
const apnsNotifInputDataValidator = tShape<APNsNotifInputData>({ | |||||
const commonNativeNotifInputDataValidator = tShape<CommonNativeNotifInputData>({ | |||||
keyserverID: t.String, | keyserverID: t.String, | ||||
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, | |||||
unreadCount: t.Number, | unreadCount: t.Number, | ||||
platformDetails: tPlatformDetails, | platformDetails: tPlatformDetails, | ||||
}); | }); | ||||
type APNsNotifInputData = { | |||||
...CommonNativeNotifInputData, | |||||
+badgeOnly: boolean, | |||||
}; | |||||
const apnsNotifInputDataValidator = tShape<APNsNotifInputData>({ | |||||
...commonNativeNotifInputDataValidator.meta.props, | |||||
badgeOnly: t.Boolean, | |||||
}); | |||||
async function prepareAPNsNotification( | async function prepareAPNsNotification( | ||||
inputData: APNsNotifInputData, | inputData: APNsNotifInputData, | ||||
devices: $ReadOnlyArray<NotificationTargetDevice>, | devices: $ReadOnlyArray<NotificationTargetDevice>, | ||||
): Promise<$ReadOnlyArray<TargetedAPNsNotification>> { | ): Promise<$ReadOnlyArray<TargetedAPNsNotification>> { | ||||
const convertedData = await validateOutput( | const convertedData = await validateOutput( | ||||
inputData.platformDetails, | inputData.platformDetails, | ||||
apnsNotifInputDataValidator, | apnsNotifInputDataValidator, | ||||
inputData, | inputData, | ||||
▲ Show 20 Lines • Show All 216 Lines • ▼ Show 20 Lines | ): Promise<$ReadOnlyArray<TargetedAPNsNotification>> { | ||||
return [ | return [ | ||||
...targetedNotifsWithMessageInfos, | ...targetedNotifsWithMessageInfos, | ||||
...targetedNotifsWithoutMessageInfos, | ...targetedNotifsWithoutMessageInfos, | ||||
]; | ]; | ||||
} | } | ||||
type AndroidNotifInputData = { | type AndroidNotifInputData = { | ||||
...APNsNotifInputData, | ...CommonNativeNotifInputData, | ||||
+dbID: string, | +dbID: string, | ||||
}; | }; | ||||
const androidNotifInputDataValidator = tShape<AndroidNotifInputData>({ | const androidNotifInputDataValidator = tShape<AndroidNotifInputData>({ | ||||
...apnsNotifInputDataValidator.meta.props, | ...commonNativeNotifInputDataValidator.meta.props, | ||||
dbID: t.String, | dbID: t.String, | ||||
}); | }); | ||||
async function prepareAndroidNotification( | async function prepareAndroidNotification( | ||||
inputData: AndroidNotifInputData, | inputData: AndroidNotifInputData, | ||||
devices: $ReadOnlyArray<NotificationTargetDevice>, | devices: $ReadOnlyArray<NotificationTargetDevice>, | ||||
): Promise<$ReadOnlyArray<TargetedAndroidNotification>> { | ): Promise<$ReadOnlyArray<TargetedAndroidNotification>> { | ||||
const convertedData = await validateOutput( | const convertedData = await validateOutput( | ||||
inputData.platformDetails, | inputData.platformDetails, | ||||
androidNotifInputDataValidator, | androidNotifInputDataValidator, | ||||
inputData, | inputData, | ||||
); | ); | ||||
const { | const { | ||||
keyserverID, | keyserverID, | ||||
notifTexts, | notifTexts, | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
threadID, | threadID, | ||||
collapseKey, | collapseKey, | ||||
badgeOnly, | |||||
unreadCount, | unreadCount, | ||||
platformDetails: { codeVersion }, | platformDetails, | ||||
dbID, | dbID, | ||||
} = convertedData; | } = convertedData; | ||||
const canDecryptNonCollapsibleTextNotifs = codeVersion && codeVersion > 228; | |||||
const canDecryptNonCollapsibleTextNotifs = hasMinCodeVersion( | |||||
platformDetails, | |||||
{ native: 228 }, | |||||
); | |||||
const isNonCollapsibleTextNotif = | const isNonCollapsibleTextNotif = | ||||
newRawMessageInfos.every( | newRawMessageInfos.every( | ||||
newRawMessageInfo => newRawMessageInfo.type === messageTypes.TEXT, | newRawMessageInfo => newRawMessageInfo.type === messageTypes.TEXT, | ||||
) && !collapseKey; | ) && !collapseKey; | ||||
const canDecryptAllNotifTypes = codeVersion && codeVersion >= 267; | const canDecryptAllNotifTypes = hasMinCodeVersion(platformDetails, { | ||||
native: 267, | |||||
}); | |||||
const shouldBeEncrypted = | const shouldBeEncrypted = | ||||
canDecryptAllNotifTypes || | canDecryptAllNotifTypes || | ||||
(canDecryptNonCollapsibleTextNotifs && isNonCollapsibleTextNotif); | (canDecryptNonCollapsibleTextNotifs && isNonCollapsibleTextNotif); | ||||
const { merged, ...rest } = notifTexts; | const { merged, ...rest } = notifTexts; | ||||
const notification = { | const notification = { | ||||
data: { | data: { | ||||
Show All 12 Lines | notification.data = { | ||||
collapseKey, | collapseKey, | ||||
}; | }; | ||||
} else if (collapseKey) { | } else if (collapseKey) { | ||||
notifID = collapseKey; | notifID = collapseKey; | ||||
} else { | } else { | ||||
notifID = dbID; | notifID = dbID; | ||||
} | } | ||||
// The reason we only include `badgeOnly` for newer clients is because older | |||||
// clients don't know how to parse it. The reason we only include `id` for | |||||
// newer clients is that if the older clients see that field, they assume | |||||
// the notif has a full payload, and then crash when trying to parse it. | |||||
// By skipping `id` we allow old clients to still handle in-app notifs and | |||||
// badge updating. | |||||
if (!badgeOnly || (codeVersion && codeVersion >= 69)) { | |||||
notification.data = { | notification.data = { | ||||
...notification.data, | ...notification.data, | ||||
id: notifID, | id: notifID, | ||||
badgeOnly: badgeOnly ? '1' : '0', | badgeOnly: '0', | ||||
}; | }; | ||||
} | |||||
const messageInfos = JSON.stringify(newRawMessageInfos); | const messageInfos = JSON.stringify(newRawMessageInfos); | ||||
const copyWithMessageInfos = { | const copyWithMessageInfos = { | ||||
...notification, | ...notification, | ||||
data: { ...notification.data, messageInfos }, | data: { ...notification.data, messageInfos }, | ||||
}; | }; | ||||
if (!shouldBeEncrypted) { | if (!shouldBeEncrypted) { | ||||
Show All 32 Lines | return notifsWithMessageInfos.map( | ||||
({ notification: notif, deviceToken, encryptionOrder }) => ({ | ({ notification: notif, deviceToken, encryptionOrder }) => ({ | ||||
notification: notif, | notification: notif, | ||||
deviceToken, | deviceToken, | ||||
encryptionOrder, | encryptionOrder, | ||||
}), | }), | ||||
); | ); | ||||
} | } | ||||
const canQueryBlobService = hasMinCodeVersion(platformDetails, { | |||||
native: NEXT_CODE_VERSION, | |||||
}); | |||||
let blobHash, encryptionKey, blobUploadError; | |||||
if (canQueryBlobService) { | |||||
({ blobHash, encryptionKey, blobUploadError } = await blobServiceUpload( | |||||
JSON.stringify(copyWithMessageInfos.data), | |||||
1, | |||||
)); | |||||
} | |||||
if (blobUploadError) { | |||||
console.warn( | |||||
`Failed to upload payload of notification: ${notifID} ` + | |||||
`due to error: ${blobUploadError}`, | |||||
); | |||||
} | |||||
if (blobHash && encryptionKey) { | |||||
notification.data = { | |||||
...notification.data, | |||||
blobHash, | |||||
encryptionKey, | |||||
}; | |||||
} | |||||
const notifsWithoutMessageInfos = await prepareEncryptedAndroidNotifications( | const notifsWithoutMessageInfos = await prepareEncryptedAndroidNotifications( | ||||
devicesWithExcessiveSize, | devicesWithExcessiveSize, | ||||
notification, | notification, | ||||
); | ); | ||||
const targetedNotifsWithMessageInfos = notifsWithMessageInfos | const targetedNotifsWithMessageInfos = notifsWithMessageInfos | ||||
.filter(({ payloadSizeExceeded }) => !payloadSizeExceeded) | .filter(({ payloadSizeExceeded }) => !payloadSizeExceeded) | ||||
.map(({ notification: notif, deviceToken, encryptionOrder }) => ({ | .map(({ notification: notif, deviceToken, encryptionOrder }) => ({ | ||||
▲ Show 20 Lines • Show All 614 Lines • Show Last 20 Lines |