diff --git a/web/redux/create-async-migrate.js b/web/redux/create-async-migrate.js new file mode 100644 index 000000000..ecf22681f --- /dev/null +++ b/web/redux/create-async-migrate.js @@ -0,0 +1,74 @@ +// @flow + +import { DEFAULT_VERSION } from 'redux-persist/es/constants.js'; +import type { PersistState } from 'redux-persist/es/types.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) { + if (debug) { + console.log('redux-persist: no inbound state, skipping migration'); + } + 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 67e25559a..c4da508be 100644 --- a/web/root.js +++ b/web/root.js @@ -1,80 +1,81 @@ // @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 { createMigrate, persistReducer, persistStore } from 'redux-persist'; +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 { SQLiteDataHandler } from './database/sqlite-data-handler.js'; import ErrorBoundary from './error-boundary.react.js'; import Loading from './loading.react.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'; const migrations = { - [1]: state => { + [1]: async state => { const { primaryIdentityPublicKey, ...stateWithoutPrimaryIdentityPublicKey } = state; return { ...stateWithoutPrimaryIdentityPublicKey, cryptoStore: { primaryAccount: null, primaryIdentityKeys: null, notificationAccount: null, notificationIdentityKeys: null, }, }; }, }; const persistConfig = { key: 'root', storage, whitelist: [ 'enabledApps', 'deviceID', 'draftStore', 'cryptoStore', 'notifPermissionAlertInfo', 'commServicesAccessToken', ], - migrate: (createMigrate(migrations, { debug: isDev }): any), + migrate: (createAsyncMigrate(migrations, { debug: isDev }): any), version: 1, }; 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;