diff --git a/web/redux/create-async-migrate.js b/web/redux/create-async-migrate.js new file mode 100644 --- /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 --- a/web/root.js +++ b/web/root.js @@ -5,7 +5,7 @@ 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'; @@ -17,13 +17,14 @@ 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 @@ -50,7 +51,7 @@ 'notifPermissionAlertInfo', 'commServicesAccessToken', ], - migrate: (createMigrate(migrations, { debug: isDev }): any), + migrate: (createAsyncMigrate(migrations, { debug: isDev }): any), version: 1, };