Skip to content

Commit 3a7560a

Browse files
Fix(extension): [LW-8723] make deletion of wallet more robust (#674)
* refactor(extension): make wallet cleanup more robust * fix(extension): refresh wallet after deleting to reset all stores * refactor(extension): refactor cleanup of storage when deleting wallet * fix(extension): fix keys whitelist * fix(extension): fix more keys * fix(extension): fix wallet manager unit tests * test(extension): fix background service unit test * fix(extension): key iteration in clearLocalStorage --------- Co-authored-by: przemyslaw.wlodek <przem.wlodek.github@gmail.com>
1 parent 1a14340 commit 3a7560a

File tree

21 files changed

+123
-69
lines changed

21 files changed

+123
-69
lines changed

apps/browser-extension-wallet/src/components/ResetDataError/ResetDataError.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ export const ResetDataError = ({
2222
buttonLabel
2323
}: ResetDataErrorProps): React.ReactElement => {
2424
const { deleteWallet } = useWalletManager();
25-
const { walletManagerUi } = useWalletStore();
25+
const { walletManagerUi, setDeletingWallet } = useWalletStore();
2626
const { theme } = useTheme();
2727
const backgroundService = useBackgroundServiceAPIContext();
2828

2929
const Layout = appMode === 'browser' ? WalletSetupLayout : React.Fragment;
3030

3131
const resetData = async () => {
32-
if (walletManagerUi) await deleteWallet();
32+
if (walletManagerUi) {
33+
setDeletingWallet(true);
34+
await deleteWallet();
35+
}
3336
window.localStorage.clear();
3437
window.localStorage.setItem('mode', theme.name);
3538
await backgroundService.resetStorage();

apps/browser-extension-wallet/src/features/unlock-wallet/components/UnlockWalletContainer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface UnlockWalletContainerProps {
1919
export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContainerProps): React.ReactElement => {
2020
const analytics = useAnalyticsContext();
2121
const { unlockWallet, lockWallet, deleteWallet } = useWalletManager();
22-
const { environmentName, setKeyAgentData } = useWalletStore();
22+
const { environmentName, setKeyAgentData, setDeletingWallet } = useWalletStore();
2323
const backgroundService = useBackgroundServiceAPIContext();
2424

2525
const [isVerifyingPassword, setIsVerifyingPassword] = useState(false);
@@ -75,8 +75,10 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine
7575
const onForgotPasswordClick = async (): Promise<void> => {
7676
await analytics.sendEventToPostHog(PostHogAction.UnlockWalletForgotPasswordProceedClick);
7777
saveValueInLocalStorage({ key: 'isForgotPasswordFlow', value: true });
78+
setDeletingWallet(true);
7879
await deleteWallet(true);
7980
await backgroundService.handleOpenBrowser({ section: BrowserViewSections.FORGOT_PASSWORD });
81+
setDeletingWallet(false);
8082
};
8183

8284
useKeyboardShortcut(['Enter'], onUnlock);

apps/browser-extension-wallet/src/hooks/__tests__/useWalletManager.test.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('Testing useWalletManager hook', () => {
162162

163163
await lockWallet();
164164
expect(lockWallet).toBeDefined();
165-
expect(clearBackgroundStorage).toBeCalledWith(['keyAgentsByChain']);
165+
expect(clearBackgroundStorage).toBeCalledWith({ keys: ['keyAgentsByChain'] });
166166
expect(deleteFromLocalStorage).toBeCalledWith('keyAgentData');
167167
expect(setCardanoWallet).toBeCalledWith();
168168
expect(setKeyAgentData).toBeCalledWith();
@@ -728,8 +728,8 @@ describe('Testing useWalletManager hook', () => {
728728
const shutdownWalletMocked = jest.fn();
729729
mockShutdownWallet.mockImplementation(shutdownWalletMocked);
730730

731-
const deleteFromLocalStorage = jest.fn();
732-
jest.spyOn(localStorage, 'deleteFromLocalStorage').mockImplementation(deleteFromLocalStorage);
731+
const clearLocalStorage = jest.fn();
732+
jest.spyOn(localStorage, 'clearLocalStorage').mockImplementation(clearLocalStorage);
733733

734734
const resetWalletLock = jest.fn();
735735
const setCardanoWallet = jest.fn();
@@ -757,12 +757,20 @@ describe('Testing useWalletManager hook', () => {
757757
expect(deleteWallet).toBeDefined();
758758
await deleteWallet();
759759
expect(shutdownWalletMocked).toBeCalledWith(walletManagerUi);
760-
expect(deleteFromLocalStorage.mock.calls[0]).toEqual(['appSettings']);
761-
expect(deleteFromLocalStorage.mock.calls[1]).toEqual(['showDappBetaModal']);
762-
expect(deleteFromLocalStorage.mock.calls[2]).toEqual(['lastStaking']);
763-
expect(deleteFromLocalStorage.mock.calls[3]).toEqual(['userInfo']);
764-
expect(deleteFromLocalStorage.mock.calls[4]).toEqual(['keyAgentData']);
765-
expect(clearBackgroundStorage).toBeCalledWith(['message', 'mnemonic', 'keyAgentsByChain']);
760+
expect(clearLocalStorage).toBeCalledWith({
761+
except: [
762+
'currency',
763+
'lock',
764+
'mode',
765+
'hideBalance',
766+
'analyticsAccepted',
767+
'isForgotPasswordFlow',
768+
'multidelegationFirstVisit'
769+
]
770+
});
771+
expect(clearBackgroundStorage).toBeCalledWith({
772+
except: ['fiatPrices', 'userId', 'usePersistentUserId', 'experimentsConfiguration']
773+
});
766774
expect(resetWalletLock).toBeCalledWith();
767775
expect(setCardanoWallet).toBeCalledWith();
768776
expect(setKeyAgentData).toBeCalledWith();

apps/browser-extension-wallet/src/hooks/useWalletManager.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ import { useCardanoWalletManagerContext } from '@providers/CardanoWalletManager'
77
import { useAppSettingsContext } from '@providers/AppSettings';
88
import { useBackgroundServiceAPIContext } from '@providers/BackgroundServiceAPI';
99
import { AddressBookSchema, addressBookSchema, NftFoldersSchema, nftFoldersSchema, useDbState } from '@src/lib/storage';
10-
import { deleteFromLocalStorage, getValueFromLocalStorage, saveValueInLocalStorage } from '@src/utils/local-storage';
10+
import {
11+
deleteFromLocalStorage,
12+
getValueFromLocalStorage,
13+
clearLocalStorage,
14+
saveValueInLocalStorage
15+
} from '@src/utils/local-storage';
1116
import { config } from '@src/config';
1217
import { getWalletFromStorage } from '@src/utils/get-wallet-from-storage';
1318
import { getUserIdService } from '@providers/AnalyticsProvider/getUserIdService';
1419
import { ENHANCED_ANALYTICS_OPT_IN_STATUS_LS_KEY } from '@providers/AnalyticsProvider/matomo/config';
20+
import { ILocalStorage } from '@src/types';
1521

1622
const { AVAILABLE_CHAINS, CHAIN } = config();
1723

@@ -145,7 +151,7 @@ export const useWalletManager = (): UseWalletManager => {
145151
const lockWallet = useCallback(async (): Promise<void> => {
146152
if (!walletLock) return;
147153
// Deletes key agent data from storage and clears states
148-
await backgroundService.clearBackgroundStorage(['keyAgentsByChain']);
154+
await backgroundService.clearBackgroundStorage({ keys: ['keyAgentsByChain'] });
149155
deleteFromLocalStorage('keyAgentData');
150156

151157
setKeyAgentData();
@@ -296,35 +302,49 @@ export const useWalletManager = (): UseWalletManager => {
296302
const deleteWallet = useCallback(
297303
async (isForgotPasswordFlow = false): Promise<void> => {
298304
await Wallet.shutdownWallet(walletManagerUi);
299-
deleteFromLocalStorage('appSettings');
300-
deleteFromLocalStorage('showDappBetaModal');
301-
deleteFromLocalStorage('lastStaking');
302-
deleteFromLocalStorage('userInfo');
303-
deleteFromLocalStorage('keyAgentData');
304-
await backgroundService.clearBackgroundStorage(['message', 'mnemonic', 'keyAgentsByChain']);
305305
setKeyAgentData();
306306
resetWalletLock();
307307
setCardanoWallet();
308+
309+
await backgroundService.clearBackgroundStorage({
310+
except: ['fiatPrices', 'userId', 'usePersistentUserId', 'experimentsConfiguration']
311+
});
312+
313+
const commonLocalStorageKeysToKeep: (keyof ILocalStorage)[] = [
314+
'currency',
315+
'lock',
316+
'mode',
317+
'hideBalance',
318+
'analyticsAccepted',
319+
'isForgotPasswordFlow',
320+
'multidelegationFirstVisit'
321+
];
322+
308323
setCurrentChain(CHAIN);
309324

310-
if (!isForgotPasswordFlow) {
311-
deleteFromLocalStorage('wallet');
312-
deleteFromLocalStorage(ENHANCED_ANALYTICS_OPT_IN_STATUS_LS_KEY);
313-
await userIdService.clearId();
314-
clearAddressBook();
315-
clearNftsFolders();
325+
if (isForgotPasswordFlow) {
326+
const additionalKeysToKeep: (keyof ILocalStorage)[] = ['wallet', ENHANCED_ANALYTICS_OPT_IN_STATUS_LS_KEY];
327+
clearLocalStorage({
328+
except: [...commonLocalStorageKeysToKeep, ...additionalKeysToKeep]
329+
});
330+
return;
316331
}
332+
333+
clearLocalStorage({ except: commonLocalStorageKeysToKeep });
334+
await userIdService.clearId();
335+
clearAddressBook();
336+
clearNftsFolders();
317337
},
318338
[
319339
walletManagerUi,
320-
backgroundService,
321-
userIdService,
322-
clearAddressBook,
323-
clearNftsFolders,
324340
setKeyAgentData,
325341
resetWalletLock,
326342
setCardanoWallet,
327-
setCurrentChain
343+
backgroundService,
344+
setCurrentChain,
345+
userIdService,
346+
clearAddressBook,
347+
clearNftsFolders
328348
]
329349
);
330350

apps/browser-extension-wallet/src/lib/scripts/background/services/userIdService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ describe('userIdService', () => {
112112
await userIdService.clearId();
113113
const newUserId = await userIdService.getUserId(1);
114114

115-
expect(clearStorageMock).toHaveBeenCalledWith(expect.arrayContaining(['userId', 'usePersistentUserId']));
115+
expect(clearStorageMock).toHaveBeenCalledWith({ keys: ['userId', 'usePersistentUserId'] });
116116
expect(previousUserId).not.toEqual(newUserId);
117117
const subscription = userIdService.userTrackingType$.subscribe((trackingType) => {
118118
expect(trackingType).toEqual(UserTrackingType.Basic);

apps/browser-extension-wallet/src/lib/scripts/background/services/userIdService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class UserIdService implements UserIdServiceInterface {
104104
this.walletBasedUserId = undefined;
105105
this.userTrackingType$.next(UserTrackingType.Basic);
106106
this.clearSessionTimeout();
107-
await this.clearStorage(['userId', 'usePersistentUserId']);
107+
await this.clearStorage({ keys: ['userId', 'usePersistentUserId'] });
108108
}
109109

110110
async makePersistent(): Promise<void> {

apps/browser-extension-wallet/src/lib/scripts/background/util.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,33 @@ export const getADAPriceFromBackgroundStorage = async (): Promise<BackgroundStor
3737
/**
3838
* Deletes the specified `keys` from the background storage.
3939
*
40-
* If no `keys` are passed then **ALL** of it is cleared.
40+
* If no `options` are passed then **ALL** of it is cleared.
4141
*
42-
* @param keys Optional. List of keys to delete from storage
42+
* @param options Optional. List of keys to either delete or remove from storage
4343
*/
44-
export const clearBackgroundStorage = async (keys?: BackgroundStorageKeys[]): Promise<void> => {
45-
if (!keys?.length) {
44+
type ClearBackgroundStorageOptions =
45+
| {
46+
keys: BackgroundStorageKeys[];
47+
except?: never;
48+
}
49+
| {
50+
keys?: never;
51+
except: BackgroundStorageKeys[];
52+
};
53+
export const clearBackgroundStorage = async (options?: ClearBackgroundStorageOptions): Promise<void> => {
54+
if (!options) {
4655
await webStorage.local.remove('BACKGROUND_STORAGE');
4756
return;
4857
}
4958
const backgroundStorage = await getBackgroundStorage();
50-
51-
for (const key of keys) delete backgroundStorage?.[key];
59+
for (const key in backgroundStorage) {
60+
if (options.keys && options.keys.includes(key as BackgroundStorageKeys)) {
61+
delete backgroundStorage[key as BackgroundStorageKeys];
62+
}
63+
if (options.except && options.except.includes(key as BackgroundStorageKeys)) {
64+
delete backgroundStorage[key as BackgroundStorageKeys];
65+
}
66+
}
5267
await webStorage.local.set({ BACKGROUND_STORAGE: backgroundStorage ?? {} });
5368
};
5469

apps/browser-extension-wallet/src/lib/scripts/migrations/versions/v0_6_0.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export const v0_6_0: Migration = {
129129
removeItemFromLocalStorage('wallet_tmp');
130130
removeItemFromLocalStorage('keyAgentData_tmp');
131131
removeItemFromLocalStorage('lock_tmp');
132-
await clearBackgroundStorage(['keyAgentsByChain_tmp' as BackgroundStorageKeys]);
132+
await clearBackgroundStorage({ keys: ['keyAgentsByChain_tmp' as BackgroundStorageKeys] });
133133
throw error;
134134
}
135135
},
@@ -214,13 +214,13 @@ export const v0_6_0: Migration = {
214214
if (tmpBackgroundStorage?.keyAgentsByChain_tmp) {
215215
await setBackgroundStorage({ keyAgentsByChain: tmpBackgroundStorage.keyAgentsByChain_tmp });
216216
}
217-
await clearBackgroundStorage(['walletName' as BackgroundStorageKeys]);
217+
await clearBackgroundStorage({ keys: ['walletName' as BackgroundStorageKeys] });
218218
// Delete temporary storage
219219
removeItemFromLocalStorage('appSettings_tmp');
220220
removeItemFromLocalStorage('wallet_tmp');
221221
removeItemFromLocalStorage('keyAgentData_tmp');
222222
removeItemFromLocalStorage('lock_tmp');
223-
await clearBackgroundStorage(['keyAgentsByChain_tmp' as BackgroundStorageKeys]);
223+
await clearBackgroundStorage({ keys: ['keyAgentsByChain_tmp' as BackgroundStorageKeys] });
224224
},
225225
rollback: async () => {
226226
console.info(`Rollback migrated data for ${MIGRATION_VERSION} upgrade`);
@@ -229,15 +229,15 @@ export const v0_6_0: Migration = {
229229
if (oldWalletInfo) setItemInLocalStorage('wallet', oldWalletInfo);
230230
if (oldLock) setItemInLocalStorage('lock', oldLock);
231231
removeItemFromLocalStorage('keyAgentData');
232-
await clearBackgroundStorage(['keyAgentsByChain']);
232+
await clearBackgroundStorage({ keys: ['keyAgentsByChain'] });
233233
if (backgroundStorage?.walletName)
234234
await setBackgroundStorage({ walletName: backgroundStorage.walletName } as BackgroundStorage);
235235
// Delete any temporary storage that may have been created
236236
removeItemFromLocalStorage('appSettings_tmp');
237237
removeItemFromLocalStorage('wallet_tmp');
238238
removeItemFromLocalStorage('keyAgentData_tmp');
239239
removeItemFromLocalStorage('lock_tmp');
240-
await clearBackgroundStorage(['keyAgentsByChain_tmp' as BackgroundStorageKeys]);
240+
await clearBackgroundStorage({ keys: ['keyAgentsByChain_tmp' as BackgroundStorageKeys] });
241241
}
242242
};
243243
}

apps/browser-extension-wallet/src/lib/scripts/types/background-service.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { BehaviorSubject, Subject } from 'rxjs';
22
import { themes } from '@providers/ThemeProvider';
3-
import { BackgroundStorage, BackgroundStorageKeys, MigrationState } from './storage';
3+
import { BackgroundStorage, MigrationState } from './storage';
44
import { CoinPrices } from './prices';
5+
import type { clearBackgroundStorage } from '../background/util';
56

67
export enum BaseChannels {
78
BACKGROUND_ACTIONS = 'background-actions'
@@ -60,14 +61,7 @@ export type BackgroundService = {
6061
handleChangeTheme: (data: ChangeThemeData) => void;
6162
setBackgroundStorage: (data: BackgroundStorage) => Promise<void>;
6263
getBackgroundStorage: () => Promise<BackgroundStorage>;
63-
/**
64-
* Deletes the specified `keys` from the background storage.
65-
*
66-
* If no `keys` are passed then **ALL** of it is cleared.
67-
*
68-
* @param keys Optional. List of keys to delete from storage
69-
*/
70-
clearBackgroundStorage: (keys?: BackgroundStorageKeys[]) => Promise<void>;
64+
clearBackgroundStorage: typeof clearBackgroundStorage;
7165
getWalletPassword: () => Uint8Array;
7266
setWalletPassword: (password?: Uint8Array) => void;
7367
resetStorage: () => Promise<void>;

apps/browser-extension-wallet/src/stores/slices/wallet-info-slice.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ export const walletInfoSlice: SliceCreator<WalletInfoSlice & BlockchainProviderS
2828
set({ currentChain: Wallet.Cardano.ChainIds[chain], environmentName: chain });
2929
get().setBlockchainProvider(chain);
3030
},
31-
getKeyAgentType: () => get()?.cardanoWallet?.keyAgent.serializableData.__typename
31+
getKeyAgentType: () => get()?.cardanoWallet?.keyAgent.serializableData.__typename,
32+
setDeletingWallet: (deletingWallet: boolean) => set({ deletingWallet })
3233
});

0 commit comments

Comments
 (0)