diff --git a/lib/backup/use-user-data-utils.js b/lib/backup/use-user-data-utils.js new file mode 100644 --- /dev/null +++ b/lib/backup/use-user-data-utils.js @@ -0,0 +1,61 @@ +// @flow + +import { + type AddLogCallback, + logTypes, +} from '../components/debug-logs-context.js'; +import { databaseIdentifier } from '../types/database-identifier-types.js'; +import { syncedMetadataNames } from '../types/synced-metadata-types.js'; +import { getConfig } from '../utils/config.js'; + +export type StoreAndDatabaseVersions = { + +mainDatabaseVersion: number, + +restoredDatabaseVersion: number, + +mainStoreVersion: number, + +restoredStoreVersion: number, +}; + +async function getStoreAndDatabaseVersions( + addLog: AddLogCallback, +): Promise { + const { sqliteAPI } = getConfig(); + + const [ + mainDatabaseVersion, + restoredDatabaseVersion, + restoredStoreVersionString, + ] = await Promise.all([ + sqliteAPI.getDatabaseVersion(databaseIdentifier.MAIN), + sqliteAPI.getDatabaseVersion(databaseIdentifier.RESTORED), + sqliteAPI.getSyncedMetadata( + syncedMetadataNames.STORE_VERSION, + databaseIdentifier.RESTORED, + ), + ]); + + const mainStoreVersion = getConfig().platformDetails.stateVersion; + + if (!mainStoreVersion || !restoredStoreVersionString) { + addLog( + 'User Data Restore', + `Error when restoring user data, main store version(${ + mainStoreVersion ?? 'undefined' + }) or restored store version (${ + restoredStoreVersionString ?? 'undefined' + }) are undefined`, + new Set([logTypes.BACKUP, logTypes.ERROR]), + ); + throw new Error('version_check_failed'); + } + + const restoredStoreVersion = parseInt(restoredStoreVersionString); + + return { + mainDatabaseVersion, + restoredDatabaseVersion, + mainStoreVersion, + restoredStoreVersion, + }; +} + +export { getStoreAndDatabaseVersions }; diff --git a/lib/backup/use-user-data-restore.js b/lib/backup/user-data-restore-context.js rename from lib/backup/use-user-data-restore.js rename to lib/backup/user-data-restore-context.js --- a/lib/backup/use-user-data-restore.js +++ b/lib/backup/user-data-restore-context.js @@ -4,87 +4,48 @@ import * as React from 'react'; import { runRestoredBackupMigrations } from './restored-migrations.js'; +import { + getStoreAndDatabaseVersions, + type StoreAndDatabaseVersions, +} from './use-user-data-utils.js'; import { restoreUserDataStepActionTypes } from '../actions/backup-actions.js'; import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js'; import { generateOpsToEstablishHoldersOnDevice } from '../actions/holder-actions.js'; -import { - type AddLogCallback, - logTypes, - useDebugLogs, -} from '../components/debug-logs-context.js'; -import { - restoreUserDataSteps, - type RestoreUserDataStep, -} from '../types/backup-types.js'; +import { logTypes, useDebugLogs } from '../components/debug-logs-context.js'; +import type { RestoreUserDataStep } from '../types/backup-types.js'; +import { restoreUserDataSteps } from '../types/backup-types.js'; import { databaseIdentifier } from '../types/database-identifier-types.js'; import type { IdentityAuthResult } from '../types/identity-service-types.js'; -import { syncedMetadataNames } from '../types/synced-metadata-types.js'; import type { QRAuthBackupData } from '../types/tunnelbroker/qr-code-auth-message-types.js'; import { getConfig } from '../utils/config.js'; import { BackupIsNewerError } from '../utils/errors.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; -import { useSelector, useDispatch } from '../utils/redux-utils.js'; +import { useDispatch, useSelector } from '../utils/redux-utils.js'; -type StoreAndDatabaseVersions = { - +mainDatabaseVersion: number, - +restoredDatabaseVersion: number, - +mainStoreVersion: number, - +restoredStoreVersion: number, +type UserDataRestoreContextType = { + +userDataRestore: ( + backupData: ?QRAuthBackupData, + identityAuthResult: ?IdentityAuthResult, + ) => Promise, + +isRestoring: boolean, }; -async function getStoreAndDatabaseVersions( - addLog: AddLogCallback, -): Promise { - const { sqliteAPI } = getConfig(); - - const [ - mainDatabaseVersion, - restoredDatabaseVersion, - restoredStoreVersionString, - ] = await Promise.all([ - sqliteAPI.getDatabaseVersion(databaseIdentifier.MAIN), - sqliteAPI.getDatabaseVersion(databaseIdentifier.RESTORED), - sqliteAPI.getSyncedMetadata( - syncedMetadataNames.STORE_VERSION, - databaseIdentifier.RESTORED, - ), - ]); - - const mainStoreVersion = getConfig().platformDetails.stateVersion; - - if (!mainStoreVersion || !restoredStoreVersionString) { - addLog( - 'User Data Restore', - `Error when restoring user data, main store version(${ - mainStoreVersion ?? 'undefined' - }) or restored store version (${ - restoredStoreVersionString ?? 'undefined' - }) are undefined`, - new Set([logTypes.BACKUP, logTypes.ERROR]), - ); - throw new Error('version_check_failed'); - } +const UserDataRestoreContext: React.Context = + React.createContext(null); - const restoredStoreVersion = parseInt(restoredStoreVersionString); - - return { - mainDatabaseVersion, - restoredDatabaseVersion, - mainStoreVersion, - restoredStoreVersion, - }; -} +type Props = { + +children: React.Node, +}; -function useUserDataRestore(): ( - backupData: ?QRAuthBackupData, - identityAuthResult: ?IdentityAuthResult, -) => Promise { +function UserDataRestoreProvider(props: Props): React.Node { + const { children } = props; + const restorePromiseRef = React.useRef>(null); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const { addLog } = useDebugLogs(); const restoreBackupState = useSelector(state => state.restoreBackupState); - return React.useCallback( + const executeRestoreUserData = React.useCallback( async ( backupData: ?QRAuthBackupData, identityAuthResult: ?IdentityAuthResult, @@ -294,6 +255,49 @@ }, [addLog, dispatch, dispatchActionPromise, restoreBackupState], ); + + const userDataRestore = React.useCallback( + async ( + backupData: ?QRAuthBackupData, + identityAuthResult: ?IdentityAuthResult, + ): Promise => { + if (restorePromiseRef.current) { + return restorePromiseRef.current; + } + + const promise = (async () => { + try { + await executeRestoreUserData(backupData, identityAuthResult); + } finally { + restorePromiseRef.current = null; + } + })(); + + restorePromiseRef.current = promise; + return promise; + }, + [executeRestoreUserData], + ); + + const contextValue: UserDataRestoreContextType = React.useMemo( + () => ({ + userDataRestore, + isRestoring: restorePromiseRef.current !== null, + }), + [userDataRestore], + ); + + return ( + + {children} + + ); +} + +function useUserDataRestoreContext(): UserDataRestoreContextType { + const userDataRestoreContext = React.useContext(UserDataRestoreContext); + invariant(userDataRestoreContext, 'userDataRestoreContext should be set'); + return userDataRestoreContext; } -export { useUserDataRestore, getStoreAndDatabaseVersions }; +export { UserDataRestoreProvider, useUserDataRestoreContext }; diff --git a/lib/components/secondary-device-qr-auth-context-provider.react.js b/lib/components/secondary-device-qr-auth-context-provider.react.js --- a/lib/components/secondary-device-qr-auth-context-provider.react.js +++ b/lib/components/secondary-device-qr-auth-context-provider.react.js @@ -4,7 +4,7 @@ import * as React from 'react'; import { logTypes, useDebugLogs } from './debug-logs-context.js'; -import { useUserDataRestore } from '../backup/use-user-data-restore.js'; +import { useUserDataRestoreContext } from '../backup/user-data-restore-context.js'; import { qrCodeLinkURL } from '../facts/links.js'; import { useSecondaryDeviceLogIn } from '../hooks/login-hooks.js'; import { uintArrayToHexString } from '../media/data-utils.js'; @@ -188,7 +188,7 @@ ]); const { addLog } = useDebugLogs(); - const userDataRestore = useUserDataRestore(); + const { userDataRestore } = useUserDataRestoreContext(); const restoreUserData = React.useCallback( async ( backupData: ?QRAuthBackupData, diff --git a/lib/handlers/restore-backup-handler.react.js b/lib/handlers/restore-backup-handler.react.js --- a/lib/handlers/restore-backup-handler.react.js +++ b/lib/handlers/restore-backup-handler.react.js @@ -2,7 +2,7 @@ import * as React from 'react'; -import { useUserDataRestore } from '../backup/use-user-data-restore.js'; +import { useUserDataRestoreContext } from '../backup/user-data-restore-context.js'; import { usePersistedStateLoaded } from '../selectors/app-state-selectors.js'; import { useSelector } from '../utils/redux-utils.js'; import { fullBackupSupport } from '../utils/services-utils.js'; @@ -12,7 +12,7 @@ // We want this handler to be executed only once const executed = React.useRef(false); const restoreBackupState = useSelector(state => state.restoreBackupState); - const userDataRestore = useUserDataRestore(); + const { userDataRestore } = useUserDataRestoreContext(); React.useEffect(() => { if (!fullBackupSupport || !persistedStateLoaded || executed.current) { diff --git a/lib/tunnelbroker/use-peer-to-peer-message-handler.js b/lib/tunnelbroker/use-peer-to-peer-message-handler.js --- a/lib/tunnelbroker/use-peer-to-peer-message-handler.js +++ b/lib/tunnelbroker/use-peer-to-peer-message-handler.js @@ -10,7 +10,7 @@ import { removePeerUsersActionType } from '../actions/aux-user-actions.js'; import { invalidateTunnelbrokerDeviceTokenActionType } from '../actions/tunnelbroker-actions.js'; import { logOutActionTypes, useBaseLogOut } from '../actions/user-actions.js'; -import { useUserDataRestore } from '../backup/use-user-data-restore.js'; +import { useUserDataRestoreContext } from '../backup/user-data-restore-context.js'; import { logTypes, type OlmDebugLog, @@ -96,7 +96,7 @@ const runDeviceListUpdate = useDeviceListUpdate(); const processDMOperation = useProcessDMOperation(); - const userDataRestore = useUserDataRestore(); + const { userDataRestore } = useUserDataRestoreContext(); const restoreBackupState = useSelector(state => state.restoreBackupState); const username = useSelector(state => state.currentUserInfo?.username); diff --git a/native/account/restore.js b/native/account/restore.js --- a/native/account/restore.js +++ b/native/account/restore.js @@ -7,7 +7,7 @@ restoreUserActionTypes, type RestoreProtocolResult, } from 'lib/actions/user-actions.js'; -import { useUserDataRestore } from 'lib/backup/use-user-data-restore.js'; +import { useUserDataRestoreContext } from 'lib/backup/user-data-restore-context.js'; import { logTypes, useDebugLogs } from 'lib/components/debug-logs-context.js'; import { useLogIn, @@ -208,7 +208,7 @@ ); const { addLog } = useDebugLogs(); - const userDataRestore = useUserDataRestore(); + const { userDataRestore } = useUserDataRestoreContext(); const restoreUserData = React.useCallback( async (identityAuthResult: ?IdentityAuthResult) => { if (!fullBackupSupport) { diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -23,6 +23,7 @@ import { Provider } from 'react-redux'; import { PersistGate as ReduxPersistGate } from 'redux-persist/es/integration/react.js'; +import { UserDataRestoreProvider } from 'lib/backup/user-data-restore-context.js'; import { ChatMentionContextProvider } from 'lib/components/chat-mention-provider.react.js'; import { DebugLogsContextProvider } from 'lib/components/debug-logs-context-provider.react.js'; import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js'; @@ -354,84 +355,86 @@ alchemyKey={alchemyKey} > - - - - - - - - - - - - - - - - - - - - - - {gated} - - - - - - - - - - - - - - - - {navigation} - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + {gated} + + + + + + + + + + + + + + + + {navigation} + + + + + + + + + + + + + + + + + + + diff --git a/web/app.react.js b/web/app.react.js --- a/web/app.react.js +++ b/web/app.react.js @@ -13,6 +13,7 @@ fetchEntriesActionTypes, updateCalendarQueryActionTypes, } from 'lib/actions/entry-actions.js'; +import { UserDataRestoreProvider } from 'lib/backup/user-data-restore-context.js'; import { ChatMentionContextProvider } from 'lib/components/chat-mention-provider.react.js'; import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js'; import { FarcasterChannelPrefetchHandler } from 'lib/components/farcaster-channel-prefetch-handler.react.js'; @@ -576,37 +577,41 @@ - - - - - - - - - - + + + + + + + + + + + +