diff --git a/web/redux/create-async-migrate.js b/web/redux/create-async-migrate.js index ecf22681f..65ac24bc2 100644 --- a/web/redux/create-async-migrate.js +++ b/web/redux/create-async-migrate.js @@ -1,74 +1,92 @@ // @flow +import { getStoredState, purgeStoredState } from 'redux-persist'; import { DEFAULT_VERSION } from 'redux-persist/es/constants.js'; +import storage from 'redux-persist/es/storage/index.js'; import type { PersistState } from 'redux-persist/es/types.js'; +import { databaseModule } from '../database/database-module-provider.js'; + type MigrationManifest = { +[number | string]: (PersistedState) => Promise, }; type PersistedState = { +_persist: PersistState, ... } | void; type ConfigType = { +debug: boolean, }; function createAsyncMigrate( migrations: MigrationManifest, config: ConfigType, ): (state: PersistedState, currentVersion: number) => Promise { const debug = process.env.NODE_ENV !== 'production' && !!config?.debug; return async function ( state: PersistedState, currentVersion: number, ): Promise { if (!state) { + const isSupported = await databaseModule.isDatabaseSupported(); + if (!isSupported) { + if (debug) { + console.log('redux-persist: no inbound state, skipping migration'); + } + return undefined; + } + + const oldStorage = await getStoredState({ storage, key: 'root' }); + if (!oldStorage) { + return undefined; + } + + state = oldStorage; + purgeStoredState({ storage, key: 'root' }); if (debug) { - console.log('redux-persist: no inbound state, skipping migration'); + console.log('redux-persist: migrating state to SQLite storage'); } - return undefined; } const inboundVersion: number = state?._persist?.version ?? DEFAULT_VERSION; if (inboundVersion === currentVersion) { if (debug) { console.log('redux-persist: versions match, noop migration'); } return state; } if (inboundVersion > currentVersion) { if (debug) { console.error('redux-persist: downgrading version is not supported'); } return state; } const newMigrationKeys = Object.keys(migrations) .map(ver => parseInt(ver)) .filter(key => currentVersion >= key && key > inboundVersion); const sortedMigrationKeys = newMigrationKeys.sort((a, b) => a - b); if (debug) { console.log('redux-persist: migrationKeys', sortedMigrationKeys); } let migratedState: PersistedState = state; for (const versionKey of sortedMigrationKeys) { if (debug) { console.log( 'redux-persist: running migration for versionKey', versionKey, ); } if (versionKey) { migratedState = await migrations[versionKey](migratedState); } } return migratedState; }; } export { createAsyncMigrate }; diff --git a/web/root.js b/web/root.js index ea1d8712e..815fb3978 100644 --- a/web/root.js +++ b/web/root.js @@ -1,114 +1,114 @@ // @flow import * as React from 'react'; import { Provider } from 'react-redux'; import { Router, Route } from 'react-router'; import { createStore, applyMiddleware, type Store } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction.js'; import { persistReducer, persistStore } from 'redux-persist'; import { PersistGate } from 'redux-persist/es/integration/react.js'; -import storage from 'redux-persist/es/storage/index.js'; import thunk from 'redux-thunk'; import { reduxLoggerMiddleware } from 'lib/utils/action-logger.js'; import { isDev } from 'lib/utils/dev-utils.js'; import App from './app.react.js'; import { databaseModule } from './database/database-module-provider.js'; import { SQLiteDataHandler } from './database/sqlite-data-handler.js'; import { isSQLiteSupported } from './database/utils/db-utils.js'; import ErrorBoundary from './error-boundary.react.js'; import Loading from './loading.react.js'; +import commReduxStorageEngine from './redux/comm-redux-storage-engine.js'; import { createAsyncMigrate } from './redux/create-async-migrate.js'; import { reducer } from './redux/redux-setup.js'; import type { AppState, Action } from './redux/redux-setup.js'; import history from './router-history.js'; import Socket from './socket.react.js'; import { workerRequestMessageTypes } from './types/worker-types.js'; const initiallyLoggedInUserID = preloadedState.currentUserInfo?.anonymous ? undefined : preloadedState.currentUserInfo?.id; const isDatabaseSupported = isSQLiteSupported(initiallyLoggedInUserID); const migrations = { [1]: async state => { const { primaryIdentityPublicKey, ...stateWithoutPrimaryIdentityPublicKey } = state; return { ...stateWithoutPrimaryIdentityPublicKey, cryptoStore: { primaryAccount: null, primaryIdentityKeys: null, notificationAccount: null, notificationIdentityKeys: null, }, }; }, [2]: async state => { if (!isDatabaseSupported) { return state; } const { drafts } = state.draftStore; const draftStoreOperations = []; for (const key in drafts) { const text = drafts[key]; draftStoreOperations.push({ type: 'update', payload: { key, text }, }); } await databaseModule.schedule({ type: workerRequestMessageTypes.PROCESS_STORE_OPERATIONS, storeOperations: { draftStoreOperations }, }); return state; }, }; const persistWhitelist = [ 'enabledApps', 'deviceID', 'cryptoStore', 'notifPermissionAlertInfo', 'commServicesAccessToken', ]; const persistConfig = { key: 'root', - storage, + storage: commReduxStorageEngine, whitelist: isDatabaseSupported ? persistWhitelist : [...persistWhitelist, 'draftStore'], migrate: (createAsyncMigrate(migrations, { debug: isDev }): any), version: 2, }; declare var preloadedState: AppState; const persistedReducer = persistReducer(persistConfig, reducer); const store: Store = createStore( persistedReducer, preloadedState, composeWithDevTools({})(applyMiddleware(thunk, reduxLoggerMiddleware)), ); const persistor = persistStore(store); const RootProvider = (): React.Node => ( }> ); export default RootProvider;