diff --git a/native/backup/backup-handler-context-provider.js b/native/backup/backup-handler-context-provider.js --- a/native/backup/backup-handler-context-provider.js +++ b/native/backup/backup-handler-context-provider.js @@ -15,6 +15,7 @@ } from 'lib/selectors/user-selectors.js'; import { useStaffAlert } from 'lib/shared/staff-utils.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; +import { type LocalLatestBackupInfo } from 'lib/types/backup-types.js'; import { rawDeviceListFromSignedList } from 'lib/utils/device-list-utils.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; @@ -30,6 +31,22 @@ import { commCoreModule } from '../native-modules.js'; import { useSelector } from '../redux/redux-utils.js'; +// Two weeks in milliseconds +const backupInterval = 14 * 24 * 60 * 60 * 1000; + +function checkIfCompactionNeeded( + latestBackupInfo: ?LocalLatestBackupInfo, +): boolean { + if (!fullBackupSupport) { + return false; + } + if (!latestBackupInfo) { + return false; + } + + return Date.now() - latestBackupInfo.timestamp >= backupInterval; +} + type Props = { +children: React.Node, }; @@ -40,7 +57,7 @@ const getCurrentIdentityUserState = useCurrentIdentityUserState(); const { socketState } = useTunnelbroker(); const migrateToNewFlow = useMigrationToNewFlow(); - const { createUserKeysBackup } = useClientBackup(); + const { createUserKeysBackup, createUserDataBackup } = useClientBackup(); const dispatchActionPromise = useDispatchActionPromise(); const dispatch = useDispatch(); @@ -71,7 +88,11 @@ const performBackupUpload = React.useCallback(async () => { const promise = (async () => { - const backupID = await createUserKeysBackup(); + const backupMethod = fullBackupSupport + ? createUserDataBackup + : createUserKeysBackup; + + const backupID = await backupMethod(); return { backupID, timestamp: Date.now(), @@ -79,7 +100,7 @@ })(); void dispatchActionPromise(createUserKeysBackupActionTypes, promise); await promise; - }, [createUserKeysBackup, dispatchActionPromise]); + }, [createUserDataBackup, createUserKeysBackup, dispatchActionPromise]); const startBackupHandler = React.useCallback(() => { if (handlerStartedRef.current) { @@ -163,7 +184,9 @@ step = 'computing conditions'; const shouldDoMigration = usingRestoreFlow && !currentDeviceList.curPrimarySignature; - const shouldUploadBackup = isPrimary && !latestBackupInfo; + const shouldUploadUserKeys = isPrimary && !latestBackupInfo; + const shouldUploadUserData = + isPrimary && checkIfCompactionNeeded(latestBackupInfo); // Tunnelbroker connection is required to broadcast // device list updates after migration. @@ -172,7 +195,11 @@ } // Migration or backup upload are not needed. - if (!shouldDoMigration && !shouldUploadBackup) { + if ( + !shouldDoMigration && + !shouldUploadUserKeys && + !shouldUploadUserData + ) { return; } @@ -183,7 +210,10 @@ if (shouldDoMigration) { step = 'migrating to signed device lists'; await performMigrationToNewFlow(currentIdentityUserState); - } else if (shouldUploadBackup) { + } else if (shouldUploadUserData) { + step = 'creating User Data backup'; + await performBackupUpload(); + } else if (shouldUploadUserKeys) { step = 'creating User Keys backup'; await performBackupUpload(); } diff --git a/native/backup/use-client-backup.js b/native/backup/use-client-backup.js --- a/native/backup/use-client-backup.js +++ b/native/backup/use-client-backup.js @@ -18,7 +18,7 @@ import { useSelector } from '../redux/redux-utils.js'; type ClientBackup = { - +createFullBackup: () => Promise, + +createUserDataBackup: () => Promise, +createUserKeysBackup: () => Promise, +retrieveLatestBackupInfo: ( userIdentifier: string, @@ -87,9 +87,9 @@ [invalidTokenLogOut], ); - const createFullBackup = React.useCallback(async () => { + const createUserDataBackup = React.useCallback(async () => { if (!loggedIn || !currentUserID) { - throw new Error('Attempt to upload backup for not logged in user.'); + throw new Error('Attempt to upload User Data for not logged in user.'); } const backupSecret = await getBackupSecret(); @@ -109,12 +109,12 @@ return React.useMemo( () => ({ - createFullBackup, + createUserDataBackup, createUserKeysBackup, retrieveLatestBackupInfo, getBackupUserKeys, }), - [createFullBackup, createUserKeysBackup], + [createUserDataBackup, createUserKeysBackup], ); } diff --git a/native/backup/use-migration-to-new-flow.js b/native/backup/use-migration-to-new-flow.js --- a/native/backup/use-migration-to-new-flow.js +++ b/native/backup/use-migration-to-new-flow.js @@ -22,6 +22,7 @@ rawDeviceListFromSignedList, } from 'lib/utils/device-list-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; +import { fullBackupSupport } from 'lib/utils/services-utils.js'; import { useClientBackup } from './use-client-backup.js'; import { useSelector } from '../redux/redux-utils.js'; @@ -63,7 +64,11 @@ const userIdentifier = useSelector(state => state.currentUserInfo?.username); const allPeerDevices = useSelector(getAllPeerDevices); - const { retrieveLatestBackupInfo, createUserKeysBackup } = useClientBackup(); + const { + retrieveLatestBackupInfo, + createUserKeysBackup, + createUserDataBackup, + } = useClientBackup(); const getAndUpdateDeviceListsForUsers = useGetAndUpdateDeviceListsForUsers(); const broadcastDeviceListUpdates = useBroadcastDeviceListUpdates(); const dispatch = useDispatch(); @@ -79,6 +84,10 @@ 'Are you calling it on a non-primary device?', ); + const createBackup = fullBackupSupport + ? createUserDataBackup + : createUserKeysBackup; + const { deviceID, userID, @@ -86,8 +95,8 @@ currentUserPlatformDetails, } = currentIdentityUserState; - // 1. upload UserKeys (without updating the store) - let backupID = await createUserKeysBackup(); + // 1. upload backup (without updating the store) + let backupID = await createBackup(); // 2. create in-memory device list (reorder and sign) const newDeviceList = await reorderAndSignDeviceList( @@ -126,7 +135,7 @@ throw new Error(`Backup ID mismatched ${retryCount} times`); } - backupID = await createUserKeysBackup(); + backupID = await createBackup(); fetchedBackupInfo = await retrieveLatestBackupInfo(userIdentifier); } @@ -139,6 +148,7 @@ [ allPeerDevices, broadcastDeviceListUpdates, + createUserDataBackup, createUserKeysBackup, dispatch, getAndUpdateDeviceListsForUsers, diff --git a/native/profile/backup-menu.react.js b/native/profile/backup-menu.react.js --- a/native/profile/backup-menu.react.js +++ b/native/profile/backup-menu.react.js @@ -42,7 +42,7 @@ invariant(userIdentifier, 'userIdentifier should be set'); const { - createFullBackup, + createUserDataBackup, retrieveLatestBackupInfo, createUserKeysBackup, getBackupUserKeys, @@ -51,14 +51,14 @@ const uploadBackup = React.useCallback(async () => { let message; try { - const backupID = await createFullBackup(); + const backupID = await createUserDataBackup(); message = `Success!\n` + `Backup ID: ${backupID}`; } catch (e) { message = `Backup upload error: ${String(getMessageForException(e))}`; console.error(message); } Alert.alert('Upload protocol result', message); - }, [createFullBackup]); + }, [createUserDataBackup]); const uploadUserKeys = React.useCallback(async () => { let message;