diff --git a/keyserver/src/push/crypto.js b/keyserver/src/push/crypto.js --- a/keyserver/src/push/crypto.js +++ b/keyserver/src/push/crypto.js @@ -46,14 +46,16 @@ encryptedNotification.id = notification.id; encryptedNotification.payload.id = notification.id; + encryptedNotification.payload.keyserverID = notification.payload.keyserverID; encryptedNotification.topic = notification.topic; encryptedNotification.sound = notification.aps.sound; encryptedNotification.pushType = 'alert'; encryptedNotification.mutableContent = true; - const { id, ...payloadSansId } = notification.payload; + const { id, keyserverID, ...payloadSansUnencryptedData } = + notification.payload; const unencryptedPayload = { - ...payloadSansId, + ...payloadSansUnencryptedData, badge: notification.aps.badge.toString(), merged: notification.body, }; @@ -197,8 +199,9 @@ +payloadSizeExceeded: boolean, +encryptionOrder?: number, }> { - const { id, badgeOnly, ...unencryptedPayload } = notification.data; - let unencryptedData = { badgeOnly }; + const { id, keyserverID, badgeOnly, ...unencryptedPayload } = + notification.data; + let unencryptedData = { badgeOnly, keyserverID }; if (id) { unencryptedData = { ...unencryptedData, id }; } @@ -238,12 +241,13 @@ // We don't validate payload size for rescind // since they are expected to be small and // never exceed any FCM limit + const { keyserverID, ...unencryptedPayload } = notification.data; const { resultPayload } = await encryptAndroidNotificationPayload( cookieID, - notification.data, + unencryptedPayload, ); return { - data: resultPayload, + data: { keyserverID, ...resultPayload }, }; } 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 @@ -30,6 +30,7 @@ import createIDs from '../creators/id-creator.js'; import { dbQuery, SQL } from '../database/database.js'; import type { SQLStatementType } from '../database/types.js'; +import { thisKeyserverID } from '../user/identity.js'; import { validateOutput } from '../utils/validation-utils.js'; type ParsedDelivery = { @@ -61,7 +62,7 @@ fetchQuery.append(SQL` ) AS unread_count FROM notifications n - LEFT JOIN memberships m ON m.user = n.user + LEFT JOIN memberships m ON m.user = n.user AND m.last_message > m.last_read_message AND m.role > 0 AND JSON_EXTRACT(subscription, ${notificationExtractString}) @@ -70,7 +71,10 @@ `); fetchQuery.append(notifCondition); fetchQuery.append(SQL` GROUP BY n.id, m.user`); - const [fetchResult] = await dbQuery(fetchQuery); + const [[fetchResult], keyserverID] = await Promise.all([ + dbQuery(fetchQuery), + thisKeyserverID(), + ]); const allDeviceTokens = new Set(); const parsedDeliveries: { [string]: $ReadOnlyArray } = {}; @@ -152,6 +156,7 @@ })); const deliveryPromise = (async () => { const targetedNotifications = await prepareIOSNotification( + keyserverID, delivery.notificationID, row.unread_count, threadID, @@ -174,6 +179,7 @@ })); const deliveryPromise = (async () => { const targetedNotifications = await prepareAndroidNotification( + keyserverID, delivery.notificationID, row.unread_count, threadID, @@ -303,6 +309,7 @@ } async function prepareIOSNotification( + keyserverID: string, iosID: string, unreadCount: number, threadID: string, @@ -334,6 +341,7 @@ notificationId: iosID, setUnreadStatus: true, threadID, + keyserverID, } : { managedAps: { @@ -350,6 +358,7 @@ } async function prepareAndroidNotification( + keyserverID: string, notifID: string, unreadCount: number, threadID: string, @@ -366,6 +375,7 @@ rescindID: notifID, setUnreadStatus: 'true', threadID, + keyserverID, }, }; return await conditionallyEncryptNotification( 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 @@ -78,6 +78,7 @@ import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; import { fetchUserInfos } from '../fetchers/user-fetchers.js'; import type { Viewer } from '../session/viewer.js'; +import { thisKeyserverID } from '../user/identity.js'; import { getENSNames } from '../utils/ens-cache.js'; import { validateOutput } from '../utils/validation-utils.js'; @@ -112,6 +113,8 @@ return; } + const keyserverID = await thisKeyserverID(); + const [ unreadCounts, { usersToCollapsableNotifInfo, serverThreadInfos, userInfos }, @@ -148,6 +151,7 @@ for (const notifInfo of usersToCollapsableNotifInfo[userID]) { preparePromises.push( preparePushNotif({ + keyserverID, notifInfo, userID, pushUserInfo: pushInfo[userID], @@ -192,6 +196,7 @@ }; async function preparePushNotif(input: { + keyserverID: string, notifInfo: CollapsableNotifInfo, userID: string, pushUserInfo: PushUserInfo, @@ -204,6 +209,7 @@ rowsToSave: Map, // mutable }): Promise> { const { + keyserverID, notifInfo, userID, pushUserInfo, @@ -319,6 +325,7 @@ (async () => { const targetedNotifications = await prepareAPNsNotification( { + keyserverID, notifTexts, newRawMessageInfos: shimmedNewRawMessageInfos, threadID: threadInfo.id, @@ -359,6 +366,7 @@ (async () => { const targetedNotifications = await prepareAndroidNotification( { + keyserverID, notifTexts, newRawMessageInfos: shimmedNewRawMessageInfos, threadID: threadInfo.id, @@ -437,6 +445,7 @@ (async () => { const targetedNotifications = await prepareAPNsNotification( { + keyserverID, notifTexts, newRawMessageInfos: shimmedNewRawMessageInfos, threadID: threadInfo.id, @@ -886,6 +895,7 @@ } type APNsNotifInputData = { + +keyserverID: string, +notifTexts: ResolvedNotifTexts, +newRawMessageInfos: RawMessageInfo[], +threadID: string, @@ -895,6 +905,7 @@ +platformDetails: PlatformDetails, }; const apnsNotifInputDataValidator = tShape({ + keyserverID: t.String, notifTexts: resolvedNotifTextsValidator, newRawMessageInfos: t.list(rawMessageInfoValidator), threadID: tID, @@ -913,6 +924,7 @@ inputData, ); const { + keyserverID, notifTexts, newRawMessageInfos, threadID, @@ -960,10 +972,14 @@ notification.body = merged; notification.sound = 'default'; } + notification.payload = { ...notification.payload, ...rest, }; + if (platformDetails.platform !== 'macos') { + notification.payload.keyserverID = keyserverID; + } notification.badge = unreadCount; notification.threadId = threadID; @@ -1093,6 +1109,7 @@ inputData, ); const { + keyserverID, notifTexts, newRawMessageInfos, threadID, @@ -1118,6 +1135,7 @@ const { merged, ...rest } = notifTexts; const notification = { data: { + keyserverID, badge: unreadCount.toString(), ...rest, threadID, @@ -1628,11 +1646,13 @@ if (viewer.data.cookieID) { deviceTokenQuery.append(SQL`AND id != ${viewer.cookieID} `); } - const [unreadCounts, [deviceTokenResult], [dbID]] = await Promise.all([ - getUnreadCounts([userID]), - dbQuery(deviceTokenQuery), - createIDs('notifications', 1), - ]); + const [unreadCounts, [deviceTokenResult], [dbID], keyserverID] = + await Promise.all([ + getUnreadCounts([userID]), + dbQuery(deviceTokenQuery), + createIDs('notifications', 1), + thisKeyserverID(), + ]); const unreadCount = unreadCounts[userID]; const devices = deviceTokenResult.map(row => { const versions = JSON.parse(row.versions); @@ -1660,6 +1680,7 @@ }); notification.badge = unreadCount; notification.pushType = 'alert'; + notification.payload.keyserverID = keyserverID; const preparePromise: Promise = (async () => { let targetedNotifications: $ReadOnlyArray; if (codeVersion > 222) { @@ -1706,7 +1727,9 @@ codeVersion < 69 ? { badge: unreadCount.toString() } : { badge: unreadCount.toString(), badgeOnly: '1' }; - const notification = { data: notificationData }; + const notification = { + data: { ...notificationData, keyserverID }, + }; const preparePromise: Promise = (async () => { let targetedNotifications: $ReadOnlyArray; if (codeVersion > 222) { diff --git a/keyserver/src/push/types.js b/keyserver/src/push/types.js --- a/keyserver/src/push/types.js +++ b/keyserver/src/push/types.js @@ -39,21 +39,25 @@ +data: { +id?: string, +badgeOnly?: string, + +keyserverID: string, ...AndroidNotificationPayload | { +encryptedPayload: string }, }, }; export type AndroidNotificationRescind = { - +data: - | { - +badge: string, - +rescind: 'true', - +rescindID: string, - +setUnreadStatus: 'true', - +threadID: string, - +encryptionFailed?: string, - } - | { +encryptedPayload: string }, + +data: { + +keyserverID: string, + ... + | { + +badge: string, + +rescind: 'true', + +rescindID: string, + +setUnreadStatus: 'true', + +threadID: string, + +encryptionFailed?: string, + } + | { +encryptedPayload: string }, + }, }; export type TargetedAndroidNotification = {