Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3394770
D13164.id43628.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
D13164.id43628.diff
View Options
diff --git a/lib/push/send-hooks.react.js b/lib/push/send-hooks.react.js
--- a/lib/push/send-hooks.react.js
+++ b/lib/push/send-hooks.react.js
@@ -1,16 +1,19 @@
// @flow
+import invariant from 'invariant';
import * as React from 'react';
import uuid from 'uuid';
import {
preparePushNotifs,
+ prepareOwnDevicesPushNotifs,
type PerUserTargetedNotifications,
} from './send-utils.js';
import { ENSCacheContext } from '../components/ens-cache-provider.react.js';
import { NeynarClientContext } from '../components/neynar-client-provider.react.js';
import { usePeerOlmSessionsCreatorContext } from '../components/peer-olm-session-creator-provider.react.js';
import { thickRawThreadInfosSelector } from '../selectors/thread-selectors.js';
+import { IdentityClientContext } from '../shared/identity-client-context.js';
import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js';
import type {
TargetedAPNsNotification,
@@ -27,7 +30,6 @@
TunnelbrokerWNSNotif,
} from '../types/tunnelbroker/notif-types.js';
import { getConfig } from '../utils/config.js';
-import { getContentSigningKey } from '../utils/crypto-utils.js';
import { getMessageForException } from '../utils/errors.js';
import { useSelector } from '../utils/redux-utils.js';
@@ -98,6 +100,9 @@
function useSendPushNotifs(): (
notifCreationData: NotificationsCreationData,
) => Promise<?PerUserTargetedNotifications> {
+ const client = React.useContext(IdentityClientContext);
+ invariant(client, 'Identity context should be set');
+ const { getAuthMetadata } = client;
const rawMessageInfos = useSelector(state => state.messageStore.messages);
const thickRawThreadInfos = useSelector(thickRawThreadInfosSelector);
const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos);
@@ -111,9 +116,17 @@
return React.useCallback(
async (notifCreationData: NotificationsCreationData) => {
- const deviceID = await getContentSigningKey();
+ const { deviceID, userID: senderUserID } = await getAuthMetadata();
+ if (!deviceID || !senderUserID) {
+ return;
+ }
+
const senderDeviceDescriptor = { senderDeviceID: deviceID };
- const { messageDatas } = notifCreationData;
+ const senderInfo = {
+ senderUserID,
+ senderDeviceDescriptor,
+ };
+ const { messageDatas, rescindData, badgeUpdateData } = notifCreationData;
const pushNotifsPreparationInput = {
encryptedNotifUtilsAPI,
@@ -128,17 +141,36 @@
getFCNames,
};
- const preparedPushNotifs = await preparePushNotifs(
- pushNotifsPreparationInput,
- );
+ const ownDevicesPushNotifsPreparationInput = {
+ encryptedNotifUtilsAPI,
+ senderInfo,
+ olmSessionCreator,
+ auxUserInfos,
+ rescindData,
+ badgeUpdateData,
+ };
+
+ const [preparedPushNotifs, preparedOwnDevicesPushNotifs] =
+ await Promise.all([
+ preparePushNotifs(pushNotifsPreparationInput),
+ prepareOwnDevicesPushNotifs(ownDevicesPushNotifsPreparationInput),
+ ]);
- if (!preparedPushNotifs) {
+ if (!preparedPushNotifs && !prepareOwnDevicesPushNotifs) {
return;
}
+ let allPreparedPushNotifs = preparedPushNotifs;
+ if (preparedOwnDevicesPushNotifs && senderUserID) {
+ allPreparedPushNotifs = {
+ ...allPreparedPushNotifs,
+ [senderUserID]: preparedOwnDevicesPushNotifs,
+ };
+ }
+
const sendPromises = [];
- for (const userID in preparedPushNotifs) {
- for (const notif of preparedPushNotifs[userID]) {
+ for (const userID in allPreparedPushNotifs) {
+ for (const notif of allPreparedPushNotifs[userID]) {
if (notif.targetedNotification.notification.encryptionFailed) {
continue;
}
@@ -183,6 +215,7 @@
await Promise.all(sendPromises);
},
[
+ getAuthMetadata,
sendNotif,
encryptedNotifUtilsAPI,
olmSessionCreator,
diff --git a/lib/push/send-utils.js b/lib/push/send-utils.js
--- a/lib/push/send-utils.js
+++ b/lib/push/send-utils.js
@@ -3,8 +3,16 @@
import _pickBy from 'lodash/fp/pickBy.js';
import uuidv4 from 'uuid/v4.js';
-import { createAndroidVisualNotification } from './android-notif-creators.js';
-import { createAPNsVisualNotification } from './apns-notif-creators.js';
+import {
+ createAndroidVisualNotification,
+ createAndroidBadgeOnlyNotification,
+ createAndroidNotificationRescind,
+} from './android-notif-creators.js';
+import {
+ createAPNsVisualNotification,
+ createAPNsBadgeOnlyNotification,
+ createAPNsNotificationRescind,
+} from './apns-notif-creators.js';
import {
stringToVersionKey,
getDevicesByPlatform,
@@ -103,12 +111,12 @@
messageInfos: { +[id: string]: RawMessageInfo },
thickRawThreadInfos: ThickRawThreadInfos,
auxUserInfos: AuxUserInfos,
- messageDatas: $ReadOnlyArray<MessageData>,
+ messageDatas: ?$ReadOnlyArray<MessageData>,
): Promise<{
+pushInfos: ?PushInfo,
+rescindInfos: ?PushInfo,
}> {
- if (messageDatas.length === 0) {
+ if (!messageDatas || messageDatas.length === 0) {
return { pushInfos: null, rescindInfos: null };
}
@@ -250,6 +258,48 @@
};
}
+type SenderInfo = {
+ +senderUserID: string,
+ +senderDeviceDescriptor: SenderDeviceDescriptor,
+};
+
+type OwnDevicesPushInfo = {
+ +devices: $ReadOnlyArray<Device>,
+};
+
+function getOwnDevicesPushInfo(
+ senderInfo: SenderInfo,
+ auxUserInfos: AuxUserInfos,
+): ?OwnDevicesPushInfo {
+ const {
+ senderUserID,
+ senderDeviceDescriptor: { senderDeviceID },
+ } = senderInfo;
+
+ if (!senderDeviceID) {
+ return null;
+ }
+
+ const senderDevicesWithPlatformDetails =
+ auxUserInfos[senderUserID].devicesPlatformDetails;
+
+ if (!senderDevicesWithPlatformDetails) {
+ return null;
+ }
+
+ const devices = Object.entries(senderDevicesWithPlatformDetails)
+ .filter(([deviceID]) => deviceID !== senderDeviceID)
+ .map(([deviceID, identityPlatformDetails]) => ({
+ platformDetails: identityPlatformDetailsToPlatformDetails(
+ identityPlatformDetails,
+ ),
+ deliveryID: deviceID,
+ cryptoID: deviceID,
+ }));
+
+ return { devices };
+}
+
function pushInfoToCollapsableNotifInfo(pushInfo: PushInfo): {
+usersToCollapseKeysToInfo: {
[string]: { [string]: CollapsableNotifInfo },
@@ -642,6 +692,187 @@
return (await Promise.all(promises)).flat();
}
+async function buildRescindsForOwnDevices(
+ encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI,
+ senderDeviceDescriptor: SenderDeviceDescriptor,
+ devicesByPlatform: $ReadOnlyMap<
+ Platform,
+ $ReadOnlyMap<string, $ReadOnlyArray<NotificationTargetDevice>>,
+ >,
+ rescindData: { +threadID: string },
+): Promise<$ReadOnlyArray<TargetedNotificationWithPlatform>> {
+ const { threadID } = rescindData;
+ const promises: Array<
+ Promise<$ReadOnlyArray<TargetedNotificationWithPlatform>>,
+ > = [];
+
+ const iosVersionToDevices = devicesByPlatform.get('ios');
+ if (iosVersionToDevices) {
+ for (const [versionKey, devices] of iosVersionToDevices) {
+ const { codeVersion, stateVersion } = stringToVersionKey(versionKey);
+ const platformDetails = {
+ platform: 'ios',
+ codeVersion,
+ stateVersion,
+ };
+
+ promises.push(
+ (async () => {
+ return (
+ await createAPNsNotificationRescind(
+ encryptedNotifUtilsAPI,
+ {
+ senderDeviceDescriptor,
+ threadID,
+ platformDetails,
+ },
+ devices,
+ )
+ ).map(targetedNotification => ({
+ platform: 'ios',
+ targetedNotification,
+ }));
+ })(),
+ );
+ }
+ }
+
+ const androidVersionToDevices = devicesByPlatform.get('android');
+ if (androidVersionToDevices) {
+ for (const [versionKey, devices] of androidVersionToDevices) {
+ const { codeVersion, stateVersion } = stringToVersionKey(versionKey);
+ const platformDetails = {
+ platform: 'android',
+ codeVersion,
+ stateVersion,
+ };
+
+ promises.push(
+ (async () => {
+ return (
+ await createAndroidNotificationRescind(
+ encryptedNotifUtilsAPI,
+ { senderDeviceDescriptor, threadID, platformDetails },
+ devices,
+ )
+ ).map(targetedNotification => ({
+ platform: 'android',
+ targetedNotification,
+ }));
+ })(),
+ );
+ }
+ }
+ return (await Promise.all(promises)).flat();
+}
+
+async function buildBadgeUpdatesForOwnDevices(
+ encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI,
+ senderDeviceDescriptor: SenderDeviceDescriptor,
+ devicesByPlatform: $ReadOnlyMap<
+ Platform,
+ $ReadOnlyMap<string, $ReadOnlyArray<NotificationTargetDevice>>,
+ >,
+ badgeUpdateData: { +threadID: string },
+): Promise<$ReadOnlyArray<TargetedNotificationWithPlatform>> {
+ const { threadID } = badgeUpdateData;
+ const promises: Array<
+ Promise<$ReadOnlyArray<TargetedNotificationWithPlatform>>,
+ > = [];
+
+ const iosVersionToDevices = devicesByPlatform.get('ios');
+ if (iosVersionToDevices) {
+ for (const [versionKey, devices] of iosVersionToDevices) {
+ const { codeVersion, stateVersion } = stringToVersionKey(versionKey);
+ const platformDetails = {
+ platform: 'ios',
+ codeVersion,
+ stateVersion,
+ };
+
+ promises.push(
+ (async () => {
+ return (
+ await createAPNsBadgeOnlyNotification(
+ encryptedNotifUtilsAPI,
+ {
+ senderDeviceDescriptor,
+ threadID,
+ platformDetails,
+ },
+ devices,
+ )
+ ).map(targetedNotification => ({
+ platform: 'ios',
+ targetedNotification,
+ }));
+ })(),
+ );
+ }
+ }
+
+ const androidVersionToDevices = devicesByPlatform.get('android');
+ if (androidVersionToDevices) {
+ for (const [versionKey, devices] of androidVersionToDevices) {
+ const { codeVersion, stateVersion } = stringToVersionKey(versionKey);
+ const platformDetails = {
+ platform: 'android',
+ codeVersion,
+ stateVersion,
+ };
+
+ promises.push(
+ (async () => {
+ return (
+ await createAndroidBadgeOnlyNotification(
+ encryptedNotifUtilsAPI,
+ { senderDeviceDescriptor, threadID, platformDetails },
+ devices,
+ )
+ ).map(targetedNotification => ({
+ platform: 'android',
+ targetedNotification,
+ }));
+ })(),
+ );
+ }
+ }
+
+ const macosVersionToDevices = devicesByPlatform.get('macos');
+ if (macosVersionToDevices) {
+ for (const [versionKey, devices] of macosVersionToDevices) {
+ const { codeVersion, stateVersion, majorDesktopVersion } =
+ stringToVersionKey(versionKey);
+ const platformDetails = {
+ platform: 'macos',
+ codeVersion,
+ stateVersion,
+ majorDesktopVersion,
+ };
+
+ promises.push(
+ (async () => {
+ return (
+ await createAPNsBadgeOnlyNotification(
+ encryptedNotifUtilsAPI,
+ {
+ senderDeviceDescriptor,
+ threadID,
+ platformDetails,
+ },
+ devices,
+ )
+ ).map(targetedNotification => ({
+ platform: 'macos',
+ targetedNotification,
+ }));
+ })(),
+ );
+ }
+ }
+ return (await Promise.all(promises)).flat();
+}
+
export type PerUserTargetedNotifications = {
+[userID: string]: $ReadOnlyArray<TargetedNotificationWithPlatform>,
};
@@ -740,6 +971,43 @@
return promiseAll(perUserBuildNotifsResultPromises);
}
+async function createOlmSessionWithDevices(
+ deviceIDsToUserIDs: {
+ +[string]: string,
+ },
+ olmSessionCreator: (userID: string, deviceID: string) => Promise<void>,
+): Promise<void> {
+ const {
+ initializeCryptoAccount,
+ isNotificationsSessionInitializedWithDevices,
+ } = getConfig().olmAPI;
+ await initializeCryptoAccount();
+
+ const deviceIDsToSessionPresence =
+ await isNotificationsSessionInitializedWithDevices(
+ Object.keys(deviceIDsToUserIDs),
+ );
+
+ const olmSessionCreationPromises = [];
+ for (const deviceID in deviceIDsToSessionPresence) {
+ if (deviceIDsToSessionPresence[deviceID]) {
+ continue;
+ }
+ olmSessionCreationPromises.push(
+ olmSessionCreator(deviceIDsToUserIDs[deviceID], deviceID),
+ );
+ }
+
+ try {
+ await Promise.allSettled(olmSessionCreationPromises);
+ } catch (e) {
+ // session creation may fail for some devices
+ // but we should still pursue notification
+ // delivery for others
+ console.log(e);
+ }
+}
+
type PreparePushNotifsInputData = {
+encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI,
+senderDeviceDescriptor: SenderDeviceDescriptor,
@@ -747,7 +1015,7 @@
+messageInfos: { +[id: string]: RawMessageInfo },
+thickRawThreadInfos: ThickRawThreadInfos,
+auxUserInfos: AuxUserInfos,
- +messageDatas: $ReadOnlyArray<MessageData>,
+ +messageDatas: ?$ReadOnlyArray<MessageData>,
+userInfos: UserInfos,
+getENSNames: ?GetENSNames,
+getFCNames: ?GetFCNames,
@@ -780,12 +1048,6 @@
return null;
}
- const {
- initializeCryptoAccount,
- isNotificationsSessionInitializedWithDevices,
- } = getConfig().olmAPI;
- await initializeCryptoAccount();
-
const deviceIDsToUserIDs: { [string]: string } = {};
for (const userID in pushInfos) {
for (const device of pushInfos[userID].devices) {
@@ -793,29 +1055,7 @@
}
}
- const deviceIDsToSessionPresence =
- await isNotificationsSessionInitializedWithDevices(
- Object.keys(deviceIDsToUserIDs),
- );
-
- const olmSessionCreationPromises = [];
- for (const deviceID in deviceIDsToSessionPresence) {
- if (deviceIDsToSessionPresence[deviceID]) {
- continue;
- }
- olmSessionCreationPromises.push(
- olmSessionCreator(deviceIDsToUserIDs[deviceID], deviceID),
- );
- }
-
- try {
- await Promise.allSettled(olmSessionCreationPromises);
- } catch (e) {
- // session creation may fail for some devices
- // but we should still pursue notification
- // delivery for others
- console.log(e);
- }
+ await createOlmSessionWithDevices(deviceIDsToUserIDs, olmSessionCreator);
return await buildNotifsFromPushInfo({
encryptedNotifUtilsAPI,
@@ -828,8 +1068,65 @@
});
}
+type PrepareOwnDevicesPushNotifsInputData = {
+ +encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI,
+ +senderInfo: SenderInfo,
+ +olmSessionCreator: (userID: string, deviceID: string) => Promise<void>,
+ +auxUserInfos: AuxUserInfos,
+ +rescindData?: { threadID: string },
+ +badgeUpdateData?: { threadID: string },
+};
+
+async function prepareOwnDevicesPushNotifs(
+ inputData: PrepareOwnDevicesPushNotifsInputData,
+): Promise<?$ReadOnlyArray<TargetedNotificationWithPlatform>> {
+ const {
+ encryptedNotifUtilsAPI,
+ senderInfo,
+ olmSessionCreator,
+ auxUserInfos,
+ rescindData,
+ badgeUpdateData,
+ } = inputData;
+
+ const ownDevicesPushInfo = getOwnDevicesPushInfo(senderInfo, auxUserInfos);
+
+ if (!ownDevicesPushInfo) {
+ return null;
+ }
+
+ const { senderUserID, senderDeviceDescriptor } = senderInfo;
+ const deviceIDsToUserIDs: { [string]: string } = {};
+
+ for (const device of ownDevicesPushInfo.devices) {
+ deviceIDsToUserIDs[device.cryptoID] = senderUserID;
+ }
+
+ await createOlmSessionWithDevices(deviceIDsToUserIDs, olmSessionCreator);
+ const devicesByPlatform = getDevicesByPlatform(ownDevicesPushInfo.devices);
+
+ if (rescindData) {
+ return await buildRescindsForOwnDevices(
+ encryptedNotifUtilsAPI,
+ senderDeviceDescriptor,
+ devicesByPlatform,
+ rescindData,
+ );
+ } else if (badgeUpdateData) {
+ return await buildBadgeUpdatesForOwnDevices(
+ encryptedNotifUtilsAPI,
+ senderDeviceDescriptor,
+ devicesByPlatform,
+ badgeUpdateData,
+ );
+ } else {
+ return null;
+ }
+}
+
export {
preparePushNotifs,
+ prepareOwnDevicesPushNotifs,
generateNotifUserInfoPromise,
pushInfoToCollapsableNotifInfo,
mergeUserToCollapsableInfoInPlace,
diff --git a/lib/shared/dm-ops/change-thread-status-spec.js b/lib/shared/dm-ops/change-thread-status-spec.js
--- a/lib/shared/dm-ops/change-thread-status-spec.js
+++ b/lib/shared/dm-ops/change-thread-status-spec.js
@@ -11,6 +11,15 @@
const changeThreadStatusSpec: DMOperationSpec<DMChangeThreadStatusOperation> =
Object.freeze({
+ notificationsCreationData: async (
+ dmOperation: DMChangeThreadStatusOperation,
+ ) => {
+ const { threadID, unread } = dmOperation;
+ if (unread) {
+ return { badgeUpdateData: { threadID } };
+ }
+ return { rescindData: { threadID } };
+ },
processDMOperation: async (dmOperation: DMChangeThreadStatusOperation) => {
const { threadID, unread, time } = dmOperation;
const updateInfos = [
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
@@ -28,9 +28,14 @@
prefix: t.maybe(t.String),
});
-export type NotificationsCreationData = {
- +messageDatas: $ReadOnlyArray<MessageData>,
-};
+export type NotificationsCreationData =
+ | {
+ +messageDatas: $ReadOnlyArray<MessageData>,
+ }
+ | {
+ +rescindData: { threadID: string },
+ }
+ | { +badgeUpdateData: { threadID: string } };
export type SenderDeviceDescriptor =
| { +keyserverID: string }
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Dec 1, 9:56 PM (18 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2606185
Default Alt Text
D13164.id43628.diff (17 KB)
Attached To
Mode
D13164: Implement code in lib to create thick threads rescinds and badge updates
Attached
Detach File
Event Timeline
Log In to Comment