Page MenuHomePhabricator

D12463.id41559.diff
No OneTemporary

D12463.id41559.diff

diff --git a/keyserver/src/creators/message-creator.js b/keyserver/src/creators/message-creator.js
--- a/keyserver/src/creators/message-creator.js
+++ b/keyserver/src/creators/message-creator.js
@@ -4,13 +4,17 @@
import _pickBy from 'lodash/fp/pickBy.js';
import { permissionLookup } from 'lib/permissions/thread-permissions.js';
+import {
+ generateNotifUserInfoPromise,
+ type Device,
+ type PushUserInfo,
+} from 'lib/push/send-utils.js';
import {
rawMessageInfoFromMessageData,
shimUnsupportedRawMessageInfos,
stripLocalIDs,
} from 'lib/shared/message-utils.js';
import { pushTypes } from 'lib/shared/messages/message-spec.js';
-import type { PushType } from 'lib/shared/messages/message-spec.js';
import { messageSpecs } from 'lib/shared/messages/message-specs.js';
import { messageTypes } from 'lib/types/message-types-enum.js';
import {
@@ -39,7 +43,6 @@
} from '../fetchers/message-fetchers.js';
import { fetchOtherSessionsForViewer } from '../fetchers/session-fetchers.js';
import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js';
-import type { Device, PushUserInfo } from '../push/send.js';
import { sendPushNotifs, sendRescindNotifs } from '../push/send.js';
import type { Viewer } from '../session/viewer.js';
import { earliestFocusedTimeConsideredExpired } from '../shared/focused-times.js';
@@ -378,13 +381,21 @@
}
}
if (deviceToken && cookieID) {
- thisUserInfo.devices.set(deviceToken, {
+ let platformDetails = {
platform,
- deviceToken,
- cookieID: cookieID.toString(),
codeVersion: versions ? versions.codeVersion : null,
stateVersion: versions ? versions.stateVersion : null,
- majorDesktopVersion: versions ? versions.majorDesktopVersion : null,
+ };
+ if (versions.majorDesktopVersion) {
+ platformDetails = {
+ ...platformDetails,
+ majorDesktopVersion: versions.majorDesktopVersion,
+ };
+ }
+ thisUserInfo.devices.set(deviceToken, {
+ deliveryID: deviceToken,
+ cryptoID: cookieID.toString(),
+ platformDetails,
});
}
thisUserInfo.threadIDs.add(threadID);
@@ -432,71 +443,27 @@
continue;
}
- const generateNotifUserInfoPromise = async (pushType: PushType) => {
- const promises: Array<
- Promise<?{
- +messageInfo: RawMessageInfo,
- +messageData: MessageData,
- }>,
- > = [];
-
- for (const threadID of preUserPushInfo.notFocusedThreadIDs) {
- const messageIndices = threadsToMessageIndices.get(threadID);
- invariant(
- messageIndices,
- `indices should exist for thread ${threadID}`,
- );
- promises.push(
- ...messageIndices.map(async messageIndex => {
- const messageInfo = messageInfos[messageIndex];
- const { type } = messageInfo;
- if (messageInfo.creatorID === userID) {
- // We never send a user notifs about their own activity
- return undefined;
- }
- const { generatesNotifs } = messageSpecs[type];
- const messageData = messageDatas[messageIndex];
- if (!generatesNotifs) {
- return undefined;
- }
- const doesGenerateNotif = await generatesNotifs(
- messageInfo,
- messageData,
- {
- notifTargetUserID: userID,
- userNotMemberOfSubthreads,
- fetchMessageInfoByID: (messageID: string) =>
- fetchMessageInfoByID(viewer, messageID),
- },
- );
- return doesGenerateNotif === pushType
- ? { messageInfo, messageData }
- : undefined;
- }),
- );
- }
-
- const messagesToNotify = await Promise.all(promises);
- const filteredMessagesToNotify = messagesToNotify.filter(Boolean);
-
- if (filteredMessagesToNotify.length === 0) {
- return undefined;
- }
-
- return {
- devices: userDevices,
- messageInfos: filteredMessagesToNotify.map(
- ({ messageInfo }) => messageInfo,
- ),
- messageDatas: filteredMessagesToNotify.map(
- ({ messageData }) => messageData,
- ),
- };
- };
-
- const userPushInfoPromise = generateNotifUserInfoPromise(pushTypes.NOTIF);
+ const userPushInfoPromise = generateNotifUserInfoPromise(
+ pushTypes.NOTIF,
+ userDevices,
+ messageInfos,
+ messageDatas,
+ threadsToMessageIndices,
+ preUserPushInfo.notFocusedThreadIDs,
+ userNotMemberOfSubthreads,
+ (messageID: string) => fetchMessageInfoByID(viewer, messageID),
+ userID,
+ );
const userRescindInfoPromise = generateNotifUserInfoPromise(
pushTypes.RESCIND,
+ userDevices,
+ messageInfos,
+ messageDatas,
+ threadsToMessageIndices,
+ preUserPushInfo.notFocusedThreadIDs,
+ userNotMemberOfSubthreads,
+ (messageID: string) => fetchMessageInfoByID(viewer, messageID),
+ userID,
);
userPushInfoPromises[userID] = userPushInfoPromise;
diff --git a/keyserver/src/fetchers/message-fetchers.js b/keyserver/src/fetchers/message-fetchers.js
--- a/keyserver/src/fetchers/message-fetchers.js
+++ b/keyserver/src/fetchers/message-fetchers.js
@@ -2,6 +2,7 @@
import invariant from 'invariant';
+import type { PushInfo } from 'lib/push/send-utils.js';
import {
sortMessageInfoList,
shimUnsupportedRawMessageInfos,
@@ -47,7 +48,6 @@
} from '../database/database.js';
import { processQueryForSearch } from '../database/search-utils.js';
import type { SQLStatementType } from '../database/types.js';
-import type { PushInfo } from '../push/send.js';
import type { Viewer } from '../session/viewer.js';
import {
creationString,
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
@@ -19,6 +19,7 @@
createAndroidBadgeOnlyNotification,
} from 'lib/push/android-notif-creators.js';
import { apnMaxNotificationPayloadByteSize } from 'lib/push/apns-notif-creators.js';
+import type { Device, PushUserInfo, PushInfo } from 'lib/push/send-utils.js';
import {
type WebNotifInputData,
webNotifInputDataValidator,
@@ -49,7 +50,6 @@
import type { Platform, PlatformDetails } from 'lib/types/device-types.js';
import { messageTypes } from 'lib/types/message-types-enum.js';
import {
- type MessageData,
type RawMessageInfo,
rawMessageInfoValidator,
} from 'lib/types/message-types.js';
@@ -94,21 +94,6 @@
import { getFCNames } from '../utils/fc-cache.js';
import { validateOutput } from '../utils/validation-utils.js';
-export type Device = {
- +platform: Platform,
- +deviceToken: string,
- +cookieID: string,
- +codeVersion: ?number,
- +stateVersion: ?number,
- +majorDesktopVersion: ?number,
-};
-
-export type PushUserInfo = {
- +devices: Device[],
- // messageInfos and messageDatas have the same key
- +messageInfos: RawMessageInfo[],
- +messageDatas: MessageData[],
-};
type Delivery = PushDelivery | { collapsedInto: string };
type NotificationRow = {
+dbID: string,
@@ -118,7 +103,6 @@
+collapseKey?: ?string,
+deliveries: Delivery[],
};
-export type PushInfo = { [userID: string]: PushUserInfo };
async function sendPushNotifs(pushInfo: PushInfo) {
if (Object.keys(pushInfo).length === 0) {
@@ -873,22 +857,23 @@
Map<string, Array<NotificationTargetDevice>>,
>();
for (const device of devices) {
- let innerMap = byPlatform.get(device.platform);
+ let innerMap = byPlatform.get(device.platformDetails.platform);
if (!innerMap) {
innerMap = new Map<string, Array<NotificationTargetDevice>>();
- byPlatform.set(device.platform, innerMap);
+ byPlatform.set(device.platformDetails.platform, innerMap);
}
const codeVersion: number =
- device.codeVersion !== null && device.codeVersion !== undefined
- ? device.codeVersion
+ device.platformDetails.codeVersion !== null &&
+ device.platformDetails.codeVersion !== undefined
+ ? device.platformDetails.codeVersion
: -1;
- const stateVersion: number = device.stateVersion ?? -1;
+ const stateVersion: number = device.platformDetails.stateVersion ?? -1;
let versionsObject = { codeVersion, stateVersion };
- if (device.majorDesktopVersion) {
+ if (device.platformDetails.majorDesktopVersion) {
versionsObject = {
...versionsObject,
- majorDesktopVersion: device.majorDesktopVersion,
+ majorDesktopVersion: device.platformDetails.majorDesktopVersion,
};
}
@@ -902,8 +887,8 @@
const innerMostArray = innerMostArrayTmp;
innerMostArray.push({
- cryptoID: device.cookieID,
- deliveryID: device.deviceToken,
+ cryptoID: device.cryptoID,
+ deliveryID: device.deliveryID,
});
}
return byPlatform;
diff --git a/lib/push/send-utils.js b/lib/push/send-utils.js
new file mode 100644
--- /dev/null
+++ b/lib/push/send-utils.js
@@ -0,0 +1,250 @@
+// @flow
+
+import invariant from 'invariant';
+import _pickBy from 'lodash/fp/pickBy.js';
+import uuidv4 from 'uuid/v4.js';
+
+import { hasPermission } from '../permissions/minimally-encoded-thread-permissions.js';
+import { rawMessageInfoFromMessageData } from '../shared/message-utils.js';
+import { type PushType, pushTypes } from '../shared/messages/message-spec.js';
+import { messageSpecs } from '../shared/messages/message-specs.js';
+import { isMemberActive } from '../shared/thread-utils.js';
+import type { AuxUserInfos } from '../types/aux-user-types.js';
+import type { PlatformDetails } from '../types/device-types.js';
+import {
+ identityDeviceTypeToPlatform,
+ type IdentityPlatformDetails,
+} from '../types/identity-service-types.js';
+import {
+ type MessageData,
+ type RawMessageInfo,
+ messageDataLocalID,
+} from '../types/message-types.js';
+import type { RawThreadInfos } from '../types/thread-types.js';
+import { promiseAll } from '../utils/promises.js';
+
+export type Device = {
+ +platformDetails: PlatformDetails,
+ +deliveryID: string,
+ +cryptoID: string,
+};
+
+export type PushUserInfo = {
+ +devices: $ReadOnlyArray<Device>,
+ +messageInfos: RawMessageInfo[],
+ +messageDatas: MessageData[],
+};
+
+export type PushInfo = { +[userID: string]: PushUserInfo };
+
+type PushUserThreadInfo = {
+ +devices: $ReadOnlyArray<Device>,
+ +threadIDs: Set<string>,
+};
+
+function identityPlatformDetailsToPlatformDetails(
+ identityPlatformDetails: IdentityPlatformDetails,
+): PlatformDetails {
+ const { deviceType, ...rest } = identityPlatformDetails;
+ return {
+ ...rest,
+ platform: identityDeviceTypeToPlatform[deviceType],
+ };
+}
+
+async function generateNotifUserInfoPromise(
+ pushType: PushType,
+ devices: $ReadOnlyArray<Device>,
+ newMessageInfos: $ReadOnlyArray<RawMessageInfo>,
+ messageDatas: $ReadOnlyArray<MessageData>,
+ threadsToMessageIndices: $ReadOnlyMap<string, number[]>,
+ threadIDs: $ReadOnlySet<string>,
+ userNotMemberOfSubthreads: Set<string>,
+ fetchMessageInfoByID: (messageID: string) => Promise<any>,
+ userID: string,
+): Promise<?{
+ devices: $ReadOnlyArray<Device>,
+ messageDatas: Array<MessageData>,
+ messageInfos: Array<RawMessageInfo>,
+}> {
+ const promises: Array<
+ Promise<?{
+ +messageInfo: RawMessageInfo,
+ +messageData: MessageData,
+ }>,
+ > = [];
+
+ for (const threadID of threadIDs) {
+ const messageIndices = threadsToMessageIndices.get(threadID);
+ invariant(messageIndices, `indices should exist for thread ${threadID}`);
+
+ promises.push(
+ ...messageIndices.map(async messageIndex => {
+ const messageInfo = newMessageInfos[messageIndex];
+ if (messageInfo.creatorID === userID) {
+ return undefined;
+ }
+
+ const { type } = messageInfo;
+ const { generatesNotifs } = messageSpecs[type];
+
+ if (!generatesNotifs) {
+ return undefined;
+ }
+
+ const messageData = messageDatas[messageIndex];
+ const doesGenerateNotif = await generatesNotifs(
+ messageInfo,
+ messageData,
+ {
+ notifTargetUserID: userID,
+ userNotMemberOfSubthreads,
+ fetchMessageInfoByID,
+ },
+ );
+
+ return doesGenerateNotif === pushType
+ ? { messageInfo, messageData }
+ : undefined;
+ }),
+ );
+ }
+
+ const messagesToNotify = await Promise.all(promises);
+ const filteredMessagesToNotify = messagesToNotify.filter(Boolean);
+
+ if (filteredMessagesToNotify.length === 0) {
+ return undefined;
+ }
+
+ return {
+ devices,
+ messageInfos: filteredMessagesToNotify.map(
+ ({ messageInfo }) => messageInfo,
+ ),
+ messageDatas: filteredMessagesToNotify.map(
+ ({ messageData }) => messageData,
+ ),
+ };
+}
+
+async function getPushUserInfo(
+ messageInfos: { +[id: string]: RawMessageInfo },
+ rawThreadInfos: RawThreadInfos,
+ auxUserInfos: AuxUserInfos,
+ messageDatas: $ReadOnlyArray<MessageData>,
+): Promise<{
+ +pushInfos: ?PushInfo,
+ +rescindInfos: ?PushInfo,
+}> {
+ if (messageDatas.length === 0) {
+ return { pushInfos: null, rescindInfos: null };
+ }
+
+ const threadsToMessageIndices: Map<string, number[]> = new Map();
+ const newMessageInfos: RawMessageInfo[] = [];
+
+ let nextNewMessageIndex = 0;
+ for (let i = 0; i < messageDatas.length; i++) {
+ const messageData = messageDatas[i];
+ const threadID = messageData.threadID;
+
+ let messageIndices = threadsToMessageIndices.get(threadID);
+ if (!messageIndices) {
+ messageIndices = [];
+ threadsToMessageIndices.set(threadID, messageIndices);
+ }
+
+ const newMessageIndex = nextNewMessageIndex++;
+ messageIndices.push(newMessageIndex);
+
+ const messageID = messageDataLocalID(messageData) ?? uuidv4();
+ const rawMessageInfo = rawMessageInfoFromMessageData(
+ messageData,
+ messageID,
+ );
+ newMessageInfos.push(rawMessageInfo);
+ }
+
+ const pushUserThreadInfos: { [userID: string]: PushUserThreadInfo } = {};
+ for (const threadID of threadsToMessageIndices.keys()) {
+ const threadInfo = rawThreadInfos[threadID];
+ for (const memberInfo of threadInfo.members) {
+ if (
+ !isMemberActive(memberInfo) ||
+ !hasPermission(memberInfo.permissions, 'visible')
+ ) {
+ continue;
+ }
+
+ if (pushUserThreadInfos[memberInfo.id]) {
+ pushUserThreadInfos[memberInfo.id].threadIDs.add(threadID);
+ continue;
+ }
+
+ const devicesPlatformDetails =
+ auxUserInfos[memberInfo.id].devicesPlatformDetails;
+
+ if (!devicesPlatformDetails) {
+ continue;
+ }
+
+ const devices = Object.entries(devicesPlatformDetails).map(
+ ([deviceID, identityPlatformDetails]) => ({
+ platformDetails: identityPlatformDetailsToPlatformDetails(
+ identityPlatformDetails,
+ ),
+ deliveryID: deviceID,
+ cryptoID: deviceID,
+ }),
+ );
+
+ pushUserThreadInfos[memberInfo.id] = {
+ devices,
+ threadIDs: new Set([threadID]),
+ };
+ }
+ }
+
+ const userPushInfoPromises: { [string]: Promise<?PushUserInfo> } = {};
+ const userRescindInfoPromises: { [string]: Promise<?PushUserInfo> } = {};
+
+ for (const userID in pushUserThreadInfos) {
+ const pushUserThreadInfo = pushUserThreadInfos[userID];
+
+ userPushInfoPromises[userID] = generateNotifUserInfoPromise(
+ pushTypes.NOTIF,
+ pushUserThreadInfo.devices,
+ newMessageInfos,
+ messageDatas,
+ threadsToMessageIndices,
+ pushUserThreadInfo.threadIDs,
+ new Set(),
+ (messageID: string) => (async () => messageInfos[messageID])(),
+ userID,
+ );
+ userRescindInfoPromises[userID] = generateNotifUserInfoPromise(
+ pushTypes.RESCIND,
+ pushUserThreadInfo.devices,
+ newMessageInfos,
+ messageDatas,
+ threadsToMessageIndices,
+ pushUserThreadInfo.threadIDs,
+ new Set(),
+ (messageID: string) => (async () => messageInfos[messageID])(),
+ userID,
+ );
+ }
+
+ const [pushInfo, rescindInfo] = await Promise.all([
+ promiseAll(userPushInfoPromises),
+ promiseAll(userRescindInfoPromises),
+ ]);
+
+ return {
+ pushInfos: _pickBy(Boolean)(pushInfo),
+ rescindInfos: _pickBy(Boolean)(rescindInfo),
+ };
+}
+
+export { getPushUserInfo, generateNotifUserInfoPromise };
diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js
--- a/lib/shared/thread-utils.js
+++ b/lib/shared/thread-utils.js
@@ -220,6 +220,11 @@
);
}
+function isMemberActive(memberInfo: MemberInfoWithPermissions): boolean {
+ const role = memberInfo.role;
+ return role !== null && role !== undefined;
+}
+
function threadIsInHome(threadInfo: ?(RawThreadInfo | ThreadInfo)): boolean {
return !!(threadInfo && threadInfo.currentUser.subscription.home);
}
@@ -1716,4 +1721,5 @@
assertAllThreadInfosAreLegacy,
useOnScreenEntryEditableThreadInfos,
extractMentionedMembers,
+ isMemberActive,
};
diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js
--- a/lib/types/identity-service-types.js
+++ b/lib/types/identity-service-types.js
@@ -9,6 +9,7 @@
type SignedPrekeys,
type OneTimeKeysResultValues,
} from './crypto-types.js';
+import { type Platform } from './device-types.js';
import {
type OlmSessionInitializationInfo,
olmSessionInitializationInfoValidator,
@@ -328,6 +329,17 @@
});
export type IdentityDeviceType = $Values<typeof identityDeviceTypes>;
+
+export const identityDeviceTypeToPlatform: {
+ +[identityDeviceType: IdentityDeviceType]: Platform,
+} = {
+ [identityDeviceTypes.WEB]: 'web',
+ [identityDeviceTypes.ANDROID]: 'android',
+ [identityDeviceTypes.IOS]: 'ios',
+ [identityDeviceTypes.WINDOWS]: 'windows',
+ [identityDeviceTypes.MAC_OS]: 'macos',
+};
+
export const identityDeviceTypeValidator: TEnums = t.enums.of(
values(identityDeviceTypes),
);

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 16, 8:37 PM (21 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2501134
Default Alt Text
D12463.id41559.diff (17 KB)

Event Timeline