Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save marcinwasowicz/1af0a2958830fd9eb080e639779b1628 to your computer and use it in GitHub Desktop.
Save marcinwasowicz/1af0a2958830fd9eb080e639779b1628 to your computer and use it in GitHub Desktop.
Hack keyserver code to use custom APNs notif types and constructors
diff --git a/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js b/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js
index d0408dee13..f17faf856a 100644
--- a/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js
+++ b/keyserver/flow-typed/npm/@parse/node-apn_vx.x.x.js
@@ -18,6 +18,8 @@ declare module '@parse/node-apn' {
declare export class Notification {
constructor(): this;
length(): number;
+ headers:() => any,
+ compile: () => string,
body: string;
topic: string;
id: string;
diff --git a/keyserver/src/push/rescind.js b/keyserver/src/push/rescind.js
index efcfa080ec..f23653957c 100644
--- a/keyserver/src/push/rescind.js
+++ b/keyserver/src/push/rescind.js
@@ -1,27 +1,26 @@
// @flow
-import apn from '@parse/node-apn';
+// import apn from '@parse/node-apn';
import type { ResponseFailure } from '@parse/node-apn';
import type { FirebaseError } from 'firebase-admin';
import invariant from 'invariant';
-import { createAndroidNotificationRescind } from 'lib/push/notif-creators.js';
-import { getAPNsNotificationTopic } from 'lib/shared/notif-utils.js';
+import {
+ createAndroidNotificationRescind,
+ createAPNsNotificationRescind,
+} from 'lib/push/notif-creators.js';
import type { PlatformDetails } from 'lib/types/device-types.js';
import type {
+ TargetedAPNsNotification,
NotificationTargetDevice,
TargetedAndroidNotification,
- SenderDeviceID,
- EncryptedNotifUtilsAPI,
} from 'lib/types/notif-types.js';
import { threadSubscriptions } from 'lib/types/subscription-types.js';
import { threadPermissions } from 'lib/types/thread-permission-types.js';
import { promiseAll } from 'lib/utils/promises.js';
import { tID } from 'lib/utils/validation-utils.js';
-import { prepareEncryptedIOSNotificationRescind } from './crypto.js';
import encryptedNotifUtilsAPI from './encrypted-notif-utils-api.js';
-import type { TargetedAPNsNotification } from './types.js';
import {
apnPush,
fcmPush,
@@ -274,47 +273,6 @@ async function getDeviceTokenToCookieID(
return deviceTokenToCookieID;
}
-async function conditionallyEncryptNotification<T>(
- encryptedNotifUtilsAPIInstance: EncryptedNotifUtilsAPI,
- senderDeviceID: SenderDeviceID,
- notification: T,
- codeVersion: ?number,
- devices: $ReadOnlyArray<NotificationTargetDevice>,
- encryptCallback: (
- encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI,
- senderDeviceID: SenderDeviceID,
- devices: $ReadOnlyArray<NotificationTargetDevice>,
- notification: T,
- codeVersion?: ?number,
- ) => Promise<
- $ReadOnlyArray<{
- +notification: T,
- +cryptoID: string,
- +deliveryID: string,
- +encryptionOrder?: number,
- }>,
- >,
-): Promise<$ReadOnlyArray<{ +deliveryID: string, +notification: T }>> {
- const shouldBeEncrypted = codeVersion && codeVersion >= 233;
- if (!shouldBeEncrypted) {
- return devices.map(({ deliveryID }) => ({
- notification,
- deliveryID,
- }));
- }
- const notifications = await encryptCallback(
- encryptedNotifUtilsAPI,
- senderDeviceID,
- devices,
- notification,
- codeVersion,
- );
- return notifications.map(({ deliveryID, notification: notif }) => ({
- deliveryID,
- notification: notif,
- }));
-}
-
async function prepareIOSNotification(
keyserverID: string,
iosID: string,
@@ -324,38 +282,16 @@ async function prepareIOSNotification(
devices: $ReadOnlyArray<NotificationTargetDevice>,
): Promise<$ReadOnlyArray<TargetedAPNsNotification>> {
threadID = await validateOutput(platformDetails, tID, threadID);
- const { codeVersion } = platformDetails;
-
- const notification = new apn.Notification();
- notification.topic = getAPNsNotificationTopic({
- platform: 'ios',
- codeVersion,
- });
-
- if (codeVersion && codeVersion > 198) {
- notification.mutableContent = true;
- notification.pushType = 'alert';
- notification.badge = unreadCount;
- } else {
- notification.priority = 5;
- notification.contentAvailable = true;
- notification.pushType = 'background';
- }
- notification.payload = {
- backgroundNotifType: 'CLEAR',
- notificationId: iosID,
- setUnreadStatus: true,
- threadID,
- keyserverID,
- };
-
- return await conditionallyEncryptNotification(
+ return await createAPNsNotificationRescind(
encryptedNotifUtilsAPI,
- { keyserverID },
- notification,
- codeVersion,
+ {
+ senderDeviceID: { keyserverID },
+ badge: unreadCount,
+ threadID,
+ rescindID: iosID,
+ platformDetails,
+ },
devices,
- prepareEncryptedIOSNotificationRescind,
);
}
diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js
index 129aba8d99..bb9de39c46 100644
--- a/keyserver/src/push/send.js
+++ b/keyserver/src/push/send.js
@@ -1,15 +1,12 @@
// @flow
import type { ResponseFailure } from '@parse/node-apn';
-import apn from '@parse/node-apn';
import invariant from 'invariant';
-import _cloneDeep from 'lodash/fp/cloneDeep.js';
import _flow from 'lodash/fp/flow.js';
import _groupBy from 'lodash/fp/groupBy.js';
import _mapValues from 'lodash/fp/mapValues.js';
import _pickBy from 'lodash/fp/pickBy.js';
import type { QueryResults } from 'mysql';
-import t from 'tcomb';
import uuidv4 from 'uuid/v4.js';
import {
@@ -23,7 +20,10 @@ import {
wnsNotifInputDataValidator,
createWNSNotification,
createAndroidBadgeOnlyNotification,
- apnMaxNotificationPayloadByteSize,
+ type APNsNotifInputData,
+ apnsNotifInputDataValidator,
+ createAPNsVisualNotification,
+ createAPNsBadgeOnlyNotification,
} from 'lib/push/notif-creators.js';
import { oldValidUsernameRegex } from 'lib/shared/account-utils.js';
import { isUserMentioned } from 'lib/shared/mention-utils.js';
@@ -33,41 +33,32 @@ import {
sortMessageInfoList,
} from 'lib/shared/message-utils.js';
import { messageSpecs } from 'lib/shared/messages/message-specs.js';
-import {
- notifTextsForMessageInfo,
- getAPNsNotificationTopic,
-} from 'lib/shared/notif-utils.js';
+import { notifTextsForMessageInfo } from 'lib/shared/notif-utils.js';
import {
rawThreadInfoFromServerThreadInfo,
threadInfoFromRawThreadInfo,
} from 'lib/shared/thread-utils.js';
-import { hasMinCodeVersion } from 'lib/shared/version-utils.js';
import type { Platform, PlatformDetails } from 'lib/types/device-types.js';
import { messageTypes } from 'lib/types/message-types-enum.js';
import {
type MessageData,
type RawMessageInfo,
- rawMessageInfoValidator,
} from 'lib/types/message-types.js';
import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
import type {
NotificationTargetDevice,
+ TargetedAPNsNotification,
TargetedAndroidNotification,
TargetedWebNotification,
TargetedWNSNotification,
- ResolvedNotifTexts,
} from 'lib/types/notif-types.js';
-import { resolvedNotifTextsValidator } from 'lib/types/notif-types.js';
import type { ServerThreadInfo } from 'lib/types/thread-types.js';
import { updateTypes } from 'lib/types/update-types-enum.js';
import { type GlobalUserInfo } from 'lib/types/user-types.js';
import { values } from 'lib/utils/objects.js';
-import { tID, tPlatformDetails, tShape } from 'lib/utils/validation-utils.js';
-import { prepareEncryptedAPNsNotifications } from './crypto.js';
import encryptedNotifUtilsAPI from './encrypted-notif-utils-api.js';
import { rescindPushNotifs } from './rescind.js';
-import type { TargetedAPNsNotification } from './types.js';
import {
apnPush,
fcmPush,
@@ -334,14 +325,15 @@ async function preparePushNotif(input: {
(async () => {
const targetedNotifications = await prepareAPNsNotification(
{
- keyserverID,
+ senderDeviceID: { keyserverID },
notifTexts,
newRawMessageInfos: shimmedNewRawMessageInfos,
threadID: threadInfo.id,
collapseKey: notifInfo.collapseKey,
- badgeOnly,
unreadCount,
platformDetails,
+ badgeOnly,
+ uniqueID: uuidv4(),
},
devices,
);
@@ -455,14 +447,15 @@ async function preparePushNotif(input: {
(async () => {
const targetedNotifications = await prepareAPNsNotification(
{
- keyserverID,
+ senderDeviceID: { keyserverID },
notifTexts,
newRawMessageInfos: shimmedNewRawMessageInfos,
threadID: threadInfo.id,
collapseKey: notifInfo.collapseKey,
- badgeOnly,
unreadCount,
platformDetails,
+ badgeOnly,
+ uniqueID: uuidv4(),
},
devices,
);
@@ -905,36 +898,6 @@ function getDevicesByPlatform(
return byPlatform;
}
-type CommonNativeNotifInputData = {
- +keyserverID: string,
- +notifTexts: ResolvedNotifTexts,
- +newRawMessageInfos: RawMessageInfo[],
- +threadID: string,
- +collapseKey: ?string,
- +unreadCount: number,
- +platformDetails: PlatformDetails,
-};
-
-const commonNativeNotifInputDataValidator = tShape<CommonNativeNotifInputData>({
- keyserverID: t.String,
- notifTexts: resolvedNotifTextsValidator,
- newRawMessageInfos: t.list(rawMessageInfoValidator),
- threadID: tID,
- collapseKey: t.maybe(t.String),
- unreadCount: t.Number,
- platformDetails: tPlatformDetails,
-});
-
-type APNsNotifInputData = {
- ...CommonNativeNotifInputData,
- +badgeOnly: boolean,
-};
-
-const apnsNotifInputDataValidator = tShape<APNsNotifInputData>({
- ...commonNativeNotifInputDataValidator.meta.props,
- badgeOnly: t.Boolean,
-});
-
async function prepareAPNsNotification(
inputData: APNsNotifInputData,
devices: $ReadOnlyArray<NotificationTargetDevice>,
@@ -944,234 +907,12 @@ async function prepareAPNsNotification(
apnsNotifInputDataValidator,
inputData,
);
- const {
- keyserverID,
- notifTexts,
- newRawMessageInfos,
- threadID,
- collapseKey,
- badgeOnly,
- unreadCount,
- platformDetails,
- } = convertedData;
-
- const canDecryptNonCollapsibleTextIOSNotifs =
- platformDetails.codeVersion && platformDetails.codeVersion > 222;
-
- const isNonCollapsibleTextNotification =
- newRawMessageInfos.every(
- newRawMessageInfo => newRawMessageInfo.type === messageTypes.TEXT,
- ) && !collapseKey;
-
- const canDecryptAllIOSNotifs =
- platformDetails.codeVersion && platformDetails.codeVersion >= 267;
-
- const canDecryptIOSNotif =
- platformDetails.platform === 'ios' &&
- (canDecryptAllIOSNotifs ||
- (isNonCollapsibleTextNotification &&
- canDecryptNonCollapsibleTextIOSNotifs));
-
- const canDecryptMacOSNotifs =
- platformDetails.platform === 'macos' &&
- hasMinCodeVersion(platformDetails, {
- web: 47,
- majorDesktop: 9,
- });
-
- const shouldBeEncrypted = canDecryptIOSNotif || canDecryptMacOSNotifs;
-
- const uniqueID = uuidv4();
- const notification = new apn.Notification();
- notification.topic = getAPNsNotificationTopic(platformDetails);
- const { merged, ...rest } = notifTexts;
- // We don't include alert's body on macos because we
- // handle displaying the notification ourselves and
- // we don't want macOS to display it automatically.
- if (!badgeOnly && platformDetails.platform !== 'macos') {
- notification.body = merged;
- notification.sound = 'default';
- }
-
- notification.payload = {
- ...notification.payload,
- ...rest,
- };
-
- notification.badge = unreadCount;
- notification.threadId = threadID;
- notification.id = uniqueID;
- notification.pushType = 'alert';
- notification.payload.id = uniqueID;
- notification.payload.threadID = threadID;
-
- if (platformDetails.codeVersion && platformDetails.codeVersion > 198) {
- notification.mutableContent = true;
- }
- if (collapseKey && (canDecryptAllIOSNotifs || canDecryptMacOSNotifs)) {
- notification.payload.collapseID = collapseKey;
- } else if (collapseKey) {
- notification.collapseId = collapseKey;
- }
- const messageInfos = JSON.stringify(newRawMessageInfos);
- // We make a copy before checking notification's length, because calling
- // length compiles the notification and makes it immutable. Further
- // changes to its properties won't be reflected in the final plaintext
- // data that is sent.
- const copyWithMessageInfos = _cloneDeep(notification);
- copyWithMessageInfos.payload = {
- ...copyWithMessageInfos.payload,
- messageInfos,
- };
-
- const notificationSizeValidator = (notif: apn.Notification) =>
- notif.length() <= apnMaxNotificationPayloadByteSize;
-
- if (!shouldBeEncrypted) {
- const notificationToSend = notificationSizeValidator(
- _cloneDeep(copyWithMessageInfos),
- )
- ? copyWithMessageInfos
- : notification;
- return devices.map(({ deliveryID }) => ({
- notification: notificationToSend,
- deliveryID,
- }));
- }
-
- // The `messageInfos` field in notification payload is
- // not used on MacOS so we can return early.
- if (platformDetails.platform === 'macos') {
- const macOSNotifsWithoutMessageInfos =
- await prepareEncryptedAPNsNotifications(
- encryptedNotifUtilsAPI,
- { keyserverID },
- devices,
- notification,
- platformDetails.codeVersion,
- );
- return macOSNotifsWithoutMessageInfos.map(
- ({ notification: notif, deliveryID }) => ({
- notification: notif,
- deliveryID,
- }),
- );
- }
-
- const notifsWithMessageInfos = await prepareEncryptedAPNsNotifications(
+ return createAPNsVisualNotification(
encryptedNotifUtilsAPI,
- { keyserverID },
+ convertedData,
devices,
- copyWithMessageInfos,
- platformDetails.codeVersion,
- notificationSizeValidator,
);
-
- const devicesWithExcessiveSizeNoHolders = notifsWithMessageInfos
- .filter(({ payloadSizeExceeded }) => payloadSizeExceeded)
- .map(({ cryptoID, deliveryID }) => ({
- cryptoID,
- deliveryID,
- }));
-
- if (devicesWithExcessiveSizeNoHolders.length === 0) {
- return notifsWithMessageInfos.map(
- ({
- notification: notif,
- deliveryID,
- encryptedPayloadHash,
- encryptionOrder,
- }) => ({
- notification: notif,
- deliveryID,
- encryptedPayloadHash,
- encryptionOrder,
- }),
- );
- }
-
- const canQueryBlobService = hasMinCodeVersion(platformDetails, {
- native: 331,
- });
-
- let blobHash, blobHolders, encryptionKey, blobUploadError;
- if (canQueryBlobService) {
- ({ blobHash, blobHolders, encryptionKey, blobUploadError } =
- await encryptedNotifUtilsAPI.uploadLargeNotifPayload(
- copyWithMessageInfos.compile(),
- devicesWithExcessiveSizeNoHolders.length,
- ));
- }
-
- if (blobUploadError) {
- console.warn(
- `Failed to upload payload of notification: ${uniqueID} ` +
- `due to error: ${blobUploadError}`,
- );
- }
-
- let devicesWithExcessiveSize = devicesWithExcessiveSizeNoHolders;
- if (
- blobHash &&
- encryptionKey &&
- blobHolders &&
- blobHolders.length === devicesWithExcessiveSize.length
- ) {
- notification.payload = {
- ...notification.payload,
- blobHash,
- encryptionKey,
- };
-
- devicesWithExcessiveSize = blobHolders.map((holder, idx) => ({
- ...devicesWithExcessiveSize[idx],
- blobHolder: holder,
- }));
- }
-
- const notifsWithoutMessageInfos = await prepareEncryptedAPNsNotifications(
- encryptedNotifUtilsAPI,
- { keyserverID },
- devicesWithExcessiveSize,
- notification,
- platformDetails.codeVersion,
- );
-
- const targetedNotifsWithMessageInfos = notifsWithMessageInfos
- .filter(({ payloadSizeExceeded }) => !payloadSizeExceeded)
- .map(
- ({
- notification: notif,
- deliveryID,
- encryptedPayloadHash,
- encryptionOrder,
- }) => ({
- notification: notif,
- deliveryID,
- encryptedPayloadHash,
- encryptionOrder,
- }),
- );
-
- const targetedNotifsWithoutMessageInfos = notifsWithoutMessageInfos.map(
- ({
- notification: notif,
- deliveryID,
- encryptedPayloadHash,
- encryptionOrder,
- }) => ({
- notification: notif,
- deliveryID,
- encryptedPayloadHash,
- encryptionOrder,
- }),
- );
-
- return [
- ...targetedNotifsWithMessageInfos,
- ...targetedNotifsWithoutMessageInfos,
- ];
}
async function prepareAndroidVisualNotification(
@@ -1238,7 +979,7 @@ type NotificationInfo =
type APNsDelivery = {
+source: $PropertyType<NotificationInfo, 'source'>,
+deviceType: 'ios' | 'macos',
- +iosID: string,
+ +iosID: ?string,
+deviceTokens: $ReadOnlyArray<string>,
+codeVersion: number,
+stateVersion: number,
@@ -1554,37 +1295,16 @@ async function updateBadgeCount(
if (iosVersionsToTokens) {
for (const [versionKey, deviceInfos] of iosVersionsToTokens) {
const { codeVersion, stateVersion } = stringToVersionKey(versionKey);
- const notification = new apn.Notification();
- notification.topic = getAPNsNotificationTopic({
- platform: 'ios',
- codeVersion,
- stateVersion,
- });
- notification.badge = unreadCount;
- notification.pushType = 'alert';
const preparePromise: Promise<PreparePushResult[]> = (async () => {
- let targetedNotifications: $ReadOnlyArray<TargetedAPNsNotification>;
- if (codeVersion > 222) {
- const notificationsArray = await prepareEncryptedAPNsNotifications(
- encryptedNotifUtilsAPI,
- { keyserverID },
- deviceInfos,
- notification,
- codeVersion,
- );
- targetedNotifications = notificationsArray.map(
- ({ notification: notif, deliveryID, encryptionOrder }) => ({
- notification: notif,
- deliveryID,
- encryptionOrder,
- }),
- );
- } else {
- targetedNotifications = deviceInfos.map(({ deliveryID }) => ({
- notification,
- deliveryID,
- }));
- }
+ const targetedNotifications = await createAPNsBadgeOnlyNotification(
+ encryptedNotifUtilsAPI,
+ {
+ senderDeviceID: { keyserverID },
+ badge: unreadCount,
+ platformDetails: { codeVersion, stateVersion, platform: 'ios' },
+ },
+ deviceInfos,
+ );
return targetedNotifications.map(targetedNotification => ({
notification: targetedNotification,
platform: 'ios',
@@ -1642,43 +1362,21 @@ async function updateBadgeCount(
for (const [versionKey, deviceInfos] of macosVersionsToTokens) {
const { codeVersion, stateVersion, majorDesktopVersion } =
stringToVersionKey(versionKey);
- const notification = new apn.Notification();
- notification.topic = getAPNsNotificationTopic({
- platform: 'macos',
- codeVersion,
- stateVersion,
- majorDesktopVersion,
- });
- notification.badge = unreadCount;
- notification.pushType = 'alert';
- notification.payload.keyserverID = keyserverID;
const preparePromise: Promise<PreparePushResult[]> = (async () => {
- const shouldBeEncrypted = hasMinCodeVersion(viewer.platformDetails, {
- web: 47,
- majorDesktop: 9,
- });
- let targetedNotifications: $ReadOnlyArray<TargetedAPNsNotification>;
- if (shouldBeEncrypted) {
- const notificationsArray = await prepareEncryptedAPNsNotifications(
- encryptedNotifUtilsAPI,
- { keyserverID },
- deviceInfos,
- notification,
- codeVersion,
- );
- targetedNotifications = notificationsArray.map(
- ({ notification: notif, deliveryID, encryptionOrder }) => ({
- notification: notif,
- deliveryID,
- encryptionOrder,
- }),
- );
- } else {
- targetedNotifications = deviceInfos.map(({ deliveryID }) => ({
- deliveryID,
- notification,
- }));
- }
+ const targetedNotifications = await createAPNsBadgeOnlyNotification(
+ encryptedNotifUtilsAPI,
+ {
+ senderDeviceID: { keyserverID },
+ badge: unreadCount,
+ platformDetails: {
+ stateVersion,
+ codeVersion,
+ platform: 'macos',
+ majorDesktopVersion,
+ },
+ },
+ deviceInfos,
+ );
return targetedNotifications.map(targetedNotification => ({
notification: targetedNotification,
diff --git a/keyserver/src/push/utils.js b/keyserver/src/push/utils.js
index 41db3657f5..baa1ffe179 100644
--- a/keyserver/src/push/utils.js
+++ b/keyserver/src/push/utils.js
@@ -1,5 +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';
@@ -10,6 +10,7 @@ import webpush from 'web-push';
import type { PlatformDetails } from 'lib/types/device-types.js';
import type {
+ TargetedAPNsNotification,
TargetedAndroidNotification,
TargetedWebNotification,
TargetedWNSNotification,
@@ -26,7 +27,7 @@ import {
ensureWebPushInitialized,
getWNSToken,
} from './providers.js';
-import type { TargetedAPNsNotification } from './types.js';
+// import type { TargetedAPNsNotification } from './types.js';
import { dbQuery, SQL } from '../database/database.js';
import { upload, assignHolder } from '../services/blob.js';
@@ -64,7 +65,14 @@ async function apnPush({
const results = await Promise.all(
targetedNotifications.map(({ notification, deliveryID }) => {
- return apnProvider.send(notification, deliveryID);
+ /// HACK TO TEST
+ const notifToSend = new apn.Notification();
+ notifToSend.headers = () => ({ ...notification.headers });
+ notifToSend.compile = () => {
+ const { headers, ...rest } = notification;
+ return JSON.stringify(rest);
+ };
+ return apnProvider.send(notifToSend, deliveryID);
}),
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment