Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3518289
D7284.id24947.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
D7284.id24947.diff
View Options
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
@@ -30,6 +30,7 @@
} from 'lib/types/message-types.js';
import type {
WebNotification,
+ WNSNotification,
ResolvedNotifTexts,
} from 'lib/types/notif-types.js';
import type { ServerThreadInfo } from 'lib/types/thread-types.js';
@@ -44,8 +45,11 @@
getUnreadCounts,
apnMaxNotificationPayloadByteSize,
fcmMaxNotificationPayloadByteSize,
+ wnsMaxNotificationPayloadByteSize,
webPush,
+ wnsPush,
type WebPushError,
+ type WNSPushError,
} from './utils.js';
import createIDs from '../creators/id-creator.js';
import { createUpdates } from '../creators/update-creator.js';
@@ -294,6 +298,23 @@
deliveryPromises.push(deliveryPromise);
}
}
+ const windowsVersionsToTokens = byPlatform.get('windows');
+ if (windowsVersionsToTokens) {
+ for (const [codeVersion, deviceTokens] of windowsVersionsToTokens) {
+ const deliveryPromise = (async () => {
+ const notification = await prepareWNSNotification({
+ notifTexts,
+ threadID: threadInfo.id,
+ unreadCount: unreadCounts[userID],
+ });
+ return await sendWNSNotification(notification, [...deviceTokens], {
+ ...notificationInfo,
+ codeVersion,
+ });
+ })();
+ deliveryPromises.push(deliveryPromise);
+ }
+ }
for (const newMessageInfo of remainingNewMessageInfos) {
const newDBID = dbIDs.shift();
@@ -741,6 +762,31 @@
return notification;
}
+type WNSNotifInputData = {
+ +notifTexts: ResolvedNotifTexts,
+ +threadID: string,
+ +unreadCount: number,
+};
+async function prepareWNSNotification(
+ inputData: WNSNotifInputData,
+): Promise<WNSNotification> {
+ const { notifTexts, threadID, unreadCount } = inputData;
+ const { merged, ...rest } = notifTexts;
+ const notification = {
+ ...rest,
+ unreadCount,
+ threadID,
+ };
+
+ if (
+ Buffer.byteLength(JSON.stringify(notification)) >
+ wnsMaxNotificationPayloadByteSize
+ ) {
+ console.warn('WNS notification exceeds size limit');
+ }
+ return notification;
+}
+
type NotificationInfo =
| {
+source: 'new_message',
@@ -803,8 +849,8 @@
return result;
}
-type PushResult = AndroidResult | APNsResult | WebResult;
-type PushDelivery = AndroidDelivery | APNsDelivery | WebDelivery;
+type PushResult = AndroidResult | APNsResult | WebResult | WNSResult;
+type PushDelivery = AndroidDelivery | APNsDelivery | WebDelivery | WNSDelivery;
type AndroidDelivery = {
source: $PropertyType<NotificationInfo, 'source'>,
deviceType: 'android',
@@ -893,6 +939,48 @@
return result;
}
+type WNSDelivery = {
+ +source: $PropertyType<NotificationInfo, 'source'>,
+ +deviceType: 'windows',
+ +wnsIDs: $ReadOnlyArray<string>,
+ +deviceTokens: $ReadOnlyArray<string>,
+ +codeVersion?: number,
+ +errors?: $ReadOnlyArray<WNSPushError>,
+};
+type WNSResult = {
+ +info: NotificationInfo,
+ +delivery: WNSDelivery,
+ +invalidTokens?: $ReadOnlyArray<string>,
+};
+async function sendWNSNotification(
+ notification: WNSNotification,
+ deviceTokens: $ReadOnlyArray<string>,
+ notificationInfo: NotificationInfo,
+): Promise<WNSResult> {
+ const { source, codeVersion } = notificationInfo;
+
+ const response = await wnsPush({
+ notification,
+ deviceTokens,
+ });
+
+ const wnsIDs = response.wnsIDs ?? [];
+ const delivery: WNSDelivery = {
+ source,
+ deviceType: 'windows',
+ wnsIDs,
+ deviceTokens,
+ codeVersion,
+ errors: response.errors,
+ };
+ const result: WNSResult = {
+ 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,10 +4,15 @@
import type { ResponseFailure } from '@parse/node-apn';
import type { FirebaseApp, FirebaseError } from 'firebase-admin';
import invariant from 'invariant';
+import fetch from 'node-fetch';
+import type { Response } from 'node-fetch';
import webpush from 'web-push';
import type { PlatformDetails } from 'lib/types/device-types.js';
-import type { WebNotification } from 'lib/types/notif-types.js';
+import type {
+ WebNotification,
+ WNSNotification,
+} from 'lib/types/notif-types.js';
import { threadSubscriptions } from 'lib/types/subscription-types.js';
import { threadPermissions } from 'lib/types/thread-types.js';
@@ -17,6 +22,7 @@
getAPNProvider,
getFCMProvider,
ensureWebPushInitialized,
+ getWNSToken,
} from './providers.js';
import { dbQuery, SQL } from '../database/database.js';
@@ -30,6 +36,8 @@
const apnBadTokenErrorString = 'BadDeviceToken';
const apnMaxNotificationPayloadByteSize = 4096;
const webInvalidTokenErrorCodes = [404, 410];
+const wnsInvalidTokenErrorCodes = [404, 410];
+const wnsMaxNotificationPayloadByteSize = 5000;
type APNPushResult =
| { +success: true }
@@ -259,11 +267,109 @@
return { ...result };
}
+export type WNSPushError = any | string | Response;
+type WNSPushResult = {
+ +success?: true,
+ +wnsIDs?: $ReadOnlyArray<string>,
+ +errors?: $ReadOnlyArray<WNSPushError>,
+ +invalidTokens?: $ReadOnlyArray<string>,
+};
+async function wnsPush({
+ notification,
+ deviceTokens,
+}: {
+ +notification: WNSNotification,
+ +deviceTokens: $ReadOnlyArray<string>,
+}): Promise<WNSPushResult> {
+ const token = await getWNSToken();
+ if (!token && process.env.NODE_ENV === 'development') {
+ console.log(`no keyserver/secrets/wns_config.json so ignoring notifs`);
+ return { success: true };
+ }
+ invariant(token, `keyserver/secrets/wns_config.json should exist`);
+
+ const notificationString = JSON.stringify(notification);
+
+ const pushResults = deviceTokens.map(async devicePushURL => {
+ try {
+ return await wnsSinglePush(token, notificationString, devicePushURL);
+ } catch (error) {
+ return { error };
+ }
+ });
+
+ const errors = [];
+ const notifIDs = [];
+ const invalidTokens = [];
+ for (let i = 0; i < pushResults.length; i++) {
+ const pushResult = await pushResults[i];
+ if (pushResult.error) {
+ errors.push(pushResult.error);
+ if (
+ pushResult.error === 'invalidDomain' ||
+ wnsInvalidTokenErrorCodes.includes(pushResult.error?.status)
+ ) {
+ invalidTokens.push(deviceTokens[i]);
+ }
+ } else {
+ notifIDs.push(pushResult.wnsID);
+ }
+ }
+
+ const result = {};
+ if (notifIDs.length > 0) {
+ result.wnsIDs = notifIDs;
+ }
+ if (errors.length > 0) {
+ result.errors = errors;
+ } else {
+ result.success = true;
+ }
+ if (invalidTokens.length > 0) {
+ result.invalidTokens = invalidTokens;
+ }
+ return { ...result };
+}
+
+async function wnsSinglePush(token: string, notification: string, url: string) {
+ const parsedURL = new URL(url);
+ const domain = parsedURL.hostname.split('.').slice(-3);
+ if (
+ domain[0] !== 'notify' ||
+ domain[1] !== 'windows' ||
+ domain[2] !== 'com'
+ ) {
+ return { error: 'invalidDomain' };
+ }
+
+ try {
+ const result = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/octet-stream',
+ 'X-WNS-Type': 'wns/raw',
+ 'Authorization': `Bearer ${token}`,
+ },
+ body: notification,
+ });
+
+ if (!result.ok) {
+ return { error: result };
+ }
+
+ return { wnsID: result.headers.get('X-WNS-MSG-ID') };
+ } catch (err) {
+ return { error: err };
+ }
+}
+
export {
apnPush,
fcmPush,
webPush,
+ wnsPush,
getUnreadCounts,
apnMaxNotificationPayloadByteSize,
fcmMaxNotificationPayloadByteSize,
+ wnsMaxNotificationPayloadByteSize,
};
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
@@ -24,3 +24,11 @@
+id: string,
+threadID: string,
};
+
+export type WNSNotification = {
+ +body: string,
+ +prefix?: string,
+ +title: string,
+ +unreadCount: number,
+ +threadID: string,
+};
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Dec 23, 7:29 PM (18 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2694880
Default Alt Text
D7284.id24947.diff (8 KB)
Attached To
Mode
D7284: [keyserver] Send WNS notifications
Attached
Detach File
Event Timeline
Log In to Comment