Page MenuHomePhabricator

D13164.id43628.diff
No OneTemporary

D13164.id43628.diff

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

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)

Event Timeline