Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/push/send.js
Show All 16 Lines | import { | ||||
shimUnsupportedRawMessageInfos, | shimUnsupportedRawMessageInfos, | ||||
} from 'lib/shared/message-utils.js'; | } from 'lib/shared/message-utils.js'; | ||||
import { messageSpecs } from 'lib/shared/messages/message-specs.js'; | import { messageSpecs } from 'lib/shared/messages/message-specs.js'; | ||||
import { notifTextsForMessageInfo } from 'lib/shared/notif-utils.js'; | import { notifTextsForMessageInfo } from 'lib/shared/notif-utils.js'; | ||||
import { | import { | ||||
rawThreadInfoFromServerThreadInfo, | rawThreadInfoFromServerThreadInfo, | ||||
threadInfoFromRawThreadInfo, | threadInfoFromRawThreadInfo, | ||||
} from 'lib/shared/thread-utils.js'; | } from 'lib/shared/thread-utils.js'; | ||||
import type { DeviceType } from 'lib/types/device-types.js'; | import type { Platform } from 'lib/types/device-types.js'; | ||||
import { | import { | ||||
type RawMessageInfo, | type RawMessageInfo, | ||||
type MessageInfo, | type MessageInfo, | ||||
messageTypes, | messageTypes, | ||||
} from 'lib/types/message-types.js'; | } from 'lib/types/message-types.js'; | ||||
import type { ServerThreadInfo, ThreadInfo } from 'lib/types/thread-types.js'; | import type { ServerThreadInfo, ThreadInfo } from 'lib/types/thread-types.js'; | ||||
import { updateTypes } from 'lib/types/update-types.js'; | import { updateTypes } from 'lib/types/update-types.js'; | ||||
import { promiseAll } from 'lib/utils/promises.js'; | import { promiseAll } from 'lib/utils/promises.js'; | ||||
Show All 12 Lines | |||||
import type { CollapsableNotifInfo } from '../fetchers/message-fetchers.js'; | import type { CollapsableNotifInfo } from '../fetchers/message-fetchers.js'; | ||||
import { fetchCollapsableNotifs } from '../fetchers/message-fetchers.js'; | import { fetchCollapsableNotifs } from '../fetchers/message-fetchers.js'; | ||||
import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; | import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; | ||||
import { fetchUserInfos } from '../fetchers/user-fetchers.js'; | import { fetchUserInfos } from '../fetchers/user-fetchers.js'; | ||||
import type { Viewer } from '../session/viewer.js'; | import type { Viewer } from '../session/viewer.js'; | ||||
import { getENSNames } from '../utils/ens-cache.js'; | import { getENSNames } from '../utils/ens-cache.js'; | ||||
type Device = { | type Device = { | ||||
+deviceType: DeviceType, | +platform: Platform, | ||||
+deviceToken: string, | +deviceToken: string, | ||||
+codeVersion: ?number, | +codeVersion: ?number, | ||||
}; | }; | ||||
type PushUserInfo = { | type PushUserInfo = { | ||||
+devices: Device[], | +devices: Device[], | ||||
+messageInfos: RawMessageInfo[], | +messageInfos: RawMessageInfo[], | ||||
}; | }; | ||||
type Delivery = IOSDelivery | AndroidDelivery | { collapsedInto: string }; | type Delivery = IOSDelivery | AndroidDelivery | { collapsedInto: string }; | ||||
▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | for (const notifInfo of usersToCollapsableNotifInfo[userID]) { | ||||
isMentioned(username, firstNewMessageInfo.text); | isMentioned(username, firstNewMessageInfo.text); | ||||
if (!updateBadge && !displayBanner && !userWasMentioned) { | if (!updateBadge && !displayBanner && !userWasMentioned) { | ||||
continue; | continue; | ||||
} | } | ||||
const badgeOnly = !displayBanner && !userWasMentioned; | const badgeOnly = !displayBanner && !userWasMentioned; | ||||
const dbID = dbIDs.shift(); | const dbID = dbIDs.shift(); | ||||
invariant(dbID, 'should have sufficient DB IDs'); | invariant(dbID, 'should have sufficient DB IDs'); | ||||
const byDeviceType = getDevicesByDeviceType(pushInfo[userID].devices); | const byPlatform = getDevicesByPlatform(pushInfo[userID].devices); | ||||
const firstMessageID = firstNewMessageInfo.id; | const firstMessageID = firstNewMessageInfo.id; | ||||
invariant(firstMessageID, 'RawMessageInfo.id should be set on server'); | invariant(firstMessageID, 'RawMessageInfo.id should be set on server'); | ||||
const notificationInfo = { | const notificationInfo = { | ||||
source: 'new_message', | source: 'new_message', | ||||
dbID, | dbID, | ||||
userID, | userID, | ||||
threadID, | threadID, | ||||
messageID: firstMessageID, | messageID: firstMessageID, | ||||
collapseKey: notifInfo.collapseKey, | collapseKey: notifInfo.collapseKey, | ||||
}; | }; | ||||
const iosVersionsToTokens = byDeviceType.get('ios'); | const iosVersionsToTokens = byPlatform.get('ios'); | ||||
if (iosVersionsToTokens) { | if (iosVersionsToTokens) { | ||||
for (const [codeVer, deviceTokens] of iosVersionsToTokens) { | for (const [codeVer, deviceTokens] of iosVersionsToTokens) { | ||||
const codeVersion = parseInt(codeVer, 10); // only for Flow | const codeVersion = parseInt(codeVer, 10); // only for Flow | ||||
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
{ platform: 'ios', codeVersion }, | { platform: 'ios', codeVersion }, | ||||
); | ); | ||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
Show All 9 Lines | for (const notifInfo of usersToCollapsableNotifInfo[userID]) { | ||||
return await sendIOSNotification(notification, [...deviceTokens], { | return await sendIOSNotification(notification, [...deviceTokens], { | ||||
...notificationInfo, | ...notificationInfo, | ||||
codeVersion, | codeVersion, | ||||
}); | }); | ||||
})(); | })(); | ||||
deliveryPromises.push(deliveryPromise); | deliveryPromises.push(deliveryPromise); | ||||
} | } | ||||
} | } | ||||
const androidVersionsToTokens = byDeviceType.get('android'); | const androidVersionsToTokens = byPlatform.get('android'); | ||||
if (androidVersionsToTokens) { | if (androidVersionsToTokens) { | ||||
for (const [codeVer, deviceTokens] of androidVersionsToTokens) { | for (const [codeVer, deviceTokens] of androidVersionsToTokens) { | ||||
const codeVersion = parseInt(codeVer, 10); // only for Flow | const codeVersion = parseInt(codeVer, 10); // only for Flow | ||||
const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | const shimmedNewRawMessageInfos = shimUnsupportedRawMessageInfos( | ||||
newRawMessageInfos, | newRawMessageInfos, | ||||
{ platform: 'android', codeVersion }, | { platform: 'android', codeVersion }, | ||||
); | ); | ||||
const deliveryPromise = (async () => { | const deliveryPromise = (async () => { | ||||
▲ Show 20 Lines • Show All 249 Lines • ▼ Show 20 Lines | |||||
async function createDBIDs(pushInfo: PushInfo): Promise<string[]> { | async function createDBIDs(pushInfo: PushInfo): Promise<string[]> { | ||||
let numIDsNeeded = 0; | let numIDsNeeded = 0; | ||||
for (const userID in pushInfo) { | for (const userID in pushInfo) { | ||||
numIDsNeeded += pushInfo[userID].messageInfos.length; | numIDsNeeded += pushInfo[userID].messageInfos.length; | ||||
} | } | ||||
return await createIDs('notifications', numIDsNeeded); | return await createIDs('notifications', numIDsNeeded); | ||||
} | } | ||||
function getDevicesByDeviceType( | function getDevicesByPlatform( | ||||
devices: Device[], | devices: Device[], | ||||
): Map<DeviceType, Map<number, Set<string>>> { | ): Map<Platform, Map<number, Set<string>>> { | ||||
const byDeviceType = new Map(); | const byPlatform = new Map(); | ||||
for (const device of devices) { | for (const device of devices) { | ||||
let innerMap = byDeviceType.get(device.deviceType); | let innerMap = byPlatform.get(device.platform); | ||||
if (!innerMap) { | if (!innerMap) { | ||||
innerMap = new Map(); | innerMap = new Map(); | ||||
byDeviceType.set(device.deviceType, innerMap); | byPlatform.set(device.platform, innerMap); | ||||
} | } | ||||
const codeVersion: number = | const codeVersion: number = | ||||
device.codeVersion !== null && device.codeVersion !== undefined | device.codeVersion !== null && device.codeVersion !== undefined | ||||
? device.codeVersion | ? device.codeVersion | ||||
: -1; | : -1; | ||||
let innerMostSet = innerMap.get(codeVersion); | let innerMostSet = innerMap.get(codeVersion); | ||||
if (!innerMostSet) { | if (!innerMostSet) { | ||||
innerMostSet = new Set(); | innerMostSet = new Set(); | ||||
innerMap.set(codeVersion, innerMostSet); | innerMap.set(codeVersion, innerMostSet); | ||||
} | } | ||||
innerMostSet.add(device.deviceToken); | innerMostSet.add(device.deviceToken); | ||||
} | } | ||||
return byDeviceType; | return byPlatform; | ||||
} | } | ||||
async function prepareIOSNotification( | async function prepareIOSNotification( | ||||
allMessageInfos: MessageInfo[], | allMessageInfos: MessageInfo[], | ||||
newRawMessageInfos: RawMessageInfo[], | newRawMessageInfos: RawMessageInfo[], | ||||
threadInfo: ThreadInfo, | threadInfo: ThreadInfo, | ||||
collapseKey: ?string, | collapseKey: ?string, | ||||
badgeOnly: boolean, | badgeOnly: boolean, | ||||
▲ Show 20 Lines • Show All 303 Lines • ▼ Show 20 Lines | ) { | ||||
const [unreadCounts, [deviceTokenResult], [dbID]] = await Promise.all([ | const [unreadCounts, [deviceTokenResult], [dbID]] = await Promise.all([ | ||||
getUnreadCounts([userID]), | getUnreadCounts([userID]), | ||||
dbQuery(deviceTokenQuery), | dbQuery(deviceTokenQuery), | ||||
createIDs('notifications', 1), | createIDs('notifications', 1), | ||||
]); | ]); | ||||
const unreadCount = unreadCounts[userID]; | const unreadCount = unreadCounts[userID]; | ||||
const devices = deviceTokenResult.map(row => ({ | const devices = deviceTokenResult.map(row => ({ | ||||
deviceType: row.platform, | platform: row.platform, | ||||
deviceToken: row.device_token, | deviceToken: row.device_token, | ||||
codeVersion: JSON.parse(row.versions)?.codeVersion, | codeVersion: JSON.parse(row.versions)?.codeVersion, | ||||
})); | })); | ||||
const byDeviceType = getDevicesByDeviceType(devices); | const byPlatform = getDevicesByPlatform(devices); | ||||
const deliveryPromises = []; | const deliveryPromises = []; | ||||
const iosVersionsToTokens = byDeviceType.get('ios'); | const iosVersionsToTokens = byPlatform.get('ios'); | ||||
if (iosVersionsToTokens) { | if (iosVersionsToTokens) { | ||||
for (const [codeVer, deviceTokens] of iosVersionsToTokens) { | for (const [codeVer, deviceTokens] of iosVersionsToTokens) { | ||||
const codeVersion = parseInt(codeVer, 10); // only for Flow | const codeVersion = parseInt(codeVer, 10); // only for Flow | ||||
const notification = new apn.Notification(); | const notification = new apn.Notification(); | ||||
notification.topic = getAPNsNotificationTopic(codeVersion); | notification.topic = getAPNsNotificationTopic(codeVersion); | ||||
notification.badge = unreadCount; | notification.badge = unreadCount; | ||||
notification.pushType = 'alert'; | notification.pushType = 'alert'; | ||||
deliveryPromises.push( | deliveryPromises.push( | ||||
sendIOSNotification(notification, [...deviceTokens], { | sendIOSNotification(notification, [...deviceTokens], { | ||||
source, | source, | ||||
dbID, | dbID, | ||||
userID, | userID, | ||||
codeVersion, | codeVersion, | ||||
}), | }), | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
const androidVersionsToTokens = byDeviceType.get('android'); | const androidVersionsToTokens = byPlatform.get('android'); | ||||
if (androidVersionsToTokens) { | if (androidVersionsToTokens) { | ||||
for (const [codeVer, deviceTokens] of androidVersionsToTokens) { | for (const [codeVer, deviceTokens] of androidVersionsToTokens) { | ||||
const codeVersion = parseInt(codeVer, 10); // only for Flow | const codeVersion = parseInt(codeVer, 10); // only for Flow | ||||
const notificationData = | const notificationData = | ||||
codeVersion < 69 | codeVersion < 69 | ||||
? { badge: unreadCount.toString() } | ? { badge: unreadCount.toString() } | ||||
: { badge: unreadCount.toString(), badgeOnly: '1' }; | : { badge: unreadCount.toString(), badgeOnly: '1' }; | ||||
const notification = { data: notificationData }; | const notification = { data: notificationData }; | ||||
Show All 16 Lines |