Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32197992
D14949.1765105032.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D14949.1765105032.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D14949: [lib][native] implement sending backup keys to existing secondary devices
Attached
Detach File
Event Timeline
Log In to Comment