Skip to content

Commit

Permalink
fix(suite-native): device state migration
Browse files Browse the repository at this point in the history
  • Loading branch information
martykan authored and matejkriz committed Oct 31, 2024
1 parent 580f3a9 commit 0bde39e
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 8 deletions.
2 changes: 1 addition & 1 deletion suite-common/wallet-core/src/device/deviceThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export const authorizeDeviceThunk = createThunk<
isDeviceAcquired(device) &&
// Should ignore device state serves as a variant to call "reauthorize" device. For example in passphrase mode
// mobile has retry button which starts passphrase flow on the same device instance to override device state.
(!device.state || shouldIgnoreDeviceState) &&
(!device.state?.staticSessionId || shouldIgnoreDeviceState) &&
device.mode === 'normal' &&
device.firmware !== 'required';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,28 @@ export const useHandleDeviceRequestsPassphrase = () => {

const handleRequestPassphrase = useCallback(() => {
// If the passphrase request was while verifying empty passphrase wallet, we handle it separately in the screen
if (!isVefifyingPassphraseOnEmptyWallet && !deviceState) {
if (!isVefifyingPassphraseOnEmptyWallet && !deviceState?.staticSessionId) {
navigation.navigate(RootStackRoutes.AuthorizeDeviceStack, {
screen: AuthorizeDeviceStackRoutes.PassphraseForm,
});
}

// Feature requests passphrase
if (!isVefifyingPassphraseOnEmptyWallet && !isCreatingNewWallet && deviceState) {
if (
!isVefifyingPassphraseOnEmptyWallet &&
!isCreatingNewWallet &&
deviceState?.staticSessionId
) {
navigation.navigate(RootStackRoutes.AuthorizeDeviceStack, {
screen: AuthorizeDeviceStackRoutes.PassphraseFeatureUnlockForm,
});
}
}, [deviceState, isCreatingNewWallet, isVefifyingPassphraseOnEmptyWallet, navigation]);
}, [
deviceState?.staticSessionId,
isCreatingNewWallet,
isVefifyingPassphraseOnEmptyWallet,
navigation,
]);

useEffect(() => {
if (deviceRequestedPassphrase) {
Expand Down
2 changes: 1 addition & 1 deletion suite-native/discovery/src/discoverySelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const selectCanRunDiscoveryForDevice = (
DiscoveryConfigSliceRootState,
) => {
const deviceState = selectDeviceState(state);
if (!deviceState) {
if (!deviceState?.staticSessionId) {
return false;
}

Expand Down
14 changes: 13 additions & 1 deletion suite-native/state/src/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
walletPersistTransform,
devicePersistTransform,
walletStopPersistTransform,
migrateDeviceState,
} from '@suite-native/storage';
import { prepareAnalyticsReducer } from '@suite-common/analytics';
import {
Expand Down Expand Up @@ -113,8 +114,19 @@ export const prepareRootReducers = async () => {
reducer: deviceReducer,
persistedKeys: ['devices'],
key: 'devices',
version: 1,
version: 2,
transforms: [devicePersistTransform],
migrations: {
2: oldState => {
if (!oldState.devices) return oldState;

const oldDevicesState: { devices: any } = { devices: oldState.devices };
const migratedDevices = migrateDeviceState(oldDevicesState.devices);
const migratedState = { ...oldState, devices: migratedDevices };

return migratedState;
},
},
});

const discoveryConfigPersistedReducer = await preparePersistReducer({
Expand Down
1 change: 1 addition & 0 deletions suite-native/storage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './atomWithUnecryptedStorage';

export * from './migrations/account/v2';
export * from './migrations/account/v3';
export * from './migrations/device/v2';

export * from './transforms/deviceTransforms';
export * from './transforms/walletTransforms';
Expand Down
38 changes: 38 additions & 0 deletions suite-native/storage/src/migrations/device/v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
interface OldDevice {
state?: string;
_state?: {
staticSessionId?: string;
[key: string]: any;
};
[key: string]: any;
}

interface MigratedDevice {
state?: {
staticSessionId?: string;
[key: string]: any;
};
[key: string]: any;
}

export const migrateDeviceState = (oldDevices: OldDevice[]): MigratedDevice[] =>
oldDevices.map(device => {
if (typeof device.state === 'string') {
if (typeof device?._state?.staticSessionId === 'string') {
// Has _state property, migrate to that

return { ...device, state: device._state, _state: undefined };
} else if (typeof device?.state === 'string') {
// No _state property, create new object

return {
...device,
state: {
staticSessionId: device.state,
},
};
}
}

return { ...device, state: undefined };
});
63 changes: 63 additions & 0 deletions suite-native/storage/src/tests/migrations/deviceV2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { migrateDeviceState } from '../../migrations/device/v2';

describe('migrateDeviceState', () => {
it('should migrate old devices without state to new format', () => {
const oldDevices = [
{
state: undefined,
otherKey: 'otherValue',
},
];

const migratedAccounts = migrateDeviceState(oldDevices);

expect(migratedAccounts).toEqual(oldDevices);
});

it('should migrate old devices with string state, without _state to new format', () => {
const oldDevices = [
{
state: 'staticSessionId',
otherKey: 'otherValue',
},
];

const migratedAccounts = migrateDeviceState(oldDevices);

const newDevices = [
{
state: {
staticSessionId: 'staticSessionId',
},
otherKey: 'otherValue',
},
];
expect(migratedAccounts).toEqual(newDevices);
});

it('should migrate old devices with string state and with _state to new format', () => {
const oldDevices = [
{
state: 'staticSessionId',
_state: {
staticSessionId: 'staticSessionId',
deriveCardano: true,
},
otherKey: 'otherValue',
},
];

const migratedAccounts = migrateDeviceState(oldDevices);

const newDevices = [
{
state: {
staticSessionId: 'staticSessionId',
deriveCardano: true,
},
otherKey: 'otherValue',
},
];
expect(migratedAccounts).toEqual(newDevices);
});
});
8 changes: 6 additions & 2 deletions suite-native/storage/src/transforms/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import { DeviceRootState } from '@suite-common/wallet-core';
*/
export const selectDeviceStatesNotRemembered = (state: DeviceRootState) => {
return A.filterMap(state.device.devices, device =>
device.remember || !device.state?.staticSessionId
device.remember || !device.state || !device.state.staticSessionId
? O.None
: O.Some(device.state.staticSessionId),
: O.Some(
typeof device.state === 'string'
? (device as any).state
: device.state.staticSessionId,
),
);
};

Expand Down

0 comments on commit 0bde39e

Please sign in to comment.