From bd77be5407d932a67d75ef3004edceb1e580ecc7 Mon Sep 17 00:00:00 2001 From: alter-eggo Date: Tue, 31 Oct 2023 15:05:25 +0400 Subject: [PATCH] fix: migration redux persist --- src/app/store/index.ts | 4 +- .../software-keys/software-key.selectors.ts | 2 +- .../store/software-keys/software-key.slice.ts | 2 +- src/shared/storage/redux-pesist.ts | 52 ++++++++++++++++++- tests/page-object-models/onboarding.page.ts | 4 +- tests/specs/onboarding/onboarding.spec.ts | 10 ++-- .../store-migrations/store-migrations.spec.ts | 11 ++-- 7 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/app/store/index.ts b/src/app/store/index.ts index fd2cb21eeea..b39675a7f45 100644 --- a/src/app/store/index.ts +++ b/src/app/store/index.ts @@ -42,7 +42,7 @@ export interface RootState { }; ordinals: ReturnType; inMemoryKeys: ReturnType; - 'software-keys': ReturnType; + softwareKeys: ReturnType; networks: ReturnType; submittedTransactions: ReturnType; settings: ReturnType; @@ -60,7 +60,7 @@ const appReducer = combineReducers({ }), ordinals: ordinalsSlice.reducer, inMemoryKeys: inMemoryKeySlice.reducer, - 'software-keys': keySlice.reducer, + softwareKeys: keySlice.reducer, networks: networksSlice.reducer, submittedTransactions: submittedTransactionsSlice.reducer, settings: settingsSlice.reducer, diff --git a/src/app/store/software-keys/software-key.selectors.ts b/src/app/store/software-keys/software-key.selectors.ts index fbc9238d64a..47d43ba6141 100644 --- a/src/app/store/software-keys/software-key.selectors.ts +++ b/src/app/store/software-keys/software-key.selectors.ts @@ -10,7 +10,7 @@ import { RootState } from '@app/store'; import { selectStacksChain } from '../chains/stx-chain.selectors'; -const selectKeysSlice = (state: RootState) => state['software-keys']; +const selectKeysSlice = (state: RootState) => state['softwareKeys']; export const selectDefaultSoftwareKey = createSelector( selectKeysSlice, diff --git a/src/app/store/software-keys/software-key.slice.ts b/src/app/store/software-keys/software-key.slice.ts index 841ea1e139b..efc7419b833 100644 --- a/src/app/store/software-keys/software-key.slice.ts +++ b/src/app/store/software-keys/software-key.slice.ts @@ -16,7 +16,7 @@ const keyAdapter = createEntityAdapter(); export const initialKeysState = keyAdapter.getInitialState(); export const keySlice = createSlice({ - name: 'software-keys', + name: 'softwareKeys', initialState: migrateVaultReducerStoreToNewStateStructure(initialKeysState), reducers: { createSoftwareWalletComplete(state, action: PayloadAction) { diff --git a/src/shared/storage/redux-pesist.ts b/src/shared/storage/redux-pesist.ts index d45e2711b5c..39341b7b415 100644 --- a/src/shared/storage/redux-pesist.ts +++ b/src/shared/storage/redux-pesist.ts @@ -1,4 +1,5 @@ import { PersistConfig, createMigrate, getStoredState } from 'redux-persist'; +import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; import type { RootState } from '@app/store'; @@ -76,7 +77,7 @@ const legacyPersistConfig: PersistConfig = { version: 1, storage, serialize: true, - whitelist: ['analytics', 'chains', 'software-keys', 'networks', 'onboarding', 'settings'], + whitelist: ['analytics', 'chains', 'keys', 'networks', 'onboarding', 'settings'], }; async function migrateToUsingNoSerialization() { @@ -96,11 +97,54 @@ async function migrateToUsingNoSerialization() { return storage; } +async function migrateToRenameKeysStoreModule(state: Promise) { + const resolvedState = await Promise.resolve(state); + + const newStore = JSON.parse( + JSON.stringify({ + ...resolvedState, + softwareKeys: resolvedState.keys, + ledger: { + ...resolvedState.ledger, + }, + }) + ); + + // add stacks ledger keys to new place + if (resolvedState.keys.entities.default.type === 'ledger') { + newStore.ledger = { ...resolvedState.ledger, stacks: resolvedState.keys }; + } + + // add default bitcoin ledger state + if (!newStore.ledger.bitcoin) { + newStore.ledger.bitcoin = { + ids: [], + entities: {}, + targetId: '', + }; + } + + // add default stacks ledger state + if (!newStore.ledger.stacks) { + newStore.ledger.stacks = { + ids: [], + entities: {}, + targetId: '', + }; + } + + // remove old keys store + Reflect.deleteProperty(newStore, 'keys'); + + return newStore; +} + interface UntypedDeserializeOption { deserialize?: boolean; } export const persistConfig: PersistConfig & UntypedDeserializeOption = { key: 'root', + stateReconciler: autoMergeLevel2, version: 1, storage, serialize: false, @@ -108,13 +152,17 @@ export const persistConfig: PersistConfig & UntypedDeserializeOption 0: async () => { return migrateToUsingNoSerialization(); }, + 1: async (state: any) => { + return migrateToRenameKeysStoreModule(state); + }, + debug: true, } as any), deserialize: false, whitelist: [ 'analytics', 'chains', 'ordinals', - 'software-keys', + 'softwareKeys', 'ledger', 'networks', 'onboarding', diff --git a/tests/page-object-models/onboarding.page.ts b/tests/page-object-models/onboarding.page.ts index ff99ad5a65e..51816bf17ac 100644 --- a/tests/page-object-models/onboarding.page.ts +++ b/tests/page-object-models/onboarding.page.ts @@ -11,7 +11,7 @@ const TEST_ACCOUNT_SECRET_KEY = process.env.TEST_ACCOUNT_SECRET_KEY ?? ''; export const testSoftwareAccountDefaultWalletState = { analytics: { hasStxDeposits: { '1': true, '2147483648': true } }, chains: { stx: { default: { highestAccountIndex: 1, currentAccountIndex: 0 } } }, - 'software-keys': { + softwareKeys: { ids: ['default'], entities: { default: { @@ -53,7 +53,7 @@ const testLedgerAccountDefaultWalletState = { _persist: { rehydrated: true, version: 1 }, analytics: { hasStxDeposits: { '1': false, '2147483648': true } }, chains: { stx: { default: { currentAccountIndex: 0, highestAccountIndex: 0 } } }, - 'software-keys': { + softwareKeys: { entities: {}, ids: [], }, diff --git a/tests/specs/onboarding/onboarding.spec.ts b/tests/specs/onboarding/onboarding.spec.ts index 383f6c8f543..0dd20301c1f 100644 --- a/tests/specs/onboarding/onboarding.spec.ts +++ b/tests/specs/onboarding/onboarding.spec.ts @@ -24,11 +24,11 @@ test.describe('Onboarding an existing user', () => { ); // Deleting values that are known to differ at random - delete (walletState as any)['software-keys'].entities.default.encryptedSecretKey; - delete (walletState as any)['software-keys'].entities.default.salt; - delete (testSoftwareAccountDefaultWalletState as any)['software-keys'].entities.default + delete (walletState as any).softwareKeys.entities.default.encryptedSecretKey; + delete (walletState as any).softwareKeys.entities.default.salt; + delete (testSoftwareAccountDefaultWalletState as any).softwareKeys.entities.default .encryptedSecretKey; - delete (testSoftwareAccountDefaultWalletState as any)['software-keys'].entities.default.salt; + delete (testSoftwareAccountDefaultWalletState as any).softwareKeys.entities.default.salt; test.expect(walletState).toEqual(testSoftwareAccountDefaultWalletState); }); @@ -42,7 +42,7 @@ test.describe('Onboarding an existing user', () => { // enter some invalid key const invalidKey = 'some incorrect data'; await onboardingPage.signInMnemonicKey(invalidKey); - const signInButton = await onboardingPage.page.getByTestId(OnboardingSelectors.SignInBtn); + const signInButton = onboardingPage.page.getByTestId(OnboardingSelectors.SignInBtn); const error = onboardingPage.page.getByText('Words 1 and 2 are incorrect or misspelled'); await test.expect(error).toBeVisible(); await test.expect(signInButton).toBeDisabled(); diff --git a/tests/specs/store-migrations/store-migrations.spec.ts b/tests/specs/store-migrations/store-migrations.spec.ts index 0fe3132184e..a159bec5787 100644 --- a/tests/specs/store-migrations/store-migrations.spec.ts +++ b/tests/specs/store-migrations/store-migrations.spec.ts @@ -8,9 +8,9 @@ test.describe('Store migrations', () => { await globalPage.page.waitForTimeout(1000); }); - test.describe('Migration 0 --> 1', () => { + test.describe('Migration 0 --> 2', () => { const previousSerializedState = - '{"analytics":"{\\"hasStxDeposits\\":{\\"1\\":true,\\"2147483648\\":true}}","chains":"{\\"stx\\":{\\"default\\":{\\"highestAccountIndex\\":16,\\"currentAccountIndex\\":0}}}","software-keys":"{\\"ids\\":[\\"default\\"],\\"entities\\":{\\"default\\":{\\"type\\":\\"software\\",\\"id\\":\\"default\\",\\"salt\\":\\"c4cccf33166051f7704cd877a2f03f93\\",\\"encryptedSecretKey\\":\\"b7f516798e7160eca15c50b62e588698937f8ecf3930efc42baa690ddc0c7a51b74e3e4b129859274ed272652bc47651c6b6effbddf4d72a3eb9d2ea657b64a833c9bdccb562e45d94f0cc1366154072f12d35290566a99a6f952cd234ca9259\\"}}}","networks":"{\\"ids\\":[],\\"entities\\":{},\\"currentNetworkId\\":\\"mainnet\\"}","onboarding":"{\\"hideSteps\\":true,\\"stepsStatus\\":{\\"Back up secret key\\":1,\\"Add some funds\\":0,\\"Explore apps\\":0,\\"Buy an NFT\\":0}}","settings":"{\\"userSelectedTheme\\":\\"system\\",\\"hasAllowedAnalytics\\":false,\\"dismissedMessages\\":[]}","_persist":"{\\"version\\":1,\\"rehydrated\\":true}"}'; + '{"analytics":"{\\"hasStxDeposits\\":{\\"1\\":true,\\"2147483648\\":true}}","chains":"{\\"stx\\":{\\"default\\":{\\"highestAccountIndex\\":16,\\"currentAccountIndex\\":0}}}","keys":"{\\"ids\\":[\\"default\\"],\\"entities\\":{\\"default\\":{\\"type\\":\\"software\\",\\"id\\":\\"default\\",\\"salt\\":\\"c4cccf33166051f7704cd877a2f03f93\\",\\"encryptedSecretKey\\":\\"b7f516798e7160eca15c50b62e588698937f8ecf3930efc42baa690ddc0c7a51b74e3e4b129859274ed272652bc47651c6b6effbddf4d72a3eb9d2ea657b64a833c9bdccb562e45d94f0cc1366154072f12d35290566a99a6f952cd234ca9259\\"}}}","networks":"{\\"ids\\":[],\\"entities\\":{},\\"currentNetworkId\\":\\"mainnet\\"}","onboarding":"{\\"hideSteps\\":true,\\"stepsStatus\\":{\\"Back up secret key\\":1,\\"Add some funds\\":0,\\"Explore apps\\":0,\\"Buy an NFT\\":0}}","settings":"{\\"userSelectedTheme\\":\\"system\\",\\"hasAllowedAnalytics\\":false,\\"dismissedMessages\\":[]}","_persist":"{\\"version\\":1,\\"rehydrated\\":true}"}'; test('that the app detects old store format', async ({ extensionId, globalPage }) => { const { page } = globalPage; @@ -43,17 +43,18 @@ test.describe('Store migrations', () => { chrome.storage.local.get(['persist:root'], state => resolve(state['persist:root'])) ) ); - // Assert that old values are present in unserialized format test - .expect(result['software-keys'].entities['default']?.encryptedSecretKey) + .expect(result.softwareKeys.entities['default']?.encryptedSecretKey) .toEqual( 'b7f516798e7160eca15c50b62e588698937f8ecf3930efc42baa690ddc0c7a51b74e3e4b129859274ed272652bc47651c6b6effbddf4d72a3eb9d2ea657b64a833c9bdccb562e45d94f0cc1366154072f12d35290566a99a6f952cd234ca9259' ); test - .expect(result['software-keys'].entities['default']?.salt) + .expect(result.softwareKeys.entities['default']?.salt) .toEqual('c4cccf33166051f7704cd877a2f03f93'); + + test.expect(result.ledger.stacks).toBeDefined(); }); }); });