Page MenuHomePhabricator

D7815.id.diff
No OneTemporary

D7815.id.diff

diff --git a/keyserver/src/creators/message-creator.js b/keyserver/src/creators/message-creator.js
--- a/keyserver/src/creators/message-creator.js
+++ b/keyserver/src/creators/message-creator.js
@@ -52,6 +52,7 @@
{
+platform: string,
+deviceToken: string,
+ +cookieID: string,
+codeVersion: ?string,
},
>,
@@ -323,7 +324,7 @@
const time = earliestFocusedTimeConsideredExpired();
const visibleExtractString = `$.${threadPermissions.VISIBLE}.value`;
const query = SQL`
- SELECT m.user, m.thread, c.platform, c.device_token, c.versions,
+ SELECT m.user, m.thread, c.platform, c.device_token, c.versions, c.id,
f.user AS focused_user
`;
query.append(subthreadSelects);
@@ -349,6 +350,7 @@
const focusedUser = !!row.focused_user;
const { platform } = row;
const versions = JSON.parse(row.versions);
+ const cookieID = row.id;
let thisUserInfo = perUserInfo.get(userID);
if (!thisUserInfo) {
thisUserInfo = {
@@ -384,10 +386,11 @@
}
}
}
- if (deviceToken) {
+ if (deviceToken && cookieID) {
thisUserInfo.devices.set(deviceToken, {
platform,
deviceToken,
+ cookieID: cookieID.toString(),
codeVersion: versions ? versions.codeVersion : null,
});
}
diff --git a/keyserver/src/push/rescind.js b/keyserver/src/push/rescind.js
--- a/keyserver/src/push/rescind.js
+++ b/keyserver/src/push/rescind.js
@@ -58,9 +58,11 @@
row.unread_count,
threadID,
);
+ const targetedNotifications = delivery.iosDeviceTokens.map(
+ deviceToken => ({ deviceToken, notification }),
+ );
deliveryPromises[id] = apnPush({
- notification,
- deviceTokens: delivery.iosDeviceTokens,
+ targetedNotifications,
platformDetails: { platform: 'ios' },
});
} else if (delivery.androidID) {
@@ -84,9 +86,12 @@
threadID,
codeVersion,
);
- deliveryPromises[id] = apnPush({
+ const targetedNotifications = deviceTokens.map(deviceToken => ({
+ deviceToken,
notification,
- deviceTokens,
+ }));
+ deliveryPromises[id] = apnPush({
+ targetedNotifications,
platformDetails: { platform: 'ios', codeVersion },
});
} else if (delivery.deviceType === 'android') {
diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js
--- a/keyserver/src/push/send.js
+++ b/keyserver/src/push/send.js
@@ -41,8 +41,10 @@
import { promiseAll } from 'lib/utils/promises.js';
import { tID, tPlatformDetails, tShape } from 'lib/utils/validation-utils.js';
+import { prepareEncryptedIOSNotifications } from './crypto.js';
import { getAPNsNotificationTopic } from './providers.js';
import { rescindPushNotifs } from './rescind.js';
+import type { TargetedAPNsNotification } from './types.js';
import {
apnPush,
fcmPush,
@@ -69,8 +71,13 @@
type Device = {
+platform: Platform,
+deviceToken: string,
+ +cookieID: string,
+codeVersion: ?number,
};
+type NotificationTargetDevice = {
+ +cookieID: string,
+ +deviceToken: string,
+};
type PushUserInfo = {
+devices: Device[],
// messageInfos and messageDatas have the same key
@@ -195,38 +202,36 @@
const iosVersionsToTokens = byPlatform.get('ios');
if (iosVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of iosVersionsToTokens) {
+ for (const [codeVersion, devices] of iosVersionsToTokens) {
const platformDetails = { platform: 'ios', codeVersion };
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos(
newRawMessageInfos,
platformDetails,
);
const deliveryPromise = (async () => {
- const notification = await prepareAPNsNotification({
- notifTexts,
- newRawMessageInfos: shimmedNewRawMessageInfos,
- threadID: threadInfo.id,
- collapseKey: notifInfo.collapseKey,
- badgeOnly,
- unreadCount: unreadCounts[userID],
- platformDetails,
- });
- return await sendAPNsNotification(
- 'ios',
- notification,
- [...deviceTokens],
+ const targetedNotifications = await prepareAPNsNotification(
{
- ...notificationInfo,
- codeVersion,
+ notifTexts,
+ newRawMessageInfos: shimmedNewRawMessageInfos,
+ threadID: threadInfo.id,
+ collapseKey: notifInfo.collapseKey,
+ badgeOnly,
+ unreadCount: unreadCounts[userID],
+ platformDetails,
},
+ devices,
);
+ return await sendAPNsNotification('ios', targetedNotifications, {
+ ...notificationInfo,
+ codeVersion,
+ });
})();
deliveryPromises.push(deliveryPromise);
}
}
const androidVersionsToTokens = byPlatform.get('android');
if (androidVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of androidVersionsToTokens) {
+ for (const [codeVersion, devices] of androidVersionsToTokens) {
const platformDetails = { platform: 'android', codeVersion };
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos(
newRawMessageInfos,
@@ -243,21 +248,18 @@
platformDetails,
dbID,
});
- return await sendAndroidNotification(
- notification,
- [...deviceTokens],
- {
- ...notificationInfo,
- codeVersion,
- },
- );
+ const deviceTokens = devices.map(({ deviceToken }) => deviceToken);
+ return await sendAndroidNotification(notification, deviceTokens, {
+ ...notificationInfo,
+ codeVersion,
+ });
})();
deliveryPromises.push(deliveryPromise);
}
}
const webVersionsToTokens = byPlatform.get('web');
if (webVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of webVersionsToTokens) {
+ for (const [codeVersion, devices] of webVersionsToTokens) {
const platformDetails = { platform: 'web', codeVersion };
const deliveryPromise = (async () => {
const notification = await prepareWebNotification({
@@ -266,7 +268,8 @@
unreadCount: unreadCounts[userID],
platformDetails,
});
- return await sendWebNotification(notification, [...deviceTokens], {
+ const deviceTokens = devices.map(({ deviceToken }) => deviceToken);
+ return await sendWebNotification(notification, deviceTokens, {
...notificationInfo,
codeVersion,
});
@@ -276,38 +279,36 @@
}
const macosVersionsToTokens = byPlatform.get('macos');
if (macosVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of macosVersionsToTokens) {
+ for (const [codeVersion, devices] of macosVersionsToTokens) {
const platformDetails = { platform: 'macos', codeVersion };
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos(
newRawMessageInfos,
platformDetails,
);
const deliveryPromise = (async () => {
- const notification = await prepareAPNsNotification({
- notifTexts,
- newRawMessageInfos: shimmedNewRawMessageInfos,
- threadID: threadInfo.id,
- collapseKey: notifInfo.collapseKey,
- badgeOnly,
- unreadCount: unreadCounts[userID],
- platformDetails,
- });
- return await sendAPNsNotification(
- 'macos',
- notification,
- [...deviceTokens],
+ const targetedNotifications = await prepareAPNsNotification(
{
- ...notificationInfo,
- codeVersion,
+ notifTexts,
+ newRawMessageInfos: shimmedNewRawMessageInfos,
+ threadID: threadInfo.id,
+ collapseKey: notifInfo.collapseKey,
+ badgeOnly,
+ unreadCount: unreadCounts[userID],
+ platformDetails,
},
+ devices,
);
+ return await sendAPNsNotification('macos', targetedNotifications, {
+ ...notificationInfo,
+ codeVersion,
+ });
})();
deliveryPromises.push(deliveryPromise);
}
}
const windowsVersionsToTokens = byPlatform.get('windows');
if (windowsVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of windowsVersionsToTokens) {
+ for (const [codeVersion, devices] of windowsVersionsToTokens) {
const platformDetails = { platform: 'windows', codeVersion };
const deliveryPromise = (async () => {
const notification = await prepareWNSNotification({
@@ -316,7 +317,8 @@
unreadCount: unreadCounts[userID],
platformDetails,
});
- return await sendWNSNotification(notification, [...deviceTokens], {
+ const deviceTokens = devices.map(({ deviceToken }) => deviceToken);
+ return await sendWNSNotification(notification, deviceTokens, {
...notificationInfo,
codeVersion,
});
@@ -586,8 +588,8 @@
}
function getDevicesByPlatform(
- devices: Device[],
-): Map<Platform, Map<number, Set<string>>> {
+ devices: $ReadOnlyArray<Device>,
+): Map<Platform, Map<number, Array<NotificationTargetDevice>>> {
const byPlatform = new Map();
for (const device of devices) {
let innerMap = byPlatform.get(device.platform);
@@ -602,12 +604,16 @@
device.platform !== 'macos'
? device.codeVersion
: -1;
- let innerMostSet = innerMap.get(codeVersion);
- if (!innerMostSet) {
- innerMostSet = new Set();
- innerMap.set(codeVersion, innerMostSet);
+ let innerMostArray = innerMap.get(codeVersion);
+ if (!innerMostArray) {
+ innerMostArray = [];
+ innerMap.set(codeVersion, innerMostArray);
}
- innerMostSet.add(device.deviceToken);
+
+ innerMostArray.push({
+ cookieID: device.cookieID,
+ deviceToken: device.deviceToken,
+ });
}
return byPlatform;
}
@@ -632,7 +638,8 @@
});
async function prepareAPNsNotification(
inputData: APNsNotifInputData,
-): Promise<apn.Notification> {
+ devices: $ReadOnlyArray<NotificationTargetDevice>,
+): Promise<$ReadOnlyArray<TargetedAPNsNotification>> {
const convertedData = validateOutput(
inputData.platformDetails,
apnsNotifInputDataValidator,
@@ -648,6 +655,12 @@
platformDetails,
} = convertedData;
+ const isTextNotification = newRawMessageInfos.every(
+ newRawMessageInfo => newRawMessageInfo.type === messageTypes.TEXT,
+ );
+ const shouldBeEncrypted =
+ platformDetails.platform === 'ios' && !collapseKey && isTextNotification;
+
const uniqueID = uuidv4();
const notification = new apn.Notification();
notification.topic = getAPNsNotificationTopic(platformDetails);
@@ -688,18 +701,48 @@
...copyWithMessageInfos.payload,
messageInfos,
};
- if (copyWithMessageInfos.length() <= apnMaxNotificationPayloadByteSize) {
- notification.payload.messageInfos = messageInfos;
- return notification;
- }
- const notificationCopy = _cloneDeep(notification);
- if (notificationCopy.length() > apnMaxNotificationPayloadByteSize) {
- console.warn(
- `${platformDetails.platform} notification ${uniqueID} ` +
- `exceeds size limit, even with messageInfos omitted`,
- );
+
+ const evaluateAndSelectNotifPayload = (notif, notifWithMessageInfos) => {
+ const notifWithMessageInfosCopy = _cloneDeep(notifWithMessageInfos);
+ if (
+ notifWithMessageInfosCopy.length() <= apnMaxNotificationPayloadByteSize
+ ) {
+ return notifWithMessageInfos;
+ }
+ const notifCopy = _cloneDeep(notif);
+ if (notifCopy.length() > apnMaxNotificationPayloadByteSize) {
+ console.warn(
+ `${platformDetails.platform} notification ${uniqueID} ` +
+ `exceeds size limit, even with messageInfos omitted`,
+ );
+ }
+ return notif;
+ };
+
+ const deviceTokens = devices.map(({ deviceToken }) => deviceToken);
+ if (
+ shouldBeEncrypted &&
+ platformDetails.codeVersion &&
+ platformDetails.codeVersion > 222
+ ) {
+ const cookieIDs = devices.map(({ cookieID }) => cookieID);
+ const [notifications, notificationsWithMessageInfos] = await Promise.all([
+ prepareEncryptedIOSNotifications(cookieIDs, notification),
+ prepareEncryptedIOSNotifications(cookieIDs, copyWithMessageInfos),
+ ]);
+ return notificationsWithMessageInfos.map((notif, idx) => ({
+ notification: evaluateAndSelectNotifPayload(notifications[idx], notif),
+ deviceToken: deviceTokens[idx],
+ }));
}
- return notification;
+ const notificationToSend = evaluateAndSelectNotifPayload(
+ notification,
+ copyWithMessageInfos,
+ );
+ return deviceTokens.map(deviceToken => ({
+ notification: notificationToSend,
+ deviceToken,
+ }));
}
type AndroidNotifInputData = {
@@ -878,20 +921,28 @@
};
async function sendAPNsNotification(
platform: 'ios' | 'macos',
- notification: apn.Notification,
- deviceTokens: $ReadOnlyArray<string>,
+ targetedNotifications: $ReadOnlyArray<TargetedAPNsNotification>,
notificationInfo: NotificationInfo,
): Promise<APNsResult> {
const { source, codeVersion } = notificationInfo;
+
const response = await apnPush({
- notification,
- deviceTokens,
+ targetedNotifications,
platformDetails: { platform, codeVersion },
});
+ invariant(
+ new Set(targetedNotifications.map(({ notification }) => notification.id))
+ .size === 1,
+ 'Encrypted versions of the same notification must share id value',
+ );
+ const iosID = targetedNotifications[0].notification.id;
+ const deviceTokens = targetedNotifications.map(
+ ({ deviceToken }) => deviceToken,
+ );
const delivery: APNsDelivery = {
source,
deviceType: platform,
- iosID: notification.id,
+ iosID,
deviceTokens,
codeVersion,
};
@@ -1111,7 +1162,7 @@
const { userID } = viewer;
const deviceTokenQuery = SQL`
- SELECT platform, device_token, versions
+ SELECT platform, device_token, versions, id
FROM cookies
WHERE user = ${userID}
AND device_token IS NOT NULL
@@ -1128,6 +1179,7 @@
const devices = deviceTokenResult.map(row => ({
platform: row.platform,
+ cookieID: row.id,
deviceToken: row.device_token,
codeVersion: JSON.parse(row.versions)?.codeVersion,
}));
@@ -1137,7 +1189,7 @@
const iosVersionsToTokens = byPlatform.get('ios');
if (iosVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of iosVersionsToTokens) {
+ for (const [codeVersion, deviceInfos] of iosVersionsToTokens) {
const notification = new apn.Notification();
notification.topic = getAPNsNotificationTopic({
platform: 'ios',
@@ -1145,27 +1197,46 @@
});
notification.badge = unreadCount;
notification.pushType = 'alert';
- deliveryPromises.push(
- sendAPNsNotification('ios', notification, [...deviceTokens], {
+ const deliveryPromise = (async () => {
+ const cookieIDs = deviceInfos.map(({ cookieID }) => cookieID);
+ let notificationsArray;
+ if (codeVersion > 222) {
+ notificationsArray = await prepareEncryptedIOSNotifications(
+ cookieIDs,
+ notification,
+ );
+ } else {
+ notificationsArray = cookieIDs.map(() => notification);
+ }
+ const targetedNotifications = deviceInfos.map(
+ ({ deviceToken }, idx) => ({
+ deviceToken,
+ notification: notificationsArray[idx],
+ }),
+ );
+ return await sendAPNsNotification('ios', targetedNotifications, {
source,
dbID,
userID,
codeVersion,
- }),
- );
+ });
+ })();
+
+ deliveryPromises.push(deliveryPromise);
}
}
const androidVersionsToTokens = byPlatform.get('android');
if (androidVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of androidVersionsToTokens) {
+ for (const [codeVersion, deviceInfos] of androidVersionsToTokens) {
const notificationData =
codeVersion < 69
? { badge: unreadCount.toString() }
: { badge: unreadCount.toString(), badgeOnly: '1' };
const notification = { data: notificationData };
+ const deviceTokens = deviceInfos.map(({ deviceToken }) => deviceToken);
deliveryPromises.push(
- sendAndroidNotification(notification, [...deviceTokens], {
+ sendAndroidNotification(notification, deviceTokens, {
source,
dbID,
userID,
@@ -1177,7 +1248,7 @@
const macosVersionsToTokens = byPlatform.get('macos');
if (macosVersionsToTokens) {
- for (const [codeVersion, deviceTokens] of macosVersionsToTokens) {
+ for (const [codeVersion, deviceInfos] of macosVersionsToTokens) {
const notification = new apn.Notification();
notification.topic = getAPNsNotificationTopic({
platform: 'macos',
@@ -1185,8 +1256,12 @@
});
notification.badge = unreadCount;
notification.pushType = 'alert';
+ const targetedNotifications = deviceInfos.map(({ deviceToken }) => ({
+ deviceToken,
+ notification,
+ }));
deliveryPromises.push(
- sendAPNsNotification('macos', notification, [...deviceTokens], {
+ sendAPNsNotification('macos', targetedNotifications, {
source,
dbID,
userID,
diff --git a/keyserver/src/push/types.js b/keyserver/src/push/types.js
new file mode 100644
--- /dev/null
+++ b/keyserver/src/push/types.js
@@ -0,0 +1,8 @@
+// @flow
+
+import apn from '@parse/node-apn';
+
+export type TargetedAPNsNotification = {
+ +notification: apn.Notification,
+ +deviceToken: string,
+};
diff --git a/keyserver/src/push/utils.js b/keyserver/src/push/utils.js
--- a/keyserver/src/push/utils.js
+++ b/keyserver/src/push/utils.js
@@ -1,6 +1,5 @@
// @flow
-import apn from '@parse/node-apn';
import type { ResponseFailure } from '@parse/node-apn';
import type { FirebaseApp, FirebaseError } from 'firebase-admin';
import invariant from 'invariant';
@@ -24,6 +23,7 @@
ensureWebPushInitialized,
getWNSToken,
} from './providers.js';
+import type { TargetedAPNsNotification } from './types.js';
import { dbQuery, SQL } from '../database/database.js';
const fcmTokenInvalidationErrors = new Set([
@@ -46,12 +46,10 @@
+invalidTokens?: $ReadOnlyArray<string>,
};
async function apnPush({
- notification,
- deviceTokens,
+ targetedNotifications,
platformDetails,
}: {
- +notification: apn.Notification,
- +deviceTokens: $ReadOnlyArray<string>,
+ +targetedNotifications: $ReadOnlyArray<TargetedAPNsNotification>,
+platformDetails: PlatformDetails,
}): Promise<APNPushResult> {
const pushProfile = getAPNPushProfileForCodeVersion(platformDetails);
@@ -61,10 +59,22 @@
return { success: true };
}
invariant(apnProvider, `keyserver/secrets/${pushProfile}.json should exist`);
- const result = await apnProvider.send(notification, deviceTokens);
+
+ const results = await Promise.all(
+ targetedNotifications.map(({ notification, deviceToken }) => {
+ return apnProvider.send(notification, deviceToken);
+ }),
+ );
+
+ const mergedResults = { sent: [], failed: [] };
+ for (const result of results) {
+ mergedResults.sent.push(...result.sent);
+ mergedResults.failed.push(...result.failed);
+ }
+
const errors = [];
const invalidTokens = [];
- for (const error of result.failed) {
+ for (const error of mergedResults.failed) {
errors.push(error);
/* eslint-disable eqeqeq */
if (

File Metadata

Mime Type
text/plain
Expires
Thu, Dec 26, 7:10 AM (12 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2704231
Default Alt Text
D7815.id.diff (19 KB)

Event Timeline