Page MenuHomePhabricator

D6817.id23275.diff
No OneTemporary

D6817.id23275.diff

diff --git a/keyserver/src/push/providers.js b/keyserver/src/push/providers.js
--- a/keyserver/src/push/providers.js
+++ b/keyserver/src/push/providers.js
@@ -101,6 +101,13 @@
return cachedWebPushConfig;
}
+async function ensureWebPushInitialized() {
+ if (cachedWebPushConfig) {
+ return;
+ }
+ await getWebPushConfig();
+}
+
export {
getAPNPushProfileForCodeVersion,
getFCMPushProfileForCodeVersion,
@@ -110,4 +117,5 @@
endAPNs,
getAPNsNotificationTopic,
getWebPushConfig,
+ ensureWebPushInitialized,
};
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
@@ -28,6 +28,7 @@
type MessageInfo,
messageTypes,
} from 'lib/types/message-types.js';
+import type { WebNotification } from 'lib/types/notif-types.js';
import type { ServerThreadInfo, ThreadInfo } from 'lib/types/thread-types.js';
import { updateTypes } from 'lib/types/update-types.js';
import type { UserInfo } from 'lib/types/user-types.js';
@@ -41,6 +42,8 @@
getUnreadCounts,
apnMaxNotificationPayloadByteSize,
fcmMaxNotificationPayloadByteSize,
+ webPush,
+ type WebPushError,
} from './utils.js';
import createIDs from '../creators/id-creator.js';
import { createUpdates } from '../creators/update-creator.js';
@@ -61,7 +64,7 @@
+devices: Device[],
+messageInfos: RawMessageInfo[],
};
-type Delivery = IOSDelivery | AndroidDelivery | { collapsedInto: string };
+type Delivery = PushDelivery | { collapsedInto: string };
type NotificationRow = {
+dbID: string,
+userID: string,
@@ -231,6 +234,27 @@
deliveryPromises.push(deliveryPromise);
}
}
+ const webVersionsToTokens = byPlatform.get('web');
+ if (webVersionsToTokens) {
+ for (const [codeVersion, deviceTokens] of webVersionsToTokens) {
+ const deliveryPromise = (async () => {
+ const notification = await prepareWebNotification({
+ allMessageInfos,
+ threadInfo,
+ unreadCount: unreadCounts[userID],
+ notifTargetUserInfo: {
+ id: userID,
+ username,
+ },
+ });
+ return await sendWebNotification(notification, [...deviceTokens], {
+ ...notificationInfo,
+ codeVersion,
+ });
+ })();
+ deliveryPromises.push(deliveryPromise);
+ }
+ }
for (const newMessageInfo of remainingNewMessageInfos) {
const newDBID = dbIDs.shift();
@@ -290,7 +314,7 @@
// The results in deliveryResults will be combined with the rows
// in rowsToSave and then written to the notifications table
async function saveNotifResults(
- deliveryResults: $ReadOnlyArray<IOSResult | AndroidResult>,
+ deliveryResults: $ReadOnlyArray<PushResult>,
inputRowsToSave: Map<string, NotificationRow>,
rescindable: boolean,
) {
@@ -666,6 +690,33 @@
return notification;
}
+type WebNotifInputData = {
+ +allMessageInfos: MessageInfo[],
+ +threadInfo: ThreadInfo,
+ +unreadCount: number,
+ +notifTargetUserInfo: UserInfo,
+};
+async function prepareWebNotification(
+ inputData: WebNotifInputData,
+): Promise<WebNotification> {
+ const { allMessageInfos, threadInfo, unreadCount, notifTargetUserInfo } =
+ inputData;
+ const id = uuidv4();
+ const { merged, ...rest } = await notifTextsForMessageInfo(
+ allMessageInfos,
+ threadInfo,
+ notifTargetUserInfo,
+ getENSNames,
+ );
+ const notification = {
+ ...rest,
+ unreadCount,
+ id,
+ threadID: threadInfo.id,
+ };
+ return notification;
+}
+
type NotificationInfo =
| {
+source: 'new_message',
@@ -723,6 +774,8 @@
return result;
}
+type PushResult = AndroidResult | IOSResult | WebResult;
+type PushDelivery = AndroidDelivery | IOSDelivery | WebDelivery;
type AndroidDelivery = {
source: $PropertyType<NotificationInfo, 'source'>,
deviceType: 'android',
@@ -772,6 +825,45 @@
return result;
}
+type WebDelivery = {
+ +source: $PropertyType<NotificationInfo, 'source'>,
+ +deviceType: 'web',
+ +deviceTokens: $ReadOnlyArray<string>,
+ +codeVersion?: number,
+ +errors?: $ReadOnlyArray<WebPushError>,
+};
+type WebResult = {
+ +info: NotificationInfo,
+ +delivery: WebDelivery,
+ +invalidTokens?: $ReadOnlyArray<string>,
+};
+async function sendWebNotification(
+ notification: WebNotification,
+ deviceTokens: $ReadOnlyArray<string>,
+ notificationInfo: NotificationInfo,
+): Promise<WebResult> {
+ const { source, codeVersion } = notificationInfo;
+
+ const response = await webPush({
+ notification,
+ deviceTokens,
+ });
+
+ const delivery: WebDelivery = {
+ source,
+ deviceType: 'web',
+ deviceTokens,
+ codeVersion,
+ errors: response.errors,
+ };
+ const result: WebResult = {
+ info: notificationInfo,
+ delivery,
+ invalidTokens: response.invalidTokens,
+ };
+ return result;
+}
+
type InvalidToken = {
+userID: string,
+tokens: $ReadOnlyArray<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
@@ -4,7 +4,9 @@
import type { ResponseFailure } from '@parse/node-apn';
import type { FirebaseApp, FirebaseError } from 'firebase-admin';
import invariant from 'invariant';
+import webpush from 'web-push';
+import type { WebNotification } from 'lib/types/notif-types.js';
import { threadSubscriptions } from 'lib/types/subscription-types.js';
import { threadPermissions } from 'lib/types/thread-types.js';
@@ -13,6 +15,7 @@
getFCMPushProfileForCodeVersion,
getAPNProvider,
getFCMProvider,
+ ensureWebPushInitialized,
} from './providers.js';
import { dbQuery, SQL } from '../database/database.js';
@@ -25,6 +28,7 @@
const apnBadRequestErrorCode = 400;
const apnBadTokenErrorString = 'BadDeviceToken';
const apnMaxNotificationPayloadByteSize = 4096;
+const webInvalidTokenErrorCodes = [404, 410];
type APNPushResult =
| { +success: true }
@@ -198,9 +202,66 @@
return usersToUnreadCounts;
}
+export type WebPushError = {
+ +statusCode: number,
+ +headers: { +[string]: string },
+ +body: string,
+};
+type WebPushResult = {
+ +success?: true,
+ +errors?: $ReadOnlyArray<WebPushError>,
+ +invalidTokens?: $ReadOnlyArray<string>,
+};
+async function webPush({
+ notification,
+ deviceTokens,
+}: {
+ +notification: WebNotification,
+ +deviceTokens: $ReadOnlyArray<string>,
+}): Promise<WebPushResult> {
+ await ensureWebPushInitialized();
+ const notificationString = JSON.stringify(notification);
+
+ const pushResults = await Promise.all(
+ deviceTokens.map(async deviceTokenString => {
+ const deviceToken: PushSubscriptionJSON = JSON.parse(deviceTokenString);
+ try {
+ await webpush.sendNotification(deviceToken, notificationString);
+ } catch (error) {
+ return { error };
+ }
+ return {};
+ }),
+ );
+
+ const errors = [];
+ const invalidTokens = [];
+ for (let i = 0; i < pushResults.length; i++) {
+ const pushResult = pushResults[i];
+ if (pushResult.error) {
+ errors.push(pushResult.error);
+ if (webInvalidTokenErrorCodes.includes(pushResult.error.statusCode)) {
+ 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 {
apnPush,
fcmPush,
+ webPush,
getUnreadCounts,
apnMaxNotificationPayloadByteSize,
fcmMaxNotificationPayloadByteSize,
diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js
--- a/lib/types/notif-types.js
+++ b/lib/types/notif-types.js
@@ -15,3 +15,12 @@
+title: string,
+prefix?: string,
};
+
+export type WebNotification = {
+ +body: string,
+ +prefix?: string,
+ +title: string,
+ +unreadCount: number,
+ +id: string,
+ +threadID: string,
+};

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 22, 6:54 PM (18 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2690666
Default Alt Text
D6817.id23275.diff (7 KB)

Event Timeline