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 @@ -7,11 +7,20 @@ import invariant from 'invariant'; import webpush from 'web-push'; +import type { PlatformDetails } from 'lib/types/device-types'; + import { importJSON } from '../utils/import-json.js'; type APNPushProfile = 'apn_config' | 'comm_apn_config'; -function getAPNPushProfileForCodeVersion(codeVersion: ?number): APNPushProfile { - return codeVersion && codeVersion >= 87 ? 'comm_apn_config' : 'apn_config'; +function getAPNPushProfileForCodeVersion( + platformDetails: PlatformDetails, +): APNPushProfile { + if (platformDetails.platform === 'macos') { + return 'comm_apn_config'; + } + return platformDetails.codeVersion && platformDetails.codeVersion >= 87 + ? 'comm_apn_config' + : 'apn_config'; } type FCMPushProfile = 'fcm_config' | 'comm_fcm_config'; @@ -77,8 +86,13 @@ } } -function getAPNsNotificationTopic(codeVersion: ?number): string { - return codeVersion && codeVersion >= 87 ? 'app.comm' : 'org.squadcal.app'; +function getAPNsNotificationTopic(platformDetails: PlatformDetails): string { + if (platformDetails.platform === 'macos') { + return 'app.comm.macos'; + } + return platformDetails.codeVersion && platformDetails.codeVersion >= 87 + ? 'app.comm' + : 'org.squadcal.app'; } type WebPushConfig = { +publicKey: string, +privateKey: string }; 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 @@ -61,7 +61,7 @@ deliveryPromises[id] = apnPush({ notification, deviceTokens: delivery.iosDeviceTokens, - codeVersion: null, + platformDetails: { platform: 'ios' }, }); } else if (delivery.androidID) { // Old Android @@ -87,7 +87,7 @@ deliveryPromises[id] = apnPush({ notification, deviceTokens, - codeVersion, + platformDetails: { platform: 'ios', codeVersion }, }); } else if (delivery.deviceType === 'android') { // New Android @@ -162,7 +162,10 @@ ): apn.Notification { const notification = new apn.Notification(); notification.contentAvailable = true; - notification.topic = getAPNsNotificationTopic(codeVersion); + notification.topic = getAPNsNotificationTopic({ + platform: 'ios', + codeVersion: codeVersion ?? undefined, + }); notification.priority = 5; notification.pushType = 'background'; notification.payload = 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 @@ -22,7 +22,7 @@ rawThreadInfoFromServerThreadInfo, threadInfoFromRawThreadInfo, } from 'lib/shared/thread-utils.js'; -import type { Platform } from 'lib/types/device-types.js'; +import type { Platform, PlatformDetails } from 'lib/types/device-types.js'; import { type RawMessageInfo, type MessageInfo, @@ -167,19 +167,24 @@ { platform: 'ios', codeVersion }, ); const deliveryPromise = (async () => { - const notification = await prepareIOSNotification( + const notification = await prepareAPNsNotification( allMessageInfos, shimmedNewRawMessageInfos, threadInfo, notifInfo.collapseKey, badgeOnly, unreadCounts[userID], - codeVersion, + { platform: 'ios', codeVersion }, + ); + return await sendAPNsNotification( + 'ios', + notification, + [...deviceTokens], + { + ...notificationInfo, + codeVersion, + }, ); - return await sendIOSNotification(notification, [...deviceTokens], { - ...notificationInfo, - codeVersion, - }); })(); deliveryPromises.push(deliveryPromise); } @@ -491,19 +496,18 @@ return byPlatform; } -async function prepareIOSNotification( +async function prepareAPNsNotification( allMessageInfos: MessageInfo[], newRawMessageInfos: RawMessageInfo[], threadInfo: ThreadInfo, collapseKey: ?string, badgeOnly: boolean, unreadCount: number, - codeVersion: number, + platformDetails: PlatformDetails, ): Promise { const uniqueID = uuidv4(); const notification = new apn.Notification(); - notification.topic = getAPNsNotificationTopic(codeVersion); - + notification.topic = getAPNsNotificationTopic(platformDetails); const { merged, ...rest } = await notifTextsForMessageInfo( allMessageInfos, threadInfo, @@ -524,7 +528,7 @@ notification.pushType = 'alert'; notification.payload.id = uniqueID; notification.payload.threadID = threadInfo.id; - if (codeVersion > 1000) { + if (platformDetails.codeVersion && platformDetails.codeVersion > 1000) { notification.mutableContent = true; } if (collapseKey) { @@ -547,7 +551,8 @@ const notificationCopy = _cloneDeep(notification); if (notificationCopy.length() > apnMaxNotificationPayloadByteSize) { console.warn( - `iOS notification ${uniqueID} exceeds size limit, even with messageInfos omitted`, + `${platformDetails.platform} notification ${uniqueID} ` + + `exceeds size limit, even with messageInfos omitted`, ); } return notification; @@ -652,29 +657,34 @@ +codeVersion: number, }; -type IOSDelivery = { +type APNsDelivery = { source: $PropertyType, - deviceType: 'ios', + deviceType: 'ios' | 'macos', iosID: string, deviceTokens: $ReadOnlyArray, codeVersion: number, errors?: $ReadOnlyArray, }; -type IOSResult = { +type APNsResult = { info: NotificationInfo, - delivery: IOSDelivery, + delivery: APNsDelivery, invalidTokens?: $ReadOnlyArray, }; -async function sendIOSNotification( +async function sendAPNsNotification( + platform: 'ios' | 'macos', notification: apn.Notification, deviceTokens: $ReadOnlyArray, notificationInfo: NotificationInfo, -): Promise { +): Promise { const { source, codeVersion } = notificationInfo; - const response = await apnPush({ notification, deviceTokens, codeVersion }); - const delivery: IOSDelivery = { + const response = await apnPush({ + notification, + deviceTokens, + platformDetails: { platform, codeVersion }, + }); + const delivery: APNsDelivery = { source, - deviceType: 'ios', + deviceType: platform, iosID: notification.id, deviceTokens, codeVersion, @@ -682,7 +692,7 @@ if (response.errors) { delivery.errors = response.errors; } - const result: IOSResult = { + const result: APNsResult = { info: notificationInfo, delivery, }; @@ -692,8 +702,8 @@ return result; } -type PushResult = AndroidResult | IOSResult | WebResult; -type PushDelivery = AndroidDelivery | IOSDelivery | WebDelivery; +type PushResult = AndroidResult | APNsResult | WebResult; +type PushDelivery = AndroidDelivery | APNsDelivery | WebDelivery; type AndroidDelivery = { source: $PropertyType, deviceType: 'android', @@ -882,11 +892,14 @@ for (const [codeVer, deviceTokens] of iosVersionsToTokens) { const codeVersion = parseInt(codeVer, 10); // only for Flow const notification = new apn.Notification(); - notification.topic = getAPNsNotificationTopic(codeVersion); + notification.topic = getAPNsNotificationTopic({ + platform: 'ios', + codeVersion, + }); notification.badge = unreadCount; notification.pushType = 'alert'; deliveryPromises.push( - sendIOSNotification(notification, [...deviceTokens], { + sendAPNsNotification('ios', notification, [...deviceTokens], { source, dbID, userID, 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 @@ -6,6 +6,7 @@ import invariant from 'invariant'; import webpush from 'web-push'; +import type { PlatformDetails } from 'lib/types/device-types.js'; import { threadSubscriptions } from 'lib/types/subscription-types.js'; import { threadPermissions } from 'lib/types/thread-types.js'; @@ -38,13 +39,13 @@ async function apnPush({ notification, deviceTokens, - codeVersion, + platformDetails, }: { +notification: apn.Notification, +deviceTokens: $ReadOnlyArray, - +codeVersion: ?number, + +platformDetails: PlatformDetails, }): Promise { - const pushProfile = getAPNPushProfileForCodeVersion(codeVersion); + const pushProfile = getAPNPushProfileForCodeVersion(platformDetails); const apnProvider = await getAPNProvider(pushProfile); if (!apnProvider && process.env.NODE_ENV === 'development') { console.log(`no keyserver/secrets/${pushProfile}.json so ignoring notifs`);