Page MenuHomePhorge

D14949.1765105032.diff
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None

D14949.1765105032.diff

diff --git a/lib/actions/backup-actions.js b/lib/actions/backup-actions.js
--- a/lib/actions/backup-actions.js
+++ b/lib/actions/backup-actions.js
@@ -28,10 +28,17 @@
const resetBackupRestoreStateActionType = 'RESET_BACKUP_RESTOTE_STATE';
const markBackupAsRestoredActionType = 'MARK_BACKUP_AS_RESTORED';
+const sendBackupDataToSecondaryActionTypes = Object.freeze({
+ started: 'SEND_BACKUP_DATA_TO_SECONDARY_STARTED',
+ success: 'SEND_BACKUP_DATA_TO_SECONDARY_SUCCESS',
+ failed: 'SEND_BACKUP_DATA_TO_SECONDARY_FAILED',
+});
+
export {
createUserKeysBackupActionTypes,
createUserDataBackupActionTypes,
restoreUserDataStepActionTypes,
resetBackupRestoreStateActionType,
markBackupAsRestoredActionType,
+ sendBackupDataToSecondaryActionTypes,
};
diff --git a/lib/reducers/backup-reducer.js b/lib/reducers/backup-reducer.js
--- a/lib/reducers/backup-reducer.js
+++ b/lib/reducers/backup-reducer.js
@@ -8,6 +8,7 @@
restoreUserDataStepActionTypes,
markBackupAsRestoredActionType,
resetBackupRestoreStateActionType,
+ sendBackupDataToSecondaryActionTypes,
} from '../actions/backup-actions.js';
import {
changeIdentityUserPasswordActionTypes,
@@ -57,6 +58,14 @@
...store,
latestBackupInfo: null,
};
+ } else if (action.type === sendBackupDataToSecondaryActionTypes.success) {
+ const recipients = action.payload;
+ return {
+ ...store,
+ ownDevicesWithoutBackup: store.ownDevicesWithoutBackup?.filter(
+ deviceID => !recipients.includes(deviceID),
+ ),
+ };
}
return store;
}
diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js
--- a/lib/types/redux-types.js
+++ b/lib/types/redux-types.js
@@ -1687,6 +1687,22 @@
+type: 'MARK_BACKUP_AS_RESTORED',
+payload?: void,
}
+ | {
+ +type: 'SEND_BACKUP_DATA_TO_SECONDARY_STARTED',
+ +loadingInfo: LoadingInfo,
+ +payload?: void,
+ }
+ | {
+ +type: 'SEND_BACKUP_DATA_TO_SECONDARY_FAILED',
+ +error: true,
+ +payload: Error,
+ +loadingInfo: LoadingInfo,
+ }
+ | {
+ +type: 'SEND_BACKUP_DATA_TO_SECONDARY_SUCCESS',
+ +payload: $ReadOnlyArray<string>,
+ +loadingInfo: LoadingInfo,
+ }
| {
+type: 'SAVE_UNSUPPORTED_DM_OPERATION',
+payload: SaveUnsupportedOperationPayload,
diff --git a/lib/types/tunnelbroker/user-actions-peer-to-peer-message-types.js b/lib/types/tunnelbroker/user-actions-peer-to-peer-message-types.js
--- a/lib/types/tunnelbroker/user-actions-peer-to-peer-message-types.js
+++ b/lib/types/tunnelbroker/user-actions-peer-to-peer-message-types.js
@@ -2,7 +2,9 @@
import t, { type TInterface, type TUnion } from 'tcomb';
-import { tShape, tString } from '../../utils/validation-utils.js';
+import type { QRAuthBackupData } from './qr-code-auth-message-types.js';
+import { qrAuthBackupDataValidator } from './qr-code-auth-message-types.js';
+import { tShape, tString, tUserID } from '../../utils/validation-utils.js';
import { type DMOperation, dmOperationValidator } from '../dm-ops.js';
export const userActionsP2PMessageTypes = Object.freeze({
@@ -10,6 +12,7 @@
LOG_OUT_SECONDARY_DEVICE: 'LOG_OUT_SECONDARY_DEVICE',
ACCOUNT_DELETION: 'ACCOUNT_DELETION',
DM_OPERATION: 'DM_OPERATION',
+ BACKUP_DATA: 'BACKUP_DATA',
});
export type DeviceLogoutP2PMessage = {
@@ -47,11 +50,28 @@
op: dmOperationValidator,
});
+// Used when the primary wants to send backup keys after uploading the backup
+// for the first time
+export type BackupDataP2PMessage = {
+ +type: 'BACKUP_DATA',
+ +userID: string,
+ +primaryDeviceID: string,
+ +backupData: QRAuthBackupData,
+};
+export const backupDataP2PMessageValidator: TInterface<BackupDataP2PMessage> =
+ tShape<BackupDataP2PMessage>({
+ type: tString(userActionsP2PMessageTypes.BACKUP_DATA),
+ userID: tUserID,
+ primaryDeviceID: t.String,
+ backupData: t.maybe(qrAuthBackupDataValidator),
+ });
+
export type UserActionP2PMessage =
| DeviceLogoutP2PMessage
| SecondaryDeviceLogoutP2PMessage
| AccountDeletionP2PMessage
- | DMOperationP2PMessage;
+ | DMOperationP2PMessage
+ | BackupDataP2PMessage;
export const userActionP2PMessageValidator: TUnion<UserActionP2PMessage> =
t.union([
@@ -59,4 +79,5 @@
secondaryDeviceLogoutP2PMessageValidator,
accountDeletionP2PMessageValidator,
dmOperationP2PMessageValidator,
+ backupDataP2PMessageValidator,
]);
diff --git a/native/backup/secondary-devices-backup-handler.react.js b/native/backup/secondary-devices-backup-handler.react.js
new file mode 100644
--- /dev/null
+++ b/native/backup/secondary-devices-backup-handler.react.js
@@ -0,0 +1,120 @@
+// @flow
+
+import * as React from 'react';
+
+import { sendBackupDataToSecondaryActionTypes } from 'lib/actions/backup-actions.js';
+import { getOwnPeerDevices } from 'lib/selectors/user-selectors.js';
+import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
+import { usePeerToPeerCommunication } from 'lib/tunnelbroker/peer-to-peer-context.js';
+import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js';
+import {
+ type BackupDataP2PMessage,
+ userActionsP2PMessageTypes,
+} from 'lib/types/tunnelbroker/user-actions-peer-to-peer-message-types.js';
+import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
+import { fullBackupSupport } from 'lib/utils/services-utils.js';
+
+import { commCoreModule } from '../native-modules.js';
+import { useSelector } from '../redux/redux-utils.js';
+
+function SecondaryDevicesBackupHandler(): React.Node {
+ const identityContext = React.useContext(IdentityClientContext);
+ if (!identityContext) {
+ throw new Error('Identity service client is not initialized');
+ }
+ const { getAuthMetadata } = identityContext;
+
+ const ownDevicesWithoutBackup = useSelector(
+ state => state.backupStore.ownDevicesWithoutBackup,
+ );
+ const ownDevices = useSelector(getOwnPeerDevices);
+ const { broadcastEphemeralMessage } = usePeerToPeerCommunication();
+ const dispatchActionPromise = useDispatchActionPromise();
+ const { socketState } = useTunnelbroker();
+ const restoreBackupState = useSelector(state => state.restoreBackupState);
+
+ const sendBackupDataToSecondaryDevices = React.useCallback(async (): Promise<
+ $ReadOnlyArray<string>,
+ > => {
+ if (!ownDevicesWithoutBackup || !ownDevicesWithoutBackup.length) {
+ return [];
+ }
+
+ // Avoid sending to devices that are removed since last backup upload
+ const removedDevices = ownDevicesWithoutBackup.filter(
+ deviceID =>
+ !ownDevices.find(ownDevice => ownDevice.deviceID === deviceID),
+ );
+
+ const backupData = await commCoreModule.getQRAuthBackupData();
+
+ const authMetadata = await getAuthMetadata();
+ const { userID: thisUserID, deviceID: thisDeviceID } = authMetadata;
+ if (!thisDeviceID || !thisUserID) {
+ throw new Error('No auth metadata');
+ }
+
+ const backupDataP2PMessage: BackupDataP2PMessage = {
+ type: userActionsP2PMessageTypes.BACKUP_DATA,
+ userID: thisUserID,
+ primaryDeviceID: thisDeviceID,
+ backupData,
+ };
+ const rawPayload = JSON.stringify(backupDataP2PMessage);
+
+ const recipients =
+ ownDevicesWithoutBackup
+ ?.filter(deviceID => !removedDevices.includes(deviceID))
+ ?.map(deviceID => ({
+ deviceID,
+ userID: thisUserID,
+ })) ?? [];
+
+ const devicesThatReceivedMessage = await broadcastEphemeralMessage(
+ rawPayload,
+ recipients,
+ authMetadata,
+ );
+ return [...removedDevices, ...devicesThatReceivedMessage];
+ }, [
+ broadcastEphemeralMessage,
+ getAuthMetadata,
+ ownDevices,
+ ownDevicesWithoutBackup,
+ ]);
+
+ const executed = React.useRef(false);
+ React.useEffect(() => {
+ if (
+ !fullBackupSupport ||
+ executed.current ||
+ !socketState.isAuthorized ||
+ !ownDevicesWithoutBackup ||
+ !ownDevicesWithoutBackup.length
+ ) {
+ return;
+ }
+
+ // This condition implies that this device is primary.
+ if (restoreBackupState.status !== 'user_data_backup_success') {
+ return;
+ }
+
+ void dispatchActionPromise(
+ sendBackupDataToSecondaryActionTypes,
+ sendBackupDataToSecondaryDevices(),
+ );
+
+ executed.current = true;
+ }, [
+ dispatchActionPromise,
+ ownDevicesWithoutBackup,
+ restoreBackupState.status,
+ sendBackupDataToSecondaryDevices,
+ socketState.isAuthorized,
+ ]);
+
+ return null;
+}
+
+export { SecondaryDevicesBackupHandler };
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -56,6 +56,7 @@
import { RegistrationContextProvider } from './account/registration/registration-context-provider.react.js';
import NativeEditThreadAvatarProvider from './avatars/native-edit-thread-avatar-provider.react.js';
import BackupHandlerContextProvider from './backup/backup-handler-context-provider.js';
+import { SecondaryDevicesBackupHandler } from './backup/secondary-devices-backup-handler.react.js';
import { BottomSheetProvider } from './bottom-sheet/bottom-sheet-provider.react.js';
import ChatContextProvider from './chat/chat-context-provider.react.js';
import MessageEditingContextProvider from './chat/message-editing-context-provider.react.js';
@@ -410,6 +411,7 @@
<AutoJoinCommunityHandler />
<SyncCommunityStoreHandler />
<InitialStateSharingHandler />
+ <SecondaryDevicesBackupHandler />
</PersistedStateGate>
{navigation}
</RegistrationContextProvider>

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 7, 10:57 AM (11 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5842159
Default Alt Text
D14949.1765105032.diff (9 KB)

Event Timeline