From 3c67fb1da23813df9539be23f92edf2a9b9f370a Mon Sep 17 00:00:00 2001 From: juriczech Date: Thu, 16 May 2024 14:03:40 +0200 Subject: [PATCH 1/4] refactor(suite): authorize device thunk --- .../suite/__fixtures__/suiteActions.ts | 28 +++--- .../suite/__tests__/suiteActions.test.ts | 4 +- .../NotificationRenderer.tsx | 5 +- .../middlewares/suite/analyticsMiddleware.ts | 5 +- .../src/middlewares/suite/eventsMiddleware.ts | 12 ++- .../src/middlewares/suite/logsMiddleware.ts | 18 +++- .../src/middlewares/suite/sentryMiddleware.ts | 3 +- .../src/middlewares/suite/suiteMiddleware.ts | 5 +- .../middlewares/wallet/discoveryMiddleware.ts | 5 +- .../suite/__fixtures__/deviceReducer.ts | 12 +-- packages/suite/src/utils/suite/logsUtils.ts | 4 +- .../src/deviceAuthenticityThunks.ts | 16 ++-- suite-common/redux-utils/src/createThunk.ts | 18 +++- suite-common/toast-notifications/src/types.ts | 3 +- .../wallet-core/src/device/deviceActions.ts | 11 --- .../wallet-core/src/device/deviceReducer.ts | 17 ++-- .../wallet-core/src/device/deviceThunks.ts | 45 +++++++--- .../wallet-core/src/stake/stakeThunks.ts | 88 +++++++++---------- .../src/transactions/transactionsThunks.ts | 14 ++- .../src/middlewares/deviceMiddleware.ts | 5 +- .../discovery/src/discoveryMiddleware.ts | 7 +- .../src/accountsImportThunks.ts | 15 ++-- 22 files changed, 200 insertions(+), 140 deletions(-) diff --git a/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts b/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts index ea9608880b9..1be24e4987b 100644 --- a/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts +++ b/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts @@ -1,9 +1,9 @@ import { testMocks } from '@suite-common/test-utils'; -import { discoveryActions, deviceActions } from '@suite-common/wallet-core'; +import { discoveryActions, deviceActions, authorizeDevice } from '@suite-common/wallet-core'; import { DEVICE, TRANSPORT } from '@trezor/connect'; import { notificationsActions } from '@suite-common/toast-notifications'; -import { SUITE, MODAL } from 'src/actions/suite/constants'; +import { SUITE } from 'src/actions/suite/constants'; import { TorStatus } from 'src/types/suite'; import * as suiteActions from '../suiteActions'; @@ -764,18 +764,18 @@ const acquireDevice = [ }, ]; -const authorizeDevice = [ +const authorizeDeviceActions = [ { description: `without device`, state: {}, - result: undefined, + result: authorizeDevice.rejected.type, }, { description: `with disconnected device`, state: { selectedDevice: getSuiteDevice(), }, - result: undefined, + result: authorizeDevice.rejected.type, }, { description: `with unacquired device`, @@ -785,7 +785,7 @@ const authorizeDevice = [ connected: true, }), }, - result: undefined, + result: authorizeDevice.rejected.type, }, { description: `with device which already has state`, @@ -795,7 +795,7 @@ const authorizeDevice = [ state: '012345', }), }, - result: undefined, + result: authorizeDevice.rejected.type, }, { description: `with device in unexpected mode`, @@ -805,7 +805,7 @@ const authorizeDevice = [ mode: 'bootloader', }), }, - result: undefined, + result: authorizeDevice.rejected.type, }, { description: `with device which needs FW update`, @@ -815,7 +815,7 @@ const authorizeDevice = [ firmware: 'required', }), }, - result: undefined, + result: authorizeDevice.rejected.type, }, { description: `success`, @@ -824,7 +824,7 @@ const authorizeDevice = [ connected: true, }), }, - result: deviceActions.authDevice.type, + result: authorizeDevice.fulfilled.type, }, { description: `duplicate detected`, @@ -849,7 +849,7 @@ const authorizeDevice = [ state: undefined, }), ], - result: MODAL.OPEN_USER_CONTEXT, + result: authorizeDevice.rejected.type, deviceReducerResult: [ getSuiteDevice({ connected: true, @@ -891,7 +891,7 @@ const authorizeDevice = [ state: undefined, }), ], - result: MODAL.OPEN_USER_CONTEXT, + result: authorizeDevice.rejected.type, deviceReducerResult: [ getSuiteDevice({ connected: true, @@ -920,7 +920,7 @@ const authorizeDevice = [ error: 'getDeviceState error', }, }, - result: notificationsActions.addToast.type, + result: authorizeDevice.rejected.type, }, ]; @@ -1085,7 +1085,7 @@ export default { forgetDisconnectedDevices, observeSelectedDevice, acquireDevice, - authorizeDevice, + authorizeDeviceActions, authConfirm, createDeviceInstance, switchDuplicatedDevice, diff --git a/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts b/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts index 5ae131ad914..83d42656ad5 100644 --- a/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts +++ b/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts @@ -237,7 +237,7 @@ describe('Suite Actions', () => { }); }); - fixtures.authorizeDevice.forEach(f => { + fixtures.authorizeDeviceActions.forEach(f => { it(`authorizeDevice: ${f.description}`, async () => { setTrezorConnectFixtures(f.getDeviceState); const state = getInitialState(undefined, { @@ -249,7 +249,7 @@ describe('Suite Actions', () => { if (!f.result) { expect(filterThunkActionTypes(store.getActions()).length).toEqual(0); } else { - const action = filterThunkActionTypes(store.getActions()).pop(); + const action = store.getActions().pop(); expect(action?.type).toEqual(f.result); if (f.deviceReducerResult) { const devices = selectDevices(store.getState()); diff --git a/packages/suite/src/components/suite/notifications/NotificationRenderer/NotificationRenderer.tsx b/packages/suite/src/components/suite/notifications/NotificationRenderer/NotificationRenderer.tsx index 0e91ebf9244..7f80f5683a5 100644 --- a/packages/suite/src/components/suite/notifications/NotificationRenderer/NotificationRenderer.tsx +++ b/packages/suite/src/components/suite/notifications/NotificationRenderer/NotificationRenderer.tsx @@ -1,7 +1,6 @@ import type { ComponentType } from 'react'; -import type { NotificationEntry } from '@suite-common/toast-notifications'; -import { deviceActions } from '@suite-common/wallet-core'; +import { AUTH_DEVICE, type NotificationEntry } from '@suite-common/toast-notifications'; import { DEVICE } from '@trezor/connect'; import { NotificationViewProps } from 'src/components/suite'; @@ -218,7 +217,7 @@ export const NotificationRenderer = ({ case 'coinjoin-interrupted': return error(render, notification, 'TR_COINJOIN_INTERRUPTED_ERROR'); // Events: - case deviceActions.authDevice.type: + case AUTH_DEVICE: return info(render, notification, 'EVENT_WALLET_CREATED'); case DEVICE.CONNECT: return ( diff --git a/packages/suite/src/middlewares/suite/analyticsMiddleware.ts b/packages/suite/src/middlewares/suite/analyticsMiddleware.ts index 6e93a2826da..ac0d4312c6f 100644 --- a/packages/suite/src/middlewares/suite/analyticsMiddleware.ts +++ b/packages/suite/src/middlewares/suite/analyticsMiddleware.ts @@ -6,7 +6,7 @@ import { discoveryActions, selectDevices, selectDevicesCount, - deviceActions, + authorizeDevice, } from '@suite-common/wallet-core'; import { analytics, EventType } from '@trezor/suite-analytics'; import { TRANSPORT, DEVICE } from '@trezor/connect'; @@ -33,6 +33,7 @@ import { } from 'src/reducers/wallet/coinjoinReducer'; import { updateLastAnonymityReportTimestamp } from 'src/actions/wallet/coinjoinAccountActions'; import { Account } from '@suite-common/wallet-types'; +import { isAnyOf } from '@reduxjs/toolkit'; /* In analytics middleware we may intercept actions we would like to log. For example: @@ -49,7 +50,7 @@ const analyticsMiddleware = const state = api.getState(); - if (deviceActions.authDevice.match(action)) { + if (isAnyOf(authorizeDevice.fulfilled)(action)) { analytics.report({ type: EventType.SelectWalletType, payload: { type: action.payload.device.walletNumber ? 'hidden' : 'standard' }, diff --git a/packages/suite/src/middlewares/suite/eventsMiddleware.ts b/packages/suite/src/middlewares/suite/eventsMiddleware.ts index 38bf57fc59a..0864606c343 100644 --- a/packages/suite/src/middlewares/suite/eventsMiddleware.ts +++ b/packages/suite/src/middlewares/suite/eventsMiddleware.ts @@ -5,13 +5,19 @@ import { selectDevice, accountsActions, deviceActions, + authorizeDevice, } from '@suite-common/wallet-core'; import * as deviceUtils from '@suite-common/suite-utils'; import { DEVICE } from '@trezor/connect'; -import { notificationsActions, removeAccountEventsThunk } from '@suite-common/toast-notifications'; +import { + AUTH_DEVICE, + notificationsActions, + removeAccountEventsThunk, +} from '@suite-common/toast-notifications'; import { SUITE } from 'src/actions/suite/constants'; import { AppState, Action, Dispatch } from 'src/types/suite'; +import { isAnyOf } from '@reduxjs/toolkit'; /* * Middleware for event notifications. @@ -103,8 +109,8 @@ const eventsMiddleware = }); } - if (deviceActions.authDevice.match(action)) { - api.dispatch(notificationsActions.addEvent({ type: action.type, seen: true })); + if (isAnyOf(authorizeDevice.fulfilled)(action)) { + api.dispatch(notificationsActions.addEvent({ type: AUTH_DEVICE, seen: true })); } return action; diff --git a/packages/suite/src/middlewares/suite/logsMiddleware.ts b/packages/suite/src/middlewares/suite/logsMiddleware.ts index c8a5c88b894..ea03917271a 100644 --- a/packages/suite/src/middlewares/suite/logsMiddleware.ts +++ b/packages/suite/src/middlewares/suite/logsMiddleware.ts @@ -1,6 +1,6 @@ import { MiddlewareAPI } from 'redux'; - -import { deviceActions, discoveryActions } from '@suite-common/wallet-core'; +import { isAnyOf } from '@reduxjs/toolkit'; +import { authorizeDevice, deviceActions, discoveryActions } from '@suite-common/wallet-core'; import { addLog } from '@suite-common/logger'; import { TRANSPORT, DEVICE } from '@trezor/connect'; import { redactUserPathFromString } from '@trezor/utils'; @@ -48,6 +48,19 @@ const log = } } + if (isAnyOf(authorizeDevice.fulfilled)(action)) { + api.dispatch( + addLog({ + type: 'authorizeDevice.fulfilled', + payload: { + device: action.payload.device, + firmwareRelease: undefined, + unavailableCapabilities: undefined, + }, + }), + ); + } + switch (action.type) { case SUITE.SET_LANGUAGE: case SUITE.SET_THEME: @@ -88,7 +101,6 @@ const log = }), ); break; - case deviceActions.authDevice.type: case DEVICE.CONNECT: case DEVICE.DISCONNECT: case discoveryActions.completeDiscovery.type: diff --git a/packages/suite/src/middlewares/suite/sentryMiddleware.ts b/packages/suite/src/middlewares/suite/sentryMiddleware.ts index d57c3a994d0..46d797d9541 100644 --- a/packages/suite/src/middlewares/suite/sentryMiddleware.ts +++ b/packages/suite/src/middlewares/suite/sentryMiddleware.ts @@ -6,6 +6,7 @@ import { blockchainActions, selectDevice, deviceActions, + authorizeDevice, } from '@suite-common/wallet-core'; import { getBootloaderVersion, @@ -55,7 +56,7 @@ const breadcrumbActions = [ DESKTOP_UPDATE.NOT_AVAILABLE, DESKTOP_UPDATE.READY, MODAL.CLOSE, - deviceActions.authDevice.type, + authorizeDevice.fulfilled.type, DEVICE.CONNECT, DEVICE.DISCONNECT, accountsActions.createAccount.type, diff --git a/packages/suite/src/middlewares/suite/suiteMiddleware.ts b/packages/suite/src/middlewares/suite/suiteMiddleware.ts index a70ff6c29ab..3236acb7c22 100644 --- a/packages/suite/src/middlewares/suite/suiteMiddleware.ts +++ b/packages/suite/src/middlewares/suite/suiteMiddleware.ts @@ -3,6 +3,7 @@ import { AnyAction, isAnyOf } from '@reduxjs/toolkit'; import { authConfirm, + authorizeDevice, deviceActions, forgetDisconnectedDevices, handleDeviceConnect, @@ -21,8 +22,8 @@ import { appChanged, setFlag } from 'src/actions/suite/suiteActions'; const isActionDeviceRelated = (action: AnyAction): boolean => { if ( isAnyOf( - deviceActions.authDevice, - deviceActions.authFailed, + authorizeDevice.fulfilled, + authorizeDevice.rejected, deviceActions.selectDevice, deviceActions.receiveAuthConfirm, deviceActions.updatePassphraseMode, diff --git a/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts b/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts index 6d4b049db89..35c9bcf8316 100644 --- a/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts +++ b/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts @@ -10,6 +10,7 @@ import { stopDiscoveryThunk, updateNetworkSettingsThunk, } from '@suite-common/wallet-core'; +import { isAnyOf } from '@reduxjs/toolkit'; import * as discoveryActions from '@suite-common/wallet-core'; import { UI } from '@trezor/connect'; import { DiscoveryStatus } from '@suite-common/wallet-constants'; @@ -110,7 +111,7 @@ export const prepareDiscoveryMiddleware = createMiddlewareWithExtraDeps( } // 4. device state received - if (deviceActions.authDevice.match(action)) { + if (isAnyOf(authorizeDevice.fulfilled)(action)) { // `device` is always present here // to avoid typescript conditioning use device from action as a fallback (never used) dispatch( @@ -138,7 +139,7 @@ export const prepareDiscoveryMiddleware = createMiddlewareWithExtraDeps( becomesConnected || action.type === SUITE.APP_CHANGED || deviceActions.selectDevice.match(action) || - deviceActions.authDevice.match(action) || + isAnyOf(authorizeDevice.fulfilled)(action) || walletSettingsActions.changeNetworks.match(action) || accountsActions.changeAccountVisibility.match(action) ) { diff --git a/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts b/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts index ae006faf200..a34f6e820ce 100644 --- a/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts +++ b/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts @@ -1,5 +1,5 @@ import { testMocks } from '@suite-common/test-utils'; -import { deviceActions } from '@suite-common/wallet-core'; +import { authorizeDevice, deviceActions } from '@suite-common/wallet-core'; import { DEVICE } from '@trezor/connect'; const { getConnectDevice, getSuiteDevice } = testMocks; @@ -850,7 +850,7 @@ const authDevice = [ initialState: { devices: [SUITE_DEVICE] }, actions: [ { - type: deviceActions.authDevice.type, + type: authorizeDevice.fulfilled.type, payload: { device: SUITE_DEVICE, state: 'A', @@ -875,7 +875,7 @@ const authDevice = [ }, actions: [ { - type: deviceActions.authDevice.type, + type: authorizeDevice.fulfilled.type, payload: { device: SUITE_DEVICE, state: 'A', @@ -904,7 +904,7 @@ const authDevice = [ }, actions: [ { - type: deviceActions.authDevice.type, + type: authorizeDevice.fulfilled.type, payload: { device: getSuiteDevice({ instance: 1 }), state: 'A', @@ -933,7 +933,7 @@ const authDevice = [ initialState: { devices: [SUITE_DEVICE] }, actions: [ { - type: deviceActions.authDevice.type, + type: authorizeDevice.fulfilled.type, payload: { device: getConnectDevice({ type: 'unacquired', @@ -953,7 +953,7 @@ const authDevice = [ initialState: { devices: [] }, actions: [ { - type: deviceActions.authDevice.type, + type: authorizeDevice.fulfilled.type, payload: { device: SUITE_DEVICE, state: 'A', diff --git a/packages/suite/src/utils/suite/logsUtils.ts b/packages/suite/src/utils/suite/logsUtils.ts index de87026d802..b85c6902010 100644 --- a/packages/suite/src/utils/suite/logsUtils.ts +++ b/packages/suite/src/utils/suite/logsUtils.ts @@ -3,7 +3,9 @@ import { discoveryActions, accountsActions, selectDevices, + authorizeDevice, } from '@suite-common/wallet-core'; +import { isAnyOf } from '@reduxjs/toolkit'; import { getEnvironment, getBrowserName, @@ -122,7 +124,7 @@ export const redactAction = (action: LogEntry) => { }; } - if (deviceActions.authDevice.match(action)) { + if (isAnyOf(authorizeDevice.fulfilled)(action)) { payload = { state: REDACTED_REPLACEMENT, ...redactDevice(action.payload.device), diff --git a/suite-common/device-authenticity/src/deviceAuthenticityThunks.ts b/suite-common/device-authenticity/src/deviceAuthenticityThunks.ts index 255e93910b3..327d567163d 100644 --- a/suite-common/device-authenticity/src/deviceAuthenticityThunks.ts +++ b/suite-common/device-authenticity/src/deviceAuthenticityThunks.ts @@ -4,12 +4,18 @@ import { notificationsActions, ToastPayload } from '@suite-common/toast-notifica import { ACTION_PREFIX, deviceAuthenticityActions } from './deviceAuthenticityActions'; -export const checkDeviceAuthenticityThunk = createThunk<{ - allowDebugKeys: boolean; - skipSuccessToast?: boolean; -}>( +export const checkDeviceAuthenticityThunk = createThunk( `${ACTION_PREFIX}/checkDeviceAuthenticity`, - async ({ allowDebugKeys, skipSuccessToast }, { dispatch, getState, extra }) => { + async ( + { + allowDebugKeys, + skipSuccessToast, + }: { + allowDebugKeys: boolean; + skipSuccessToast?: boolean; + }, + { dispatch, getState, extra }, + ) => { const { selectors: { selectDevice }, } = extra; diff --git a/suite-common/redux-utils/src/createThunk.ts b/suite-common/redux-utils/src/createThunk.ts index 532188f34a4..5f808bde288 100644 --- a/suite-common/redux-utils/src/createThunk.ts +++ b/suite-common/redux-utils/src/createThunk.ts @@ -6,8 +6,18 @@ import { import { ExtraDependencies } from './extraDependenciesType'; -export const createThunk = ( +type CustomThunkAPI = { + state: any; + extra: ExtraDependencies; +}; + +export const createThunk = ( typePrefix: string, - thunk: AsyncThunkPayloadCreator, - options?: AsyncThunkOptions, -) => createAsyncThunkReduxToolkit(typePrefix, thunk, options); + thunk: AsyncThunkPayloadCreator, + options?: AsyncThunkOptions, +) => + createAsyncThunkReduxToolkit( + typePrefix, + thunk, + options, + ); diff --git a/suite-common/toast-notifications/src/types.ts b/suite-common/toast-notifications/src/types.ts index 9a91a970a12..821838613df 100644 --- a/suite-common/toast-notifications/src/types.ts +++ b/suite-common/toast-notifications/src/types.ts @@ -137,13 +137,14 @@ export type ToastPayload = ( ) & NotificationOptions; +export const AUTH_DEVICE = 'auth-device'; export type NotificationEventPayload = ( | { // only temporary, must be same as AUTH_DEVICE value in packages/suite/src/actions/suite/constants/suiteConstants.ts // once that will be migrated to @suite-common, this should be replaced directly by suiteActions.authDevice.type // this should not break type safety, if someone will change value of AUTH_DEVICE, it will throw error in place // where action is used and you will need to change it also here - type: '@suite/device/authDevice'; + type: typeof AUTH_DEVICE; } | ReceivedTransactionNotification | { diff --git a/suite-common/wallet-core/src/device/deviceActions.ts b/suite-common/wallet-core/src/device/deviceActions.ts index 1e9fe3bf5f1..9fad648ab32 100644 --- a/suite-common/wallet-core/src/device/deviceActions.ts +++ b/suite-common/wallet-core/src/device/deviceActions.ts @@ -22,10 +22,6 @@ const updatePassphraseMode = createAction( (payload: { device: TrezorDevice; hidden: boolean; alwaysOnDevice?: boolean }) => ({ payload }), ); -const authFailed = createAction(`${DEVICE_MODULE_PREFIX}/authFailed`, (payload: TrezorDevice) => ({ - payload, -})); - const receiveAuthConfirm = createAction( `${DEVICE_MODULE_PREFIX}/receiveAuthConfirm`, (payload: { device: TrezorDevice; success: boolean }) => ({ payload }), @@ -50,11 +46,6 @@ const forgetDevice = createAction( }), ); -const authDevice = createAction( - `${DEVICE_MODULE_PREFIX}/authDevice`, - (payload: { device: TrezorDevice; state: string }) => ({ payload }), -); - const addButtonRequest = createAction( `${DEVICE_MODULE_PREFIX}/addButtonRequest`, (payload: { device?: TrezorDevice; buttonRequest: ButtonRequest }) => ({ payload }), @@ -88,12 +79,10 @@ export const deviceActions = { deviceChanged, deviceDisconnect, updatePassphraseMode, - authFailed, receiveAuthConfirm, createDeviceInstance, rememberDevice, forgetDevice, - authDevice, addButtonRequest, requestDeviceReconnect, selectDevice, diff --git a/suite-common/wallet-core/src/device/deviceReducer.ts b/suite-common/wallet-core/src/device/deviceReducer.ts index 397d6fe92c4..231d668ec29 100644 --- a/suite-common/wallet-core/src/device/deviceReducer.ts +++ b/suite-common/wallet-core/src/device/deviceReducer.ts @@ -516,12 +516,20 @@ export const prepareDeviceReducer = createReducerWithExtraDeps(initialState, (bu .addCase(deviceActions.updatePassphraseMode, (state, { payload }) => { changePassphraseMode(state, payload.device, payload.hidden, payload.alwaysOnDevice); }) - .addCase(deviceActions.authFailed, (state, { payload }) => { - authFailed(state, payload); - }) .addCase(authorizeDevice.pending, state => { resetAuthFailed(state); }) + .addCase(authorizeDevice.fulfilled, (state, { payload }) => { + authDevice(state, payload.device, payload.state); + }) + .addCase(authorizeDevice.rejected, (state, action) => { + if (action.payload && action.payload.error) { + const { error } = action.payload; + if (error === 'auth-failed') { + authFailed(state, action.payload.device); + } + } + }) .addCase(UI.REQUEST_PIN, state => { resetAuthFailed(state); }) @@ -537,9 +545,6 @@ export const prepareDeviceReducer = createReducerWithExtraDeps(initialState, (bu .addCase(deviceActions.forgetDevice, (state, { payload }) => { forget(state, payload); }) - .addCase(deviceActions.authDevice, (state, { payload }) => { - authDevice(state, payload.device, payload.state); - }) .addCase(deviceActions.addButtonRequest, (state, { payload }) => { addButtonRequest(state, payload.device, payload.buttonRequest); }) diff --git a/suite-common/wallet-core/src/device/deviceThunks.ts b/suite-common/wallet-core/src/device/deviceThunks.ts index e524622a3a8..0f189d2a3c0 100644 --- a/suite-common/wallet-core/src/device/deviceThunks.ts +++ b/suite-common/wallet-core/src/device/deviceThunks.ts @@ -273,14 +273,26 @@ export const acquireDevice = createThunk( * Called from `discoveryMiddleware` * Fetch device state, update `devices` reducer as result of SUITE.AUTH_DEVICE */ -export const authorizeDevice = createThunk( +type AuthorizeDeviceParams = { shouldIgnoreDeviceState: boolean } | undefined; +export type AuthorizeDeviceError = { + error: string; + device: TrezorDevice; + duplicate?: TrezorDevice; +}; +type AuthorizeDeviceSuccess = { device: TrezorDevice; state: string }; + +export const authorizeDevice = createThunk< + AuthorizeDeviceSuccess, + AuthorizeDeviceParams, + { rejectValue: AuthorizeDeviceError } +>( `${DEVICE_MODULE_PREFIX}/authorizeDevice`, async ( - { shouldIgnoreDeviceState }: { shouldIgnoreDeviceState: boolean } | undefined = { + { shouldIgnoreDeviceState } = { shouldIgnoreDeviceState: false, }, - { dispatch, getState, extra }, - ): Promise => { + { dispatch, getState, extra, rejectWithValue }, + ) => { const { selectors: { selectCheckFirmwareAuthenticity }, actions: { openModal }, @@ -288,7 +300,9 @@ export const authorizeDevice = createThunk( const selectedCheckFirmwareAuthenticity = selectCheckFirmwareAuthenticity(getState()); const device = selectDeviceSelector(getState()); - if (!device) return false; + + if (!device) return rejectWithValue({ error: 'no-device' } as AuthorizeDeviceError); + const isDeviceReady = device.connected && device.features && @@ -297,7 +311,9 @@ export const authorizeDevice = createThunk( (!device.state || shouldIgnoreDeviceState) && device.mode === 'normal' && device.firmware !== 'required'; - if (!isDeviceReady) return false; + + if (!isDeviceReady) + return rejectWithValue({ error: 'device-not-ready', device } as AuthorizeDeviceError); if (selectedCheckFirmwareAuthenticity) { await dispatch(checkFirmwareAuthenticity()); @@ -333,17 +349,19 @@ export const authorizeDevice = createThunk( // reset useEmptyPassphrase field for selected device to allow future PassphraseRequests dispatch(deviceActions.updatePassphraseMode({ device, hidden: true })); } + dispatch(openModal({ type: 'passphrase-duplicate', device, duplicate })); - return false; + return rejectWithValue({ + error: 'passphrase-duplicate', + device, + duplicate, + } as AuthorizeDeviceError); } - dispatch(deviceActions.authDevice({ device: freshDeviceData as TrezorDevice, state })); - - return true; + return { device: freshDeviceData as TrezorDevice, state }; } - dispatch(deviceActions.authFailed(device)); dispatch( notificationsActions.addToast({ type: 'auth-failed', @@ -351,7 +369,10 @@ export const authorizeDevice = createThunk( }), ); - return false; + return rejectWithValue({ + error: 'auth-failed', + device: device as TrezorDevice, + } as AuthorizeDeviceError); }, ); diff --git a/suite-common/wallet-core/src/stake/stakeThunks.ts b/suite-common/wallet-core/src/stake/stakeThunks.ts index 38cdc14b75e..597fc84d4a8 100644 --- a/suite-common/wallet-core/src/stake/stakeThunks.ts +++ b/suite-common/wallet-core/src/stake/stakeThunks.ts @@ -15,54 +15,52 @@ import { const STAKE_MODULE = '@common/wallet-core/stake'; -export const fetchEverstakeData = createThunk( - `${STAKE_MODULE}/fetchEverstakeData`, - async ( - params: { - networkSymbol: SupportedNetworkSymbol; - endpointType: EverstakeEndpointType; - }, - { fulfillWithValue, rejectWithValue }, - ) => { - const { networkSymbol, endpointType } = params; - - const endpointSuffix = EVERSTAKE_ENDPOINT_TYPES[endpointType]; - const endpointPrefix = EVERSTAKE_ENDPOINT_PREFIX[networkSymbol]; - - try { - const response = await fetch(`${endpointPrefix}/${endpointSuffix}`); - - if (!response.ok) { - throw Error(response.statusText); - } - - const data = await response.json(); - - if (endpointType === EverstakeEndpointType.PoolStats) { - return fulfillWithValue({ - ethApy: Number( - new BigNumber(data.apr).times(100).toPrecision(3, BigNumber.ROUND_DOWN), - ), - nextRewardPayout: Math.ceil(data.next_reward_payout_in / 60 / 60 / 24), - }); - } +export const fetchEverstakeData = createThunk< + ValidatorsQueue | { ethApy: number; nextRewardPayout: number }, + { + networkSymbol: SupportedNetworkSymbol; + endpointType: EverstakeEndpointType; + }, + { rejectValue: string } +>(`${STAKE_MODULE}/fetchEverstakeData`, async (params, { fulfillWithValue, rejectWithValue }) => { + const { networkSymbol, endpointType } = params; + + const endpointSuffix = EVERSTAKE_ENDPOINT_TYPES[endpointType]; + const endpointPrefix = EVERSTAKE_ENDPOINT_PREFIX[networkSymbol]; + + try { + const response = await fetch(`${endpointPrefix}/${endpointSuffix}`); + + if (!response.ok) { + throw Error(response.statusText); + } + const data = await response.json(); + + if (endpointType === EverstakeEndpointType.PoolStats) { return fulfillWithValue({ - validatorsEnteringNum: data.validators_entering_num, - validatorsExitingNum: data.validators_exiting_num, - validatorsTotalCount: data.validators_total_count, - validatorsPerEpoch: data.validators_per_epoch, - validatorActivationTime: data.validator_activation_time, - validatorExitTime: data.validator_exit_time, - validatorWithdrawTime: data.validator_withdraw_time, - validatorAddingDelay: data.validator_adding_delay, - updatedAt: data.updated_at, - } as ValidatorsQueue); - } catch (error) { - return rejectWithValue(error.toString()); + ethApy: Number( + new BigNumber(data.apr).times(100).toPrecision(3, BigNumber.ROUND_DOWN), + ), + nextRewardPayout: Math.ceil(data.next_reward_payout_in / 60 / 60 / 24), + }); } - }, -); + + return fulfillWithValue({ + validatorsEnteringNum: data.validators_entering_num, + validatorsExitingNum: data.validators_exiting_num, + validatorsTotalCount: data.validators_total_count, + validatorsPerEpoch: data.validators_per_epoch, + validatorActivationTime: data.validator_activation_time, + validatorExitTime: data.validator_exit_time, + validatorWithdrawTime: data.validator_withdraw_time, + validatorAddingDelay: data.validator_adding_delay, + updatedAt: data.updated_at, + } as ValidatorsQueue); + } catch (error) { + return rejectWithValue(error.toString()); + } +}); export const initStakeDataThunk = createThunk( `${STAKE_MODULE}/initStakeDataThunk`, diff --git a/suite-common/wallet-core/src/transactions/transactionsThunks.ts b/suite-common/wallet-core/src/transactions/transactionsThunks.ts index 3a5395c02e9..8df18515fe2 100644 --- a/suite-common/wallet-core/src/transactions/transactionsThunks.ts +++ b/suite-common/wallet-core/src/transactions/transactionsThunks.ts @@ -45,9 +45,12 @@ interface ReplaceTransactionThunkParams { signedTransaction?: Transaction; // tx returned from @trezor/connect (only in bitcoin-like) } -export const replaceTransactionThunk = createThunk( +export const replaceTransactionThunk = createThunk( `${TRANSACTIONS_MODULE_PREFIX}/replaceTransactionThunk`, - ({ precomposedTx, newTxid, signedTransaction }, { getState, dispatch }) => { + ( + { precomposedTx, newTxid, signedTransaction }: ReplaceTransactionThunkParams, + { getState, dispatch }, + ) => { if (!precomposedTx.prevTxid) return; // ignore if it's not a replacement tx const walletTransactions = selectTransactions(getState()); @@ -111,9 +114,12 @@ interface AddFakePendingTransactionParams { account: Account; } -export const addFakePendingTxThunk = createThunk( +export const addFakePendingTxThunk = createThunk( `${TRANSACTIONS_MODULE_PREFIX}/addFakePendingTransaction`, - ({ transaction, precomposedTx, account }, { dispatch, getState }) => { + ( + { transaction, precomposedTx, account }: AddFakePendingTransactionParams, + { dispatch, getState }, + ) => { const blockHeight = selectBlockchainHeightBySymbol(getState(), account.symbol); const accounts = selectAccounts(getState()); diff --git a/suite-native/device/src/middlewares/deviceMiddleware.ts b/suite-native/device/src/middlewares/deviceMiddleware.ts index 3e045e73a72..f31909c3a79 100644 --- a/suite-native/device/src/middlewares/deviceMiddleware.ts +++ b/suite-native/device/src/middlewares/deviceMiddleware.ts @@ -4,6 +4,7 @@ import { createMiddlewareWithExtraDeps } from '@suite-common/redux-utils'; import { DEVICE } from '@trezor/connect'; import { accountsActions, + authorizeDevice, deviceActions, forgetDisconnectedDevices, handleDeviceDisconnect, @@ -17,8 +18,8 @@ import { clearAndUnlockDeviceAccessQueue } from '@suite-native/device-mutex'; const isActionDeviceRelated = (action: AnyAction): boolean => { if ( isAnyOf( - deviceActions.authDevice, - deviceActions.authFailed, + authorizeDevice.fulfilled, + authorizeDevice.rejected, deviceActions.selectDevice, deviceActions.receiveAuthConfirm, deviceActions.updatePassphraseMode, diff --git a/suite-native/discovery/src/discoveryMiddleware.ts b/suite-native/discovery/src/discoveryMiddleware.ts index b7024340ed0..05af9d38c5f 100644 --- a/suite-native/discovery/src/discoveryMiddleware.ts +++ b/suite-native/discovery/src/discoveryMiddleware.ts @@ -1,9 +1,12 @@ +import { isAnyOf } from '@reduxjs/toolkit'; + import { deviceActions, selectDevice, discoveryActions, selectDeviceModel, selectDeviceFirmwareVersion, + authorizeDevice, } from '@suite-common/wallet-core'; import { createMiddlewareWithExtraDeps } from '@suite-common/redux-utils'; import { isFirmwareVersionSupported } from '@suite-native/device'; @@ -40,12 +43,12 @@ export const prepareDiscoveryMiddleware = createMiddlewareWithExtraDeps( } } - // We need to wait until is the `authDevice` action applied, because we need + // We need to wait until `authorizeDevice` action is fulfilled, because we need // to know the device state when starting discovery of newly authorized device. next(action); // On successful authorization, create discovery instance and run it. - if (deviceActions.authDevice.match(action) && isDeviceFirmwareVersionSupported) { + if (isAnyOf(authorizeDevice.fulfilled)(action) && isDeviceFirmwareVersionSupported) { dispatch( startDescriptorPreloadedDiscoveryThunk({ deviceState: action.payload.state, diff --git a/suite-native/module-accounts-import/src/accountsImportThunks.ts b/suite-native/module-accounts-import/src/accountsImportThunks.ts index df662ad1eb7..381831a471d 100644 --- a/suite-native/module-accounts-import/src/accountsImportThunks.ts +++ b/suite-native/module-accounts-import/src/accountsImportThunks.ts @@ -72,16 +72,13 @@ export const importAccountThunk = createThunk( }, ); -export const getAccountInfoThunk = createThunk( +export const getAccountInfoThunk = createThunk< + AccountInfo, + { networkSymbol: NetworkSymbol; fiatCurrency: FiatCurrencyCode; xpubAddress: string }, + { rejectValue: string } +>( `${ACCOUNTS_IMPORT_MODULE_PREFIX}/getAccountInfo`, - async ( - { - networkSymbol, - fiatCurrency, - xpubAddress, - }: { networkSymbol: NetworkSymbol; fiatCurrency: FiatCurrencyCode; xpubAddress: string }, - { dispatch, rejectWithValue }, - ) => { + async ({ networkSymbol, fiatCurrency, xpubAddress }, { dispatch, rejectWithValue }) => { try { const [fetchedAccountInfo] = await Promise.all([ TrezorConnect.getAccountInfo({ From dbb9d846aca2f24e7117dde3ed1ea0d3d7501c04 Mon Sep 17 00:00:00 2001 From: juriczech Date: Fri, 17 May 2024 12:39:11 +0200 Subject: [PATCH 2/4] feat(suite-native): duplicit passphrase handling --- suite-native/intl/src/en.ts | 5 ++ suite-native/module-passphrase/package.json | 1 + .../src/components/PassphraseScreenHeader.tsx | 3 +- .../navigation/PassphraseStackNavigator.tsx | 3 + .../screens/PassphraseEmptyWalletScreen.tsx | 2 +- .../screens/PassphraseEnterOnTrezorScreen.tsx | 2 +- .../PassphraseVerifyEmptyWalletScreen.tsx | 6 +- suite-native/module-passphrase/tsconfig.json | 1 + suite-native/passphrase/package.json | 25 +++++++ suite-native/passphrase/redux.d.ts | 7 ++ suite-native/passphrase/src/index.ts | 3 + .../passphrase/src/passphraseSlice.ts | 36 ++++++++++ .../src/passphraseThunks.ts | 0 .../src/useHandleDuplicitPassphrase.ts | 71 +++++++++++++++++++ suite-native/passphrase/tsconfig.json | 19 +++++ suite-native/state/package.json | 1 + suite-native/state/src/reducers.ts | 2 + suite-native/state/tsconfig.json | 1 + tsconfig.json | 1 + yarn.lock | 20 ++++++ 20 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 suite-native/passphrase/package.json create mode 100644 suite-native/passphrase/redux.d.ts create mode 100644 suite-native/passphrase/src/index.ts create mode 100644 suite-native/passphrase/src/passphraseSlice.ts rename suite-native/{module-passphrase => passphrase}/src/passphraseThunks.ts (100%) create mode 100644 suite-native/passphrase/src/useHandleDuplicitPassphrase.ts create mode 100644 suite-native/passphrase/tsconfig.json diff --git a/suite-native/intl/src/en.ts b/suite-native/intl/src/en.ts index 350099db2e1..4a66ce4939d 100644 --- a/suite-native/intl/src/en.ts +++ b/suite-native/intl/src/en.ts @@ -703,6 +703,11 @@ export const en = { }, }, }, + passphraseMismatch: { + title: 'Passphrase mismatch', + subtitle: 'Start over and enter your passphrase.', + button: 'Start over', + }, }, }; diff --git a/suite-native/module-passphrase/package.json b/suite-native/module-passphrase/package.json index 486062c73a6..9741b2a913d 100644 --- a/suite-native/module-passphrase/package.json +++ b/suite-native/module-passphrase/package.json @@ -25,6 +25,7 @@ "@suite-native/intl": "workspace:*", "@suite-native/link": "workspace:*", "@suite-native/navigation": "workspace:*", + "@suite-native/passphrase": "workspace:*", "@suite-native/theme": "workspace:*", "@trezor/connect": "workspace:*", "@trezor/styles": "workspace:*", diff --git a/suite-native/module-passphrase/src/components/PassphraseScreenHeader.tsx b/suite-native/module-passphrase/src/components/PassphraseScreenHeader.tsx index e528d0643f1..f33bc750d59 100644 --- a/suite-native/module-passphrase/src/components/PassphraseScreenHeader.tsx +++ b/suite-native/module-passphrase/src/components/PassphraseScreenHeader.tsx @@ -22,8 +22,7 @@ import { } from '@suite-native/navigation'; import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; import { Translation } from '@suite-native/intl'; - -import { cancelPassphraseAndSelectStandardDeviceThunk } from '../passphraseThunks'; +import { cancelPassphraseAndSelectStandardDeviceThunk } from '@suite-native/passphrase'; type NavigationProp = StackToTabCompositeProps< PassphraseStackParamList, diff --git a/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx b/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx index aae9a3fa8fd..7589a7cd6a2 100644 --- a/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx +++ b/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx @@ -5,6 +5,7 @@ import { PassphraseStackRoutes, stackNavigationOptionsConfig, } from '@suite-native/navigation'; +import { useHandleDuplicitPassphrase } from '@suite-native/passphrase'; import { PassphraseFormScreen } from '../screens/PassphraseFormScreen'; import { PassphraseLoadingScreen } from '../screens/PassphraseLoadingScreen'; @@ -16,6 +17,8 @@ import { PassphraseEnterOnTrezorScreen } from '../screens/PassphraseEnterOnTrezo export const PassphraseStack = createNativeStackNavigator(); export const PassphraseStackNavigator = () => { + useHandleDuplicitPassphrase(); + return ( ({ diff --git a/suite-native/module-passphrase/src/screens/PassphraseEnterOnTrezorScreen.tsx b/suite-native/module-passphrase/src/screens/PassphraseEnterOnTrezorScreen.tsx index 7fed83aeea6..7ab9b971a78 100644 --- a/suite-native/module-passphrase/src/screens/PassphraseEnterOnTrezorScreen.tsx +++ b/suite-native/module-passphrase/src/screens/PassphraseEnterOnTrezorScreen.tsx @@ -16,8 +16,8 @@ import { Box, Button, Card, CenteredTitleHeader, Text, VStack } from '@suite-nat import { selectIsDeviceDiscoveryActive } from '@suite-common/wallet-core'; import { Translation } from '@suite-native/intl'; import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; +import { cancelPassphraseAndSelectStandardDeviceThunk } from '@suite-native/passphrase'; -import { cancelPassphraseAndSelectStandardDeviceThunk } from '../passphraseThunks'; import { DeviceT2B1Svg } from '../assets/DeviceT2B1Svg'; import { PassphraseContentScreenWrapper } from '../components/PassphraseContentScreenWrapper'; diff --git a/suite-native/module-passphrase/src/screens/PassphraseVerifyEmptyWalletScreen.tsx b/suite-native/module-passphrase/src/screens/PassphraseVerifyEmptyWalletScreen.tsx index 05d0868d788..98af2232f0f 100644 --- a/suite-native/module-passphrase/src/screens/PassphraseVerifyEmptyWalletScreen.tsx +++ b/suite-native/module-passphrase/src/screens/PassphraseVerifyEmptyWalletScreen.tsx @@ -16,13 +16,13 @@ import { } from '@suite-native/navigation'; import { AlertBox, Text, VStack } from '@suite-native/atoms'; import { Translation, useTranslate } from '@suite-native/intl'; - -import { PassphraseForm } from '../components/PassphraseForm'; import { cancelPassphraseAndSelectStandardDeviceThunk, retryPassphraseAuthenticationThunk, verifyPassphraseOnEmptyWalletThunk, -} from '../passphraseThunks'; +} from '@suite-native/passphrase'; + +import { PassphraseForm } from '../components/PassphraseForm'; import { PassphraseContentScreenWrapper } from '../components/PassphraseContentScreenWrapper'; type NavigationProp = StackToTabCompositeProps< diff --git a/suite-native/module-passphrase/tsconfig.json b/suite-native/module-passphrase/tsconfig.json index a591b71dee4..3f2496cc0e5 100644 --- a/suite-native/module-passphrase/tsconfig.json +++ b/suite-native/module-passphrase/tsconfig.json @@ -22,6 +22,7 @@ { "path": "../intl" }, { "path": "../link" }, { "path": "../navigation" }, + { "path": "../passphrase" }, { "path": "../theme" }, { "path": "../../packages/connect" }, { "path": "../../packages/styles" } diff --git a/suite-native/passphrase/package.json b/suite-native/passphrase/package.json new file mode 100644 index 00000000000..2ed392e2454 --- /dev/null +++ b/suite-native/passphrase/package.json @@ -0,0 +1,25 @@ +{ + "name": "@suite-native/passphrase", + "version": "1.0.0", + "private": true, + "license": "See LICENSE.md in repo root", + "sideEffects": false, + "main": "src/index", + "scripts": { + "lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'", + "type-check": "yarn g:tsc --build" + }, + "dependencies": { + "@react-navigation/native": "6.1.10", + "@reduxjs/toolkit": "1.9.5", + "@suite-common/redux-utils": "workspace:*", + "@suite-common/suite-types": "workspace:*", + "@suite-common/wallet-core": "workspace:*", + "@suite-native/alerts": "workspace:*", + "@suite-native/intl": "workspace:*", + "@suite-native/navigation": "workspace:*", + "@trezor/connect": "workspace:*", + "react": "18.2.0", + "react-redux": "8.0.7" + } +} diff --git a/suite-native/passphrase/redux.d.ts b/suite-native/passphrase/redux.d.ts new file mode 100644 index 00000000000..df9a0c3f969 --- /dev/null +++ b/suite-native/passphrase/redux.d.ts @@ -0,0 +1,7 @@ +import { AsyncThunkAction } from '@reduxjs/toolkit'; + +declare module 'redux' { + export interface Dispatch { + >(thunk: TThunk): ReturnType; + } +} diff --git a/suite-native/passphrase/src/index.ts b/suite-native/passphrase/src/index.ts new file mode 100644 index 00000000000..023c0f1fc29 --- /dev/null +++ b/suite-native/passphrase/src/index.ts @@ -0,0 +1,3 @@ +export * from './passphraseSlice'; +export * from './passphraseThunks'; +export * from './useHandleDuplicitPassphrase'; diff --git a/suite-native/passphrase/src/passphraseSlice.ts b/suite-native/passphrase/src/passphraseSlice.ts new file mode 100644 index 00000000000..9a36a892fa0 --- /dev/null +++ b/suite-native/passphrase/src/passphraseSlice.ts @@ -0,0 +1,36 @@ +import { createSlice } from '@reduxjs/toolkit'; + +import { AuthorizeDeviceError, authorizeDevice } from '@suite-common/wallet-core'; + +type PassphraseState = { + error: AuthorizeDeviceError | null; +}; + +type PassphraseRootState = { + passphrase: PassphraseState; +}; + +const passphraseInitialState: PassphraseState = { + error: null, +}; + +export const passphraseSlice = createSlice({ + name: 'passphrase', + initialState: passphraseInitialState, + reducers: {}, + extraReducers: builder => { + builder + .addCase(authorizeDevice.pending, state => { + state.error = null; + }) + .addCase(authorizeDevice.rejected, (state, { payload }) => { + if (payload?.error === 'passphrase-duplicate') { + state.error = payload; + } + }); + }, +}); + +export const selectPassphraseError = (state: PassphraseRootState) => state.passphrase.error; + +export const passphraseReducer = passphraseSlice.reducer; diff --git a/suite-native/module-passphrase/src/passphraseThunks.ts b/suite-native/passphrase/src/passphraseThunks.ts similarity index 100% rename from suite-native/module-passphrase/src/passphraseThunks.ts rename to suite-native/passphrase/src/passphraseThunks.ts diff --git a/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts b/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts new file mode 100644 index 00000000000..90c2740c4b9 --- /dev/null +++ b/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts @@ -0,0 +1,71 @@ +import { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { useNavigation } from '@react-navigation/native'; + +import { useAlert } from '@suite-native/alerts'; +import { switchDuplicatedDevice } from '@suite-common/wallet-core'; +import { + AppTabsRoutes, + HomeStackRoutes, + PassphraseStackParamList, + PassphraseStackRoutes, + RootStackParamList, + RootStackRoutes, + StackToStackCompositeNavigationProps, +} from '@suite-native/navigation'; +import { TrezorDevice } from '@suite-common/suite-types'; +import { useTranslate } from '@suite-native/intl'; + +import { selectPassphraseError } from './passphraseSlice'; + +type NavigationProp = StackToStackCompositeNavigationProps< + PassphraseStackParamList, + PassphraseStackRoutes, + RootStackParamList +>; + +export const useHandleDuplicitPassphrase = () => { + const dispatch = useDispatch(); + + const passphraseError = useSelector(selectPassphraseError); + + const { translate } = useTranslate(); + + const navigation = useNavigation(); + + const { showAlert, hideAlert } = useAlert(); + + const handleDuplicateDevicePassphrase = useCallback( + ({ device, duplicate }: { device: TrezorDevice; duplicate?: TrezorDevice }) => { + // Not all passphrase errors have duplicate property, but we know this one does + // based on condition in `./passphraseSlice`. This if is just to keep TS happy. + if (duplicate) { + dispatch(switchDuplicatedDevice({ device, duplicate })); + navigation.navigate(RootStackRoutes.AppTabs, { + screen: AppTabsRoutes.HomeStack, + params: { + screen: HomeStackRoutes.Home, + }, + }); + hideAlert(); + } + }, + [dispatch, hideAlert, navigation], + ); + + useEffect(() => { + if (passphraseError) { + showAlert({ + title: translate('modulePassphrase.passphraseMismatch.title'), + description: translate('modulePassphrase.passphraseMismatch.subtitle'), + primaryButtonTitle: translate('modulePassphrase.passphraseMismatch.button'), + onPressPrimaryButton: () => + handleDuplicateDevicePassphrase({ + device: passphraseError.device, + duplicate: passphraseError.duplicate, + }), + }); + } + }, [handleDuplicateDevicePassphrase, passphraseError, showAlert, translate]); +}; diff --git a/suite-native/passphrase/tsconfig.json b/suite-native/passphrase/tsconfig.json new file mode 100644 index 00000000000..eeaeeb249f7 --- /dev/null +++ b/suite-native/passphrase/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "libDev" }, + "references": [ + { + "path": "../../suite-common/redux-utils" + }, + { + "path": "../../suite-common/suite-types" + }, + { + "path": "../../suite-common/wallet-core" + }, + { "path": "../alerts" }, + { "path": "../intl" }, + { "path": "../navigation" }, + { "path": "../../packages/connect" } + ] +} diff --git a/suite-native/state/package.json b/suite-native/state/package.json index 398444390ac..6aefb30ae04 100644 --- a/suite-native/state/package.json +++ b/suite-native/state/package.json @@ -27,6 +27,7 @@ "@suite-native/feature-flags": "workspace:*", "@suite-native/graph": "workspace:*", "@suite-native/message-system": "workspace:*", + "@suite-native/passphrase": "workspace:*", "@suite-native/settings": "workspace:*", "@suite-native/storage": "workspace:*", "@suite-native/toasts": "workspace:*", diff --git a/suite-native/state/src/reducers.ts b/suite-native/state/src/reducers.ts index f59aeabe796..48671f14c50 100644 --- a/suite-native/state/src/reducers.ts +++ b/suite-native/state/src/reducers.ts @@ -29,6 +29,7 @@ import { graphReducer, graphPersistTransform } from '@suite-native/graph'; import { discoveryConfigPersistWhitelist, discoveryConfigReducer } from '@suite-native/discovery'; import { featureFlagsPersistedKeys, featureFlagsReducer } from '@suite-native/feature-flags'; import { prepareTokenDefinitionsReducer } from '@suite-common/token-definitions'; +import { passphraseReducer } from '@suite-native/passphrase'; import { extraDependencies } from './extraDependencies'; import { appReducer } from './appSlice'; @@ -143,6 +144,7 @@ export const prepareRootReducers = async () => { discoveryConfig: discoveryConfigPersistedReducer, messageSystem: messageSystemPersistedReducer, tokenDefinitions: tokenDefinitionsReducer, + passphrase: passphraseReducer, }), // 'wallet' and 'graph' need to be persisted at the top level to ensure device state // is accessible for transformation. diff --git a/suite-native/state/tsconfig.json b/suite-native/state/tsconfig.json index 8a637eb180f..5b751767a3e 100644 --- a/suite-native/state/tsconfig.json +++ b/suite-native/state/tsconfig.json @@ -32,6 +32,7 @@ { "path": "../feature-flags" }, { "path": "../graph" }, { "path": "../message-system" }, + { "path": "../passphrase" }, { "path": "../settings" }, { "path": "../storage" }, { "path": "../toasts" }, diff --git a/tsconfig.json b/tsconfig.json index ecb542be80e..30183c5ac1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -96,6 +96,7 @@ }, { "path": "suite-native/navigation" }, { "path": "suite-native/notifications" }, + { "path": "suite-native/passphrase" }, { "path": "suite-native/qr-code" }, { "path": "suite-native/react-native-graph" diff --git a/yarn.lock b/yarn.lock index 4fce7effc5d..c590271f9d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9589,6 +9589,7 @@ __metadata: "@suite-native/intl": "workspace:*" "@suite-native/link": "workspace:*" "@suite-native/navigation": "workspace:*" + "@suite-native/passphrase": "workspace:*" "@suite-native/theme": "workspace:*" "@trezor/connect": "workspace:*" "@trezor/styles": "workspace:*" @@ -9750,6 +9751,24 @@ __metadata: languageName: unknown linkType: soft +"@suite-native/passphrase@workspace:*, @suite-native/passphrase@workspace:suite-native/passphrase": + version: 0.0.0-use.local + resolution: "@suite-native/passphrase@workspace:suite-native/passphrase" + dependencies: + "@react-navigation/native": "npm:6.1.10" + "@reduxjs/toolkit": "npm:1.9.5" + "@suite-common/redux-utils": "workspace:*" + "@suite-common/suite-types": "workspace:*" + "@suite-common/wallet-core": "workspace:*" + "@suite-native/alerts": "workspace:*" + "@suite-native/intl": "workspace:*" + "@suite-native/navigation": "workspace:*" + "@trezor/connect": "workspace:*" + react: "npm:18.2.0" + react-redux: "npm:8.0.7" + languageName: unknown + linkType: soft + "@suite-native/qr-code@workspace:*, @suite-native/qr-code@workspace:suite-native/qr-code": version: 0.0.0-use.local resolution: "@suite-native/qr-code@workspace:suite-native/qr-code" @@ -9855,6 +9874,7 @@ __metadata: "@suite-native/feature-flags": "workspace:*" "@suite-native/graph": "workspace:*" "@suite-native/message-system": "workspace:*" + "@suite-native/passphrase": "workspace:*" "@suite-native/settings": "workspace:*" "@suite-native/storage": "workspace:*" "@suite-native/toasts": "workspace:*" From da7b70ccc2adb3bd09c0da6c7312733945ed8ca1 Mon Sep 17 00:00:00 2001 From: juriczech Date: Fri, 17 May 2024 14:20:40 +0200 Subject: [PATCH 3/4] fix(suite-native): avoid type casting in thunks --- suite-common/wallet-core/src/device/deviceReducer.ts | 2 +- suite-common/wallet-core/src/device/deviceThunks.ts | 11 +++++------ suite-native/intl/src/en.ts | 6 +++--- .../passphrase/src/useHandleDuplicitPassphrase.ts | 6 +++--- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/suite-common/wallet-core/src/device/deviceReducer.ts b/suite-common/wallet-core/src/device/deviceReducer.ts index 231d668ec29..d70023f22e4 100644 --- a/suite-common/wallet-core/src/device/deviceReducer.ts +++ b/suite-common/wallet-core/src/device/deviceReducer.ts @@ -525,7 +525,7 @@ export const prepareDeviceReducer = createReducerWithExtraDeps(initialState, (bu .addCase(authorizeDevice.rejected, (state, action) => { if (action.payload && action.payload.error) { const { error } = action.payload; - if (error === 'auth-failed') { + if (error === 'auth-failed' && action.payload.device) { authFailed(state, action.payload.device); } } diff --git a/suite-common/wallet-core/src/device/deviceThunks.ts b/suite-common/wallet-core/src/device/deviceThunks.ts index 0f189d2a3c0..f9b53e3bab4 100644 --- a/suite-common/wallet-core/src/device/deviceThunks.ts +++ b/suite-common/wallet-core/src/device/deviceThunks.ts @@ -276,7 +276,7 @@ export const acquireDevice = createThunk( type AuthorizeDeviceParams = { shouldIgnoreDeviceState: boolean } | undefined; export type AuthorizeDeviceError = { error: string; - device: TrezorDevice; + device?: TrezorDevice; duplicate?: TrezorDevice; }; type AuthorizeDeviceSuccess = { device: TrezorDevice; state: string }; @@ -301,7 +301,7 @@ export const authorizeDevice = createThunk< const selectedCheckFirmwareAuthenticity = selectCheckFirmwareAuthenticity(getState()); const device = selectDeviceSelector(getState()); - if (!device) return rejectWithValue({ error: 'no-device' } as AuthorizeDeviceError); + if (!device) return rejectWithValue({ error: 'no-device' }); const isDeviceReady = device.connected && @@ -312,8 +312,7 @@ export const authorizeDevice = createThunk< device.mode === 'normal' && device.firmware !== 'required'; - if (!isDeviceReady) - return rejectWithValue({ error: 'device-not-ready', device } as AuthorizeDeviceError); + if (!isDeviceReady) return rejectWithValue({ error: 'device-not-ready', device }); if (selectedCheckFirmwareAuthenticity) { await dispatch(checkFirmwareAuthenticity()); @@ -356,7 +355,7 @@ export const authorizeDevice = createThunk< error: 'passphrase-duplicate', device, duplicate, - } as AuthorizeDeviceError); + }); } return { device: freshDeviceData as TrezorDevice, state }; @@ -372,7 +371,7 @@ export const authorizeDevice = createThunk< return rejectWithValue({ error: 'auth-failed', device: device as TrezorDevice, - } as AuthorizeDeviceError); + }); }, ); diff --git a/suite-native/intl/src/en.ts b/suite-native/intl/src/en.ts index 4a66ce4939d..4e25a872c41 100644 --- a/suite-native/intl/src/en.ts +++ b/suite-native/intl/src/en.ts @@ -704,9 +704,9 @@ export const en = { }, }, passphraseMismatch: { - title: 'Passphrase mismatch', - subtitle: 'Start over and enter your passphrase.', - button: 'Start over', + title: 'Passphrase duplicate', + subtitle: 'You’re trying to enter a passphrase wallet that’s already been opened.', + button: 'Proceed to passphrase wallet ', }, }, }; diff --git a/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts b/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts index 90c2740c4b9..263f32c0cc9 100644 --- a/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts +++ b/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts @@ -37,10 +37,10 @@ export const useHandleDuplicitPassphrase = () => { const { showAlert, hideAlert } = useAlert(); const handleDuplicateDevicePassphrase = useCallback( - ({ device, duplicate }: { device: TrezorDevice; duplicate?: TrezorDevice }) => { - // Not all passphrase errors have duplicate property, but we know this one does + ({ device, duplicate }: { device?: TrezorDevice; duplicate?: TrezorDevice }) => { + // Not all passphrase errors have device property, but we know this one does // based on condition in `./passphraseSlice`. This if is just to keep TS happy. - if (duplicate) { + if (duplicate && device) { dispatch(switchDuplicatedDevice({ device, duplicate })); navigation.navigate(RootStackRoutes.AppTabs, { screen: AppTabsRoutes.HomeStack, From bced01df7c0e708665fb08a58c41390e715d3c61 Mon Sep 17 00:00:00 2001 From: juriczech Date: Mon, 20 May 2024 16:16:08 +0200 Subject: [PATCH 4/4] refactor(suite): remove redundant comment --- .../suite/__fixtures__/suiteActions.ts | 22 +++++++++---------- .../suite/__tests__/suiteActions.test.ts | 4 ++-- .../PassphraseDuplicateModal.tsx | 5 +++-- .../AccountException/AuthFailed.tsx | 4 ++-- .../middlewares/suite/analyticsMiddleware.ts | 5 ++--- .../src/middlewares/suite/eventsMiddleware.ts | 5 ++--- .../src/middlewares/suite/logsMiddleware.ts | 7 +++--- .../src/middlewares/suite/sentryMiddleware.ts | 4 ++-- .../src/middlewares/suite/suiteMiddleware.ts | 6 ++--- .../middlewares/wallet/discoveryMiddleware.ts | 9 ++++---- .../suite/__fixtures__/deviceReducer.ts | 12 +++++----- packages/suite/src/utils/suite/logsUtils.ts | 5 ++--- .../PortfolioCard/components/Exception.tsx | 4 ++-- suite-common/toast-notifications/src/types.ts | 4 ---- .../wallet-core/src/device/deviceReducer.ts | 10 ++++----- .../wallet-core/src/device/deviceThunks.ts | 4 ++-- .../src/hooks/useHandleDeviceConnection.ts | 4 ++-- .../src/middlewares/deviceMiddleware.ts | 6 ++--- .../discovery/src/discoveryMiddleware.ts | 8 +++---- .../src/components/PinFormControlButtons.tsx | 4 ++-- .../screens/ConnectAndUnlockDeviceScreen.tsx | 8 +++++-- .../navigation/PassphraseStackNavigator.tsx | 4 ++-- suite-native/passphrase/src/index.ts | 2 +- .../passphrase/src/passphraseSlice.ts | 6 ++--- .../passphrase/src/passphraseThunks.ts | 4 ++-- ...ase.ts => useHandleDuplicatePassphrase.ts} | 7 +++--- 26 files changed, 78 insertions(+), 85 deletions(-) rename suite-native/passphrase/src/{useHandleDuplicitPassphrase.ts => useHandleDuplicatePassphrase.ts} (93%) diff --git a/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts b/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts index 1be24e4987b..67fdcd0dfeb 100644 --- a/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts +++ b/packages/suite/src/actions/suite/__fixtures__/suiteActions.ts @@ -1,5 +1,5 @@ import { testMocks } from '@suite-common/test-utils'; -import { discoveryActions, deviceActions, authorizeDevice } from '@suite-common/wallet-core'; +import { discoveryActions, deviceActions, authorizeDeviceThunk } from '@suite-common/wallet-core'; import { DEVICE, TRANSPORT } from '@trezor/connect'; import { notificationsActions } from '@suite-common/toast-notifications'; @@ -768,14 +768,14 @@ const authorizeDeviceActions = [ { description: `without device`, state: {}, - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, }, { description: `with disconnected device`, state: { selectedDevice: getSuiteDevice(), }, - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, }, { description: `with unacquired device`, @@ -785,7 +785,7 @@ const authorizeDeviceActions = [ connected: true, }), }, - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, }, { description: `with device which already has state`, @@ -795,7 +795,7 @@ const authorizeDeviceActions = [ state: '012345', }), }, - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, }, { description: `with device in unexpected mode`, @@ -805,7 +805,7 @@ const authorizeDeviceActions = [ mode: 'bootloader', }), }, - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, }, { description: `with device which needs FW update`, @@ -815,7 +815,7 @@ const authorizeDeviceActions = [ firmware: 'required', }), }, - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, }, { description: `success`, @@ -824,7 +824,7 @@ const authorizeDeviceActions = [ connected: true, }), }, - result: authorizeDevice.fulfilled.type, + result: authorizeDeviceThunk.fulfilled.type, }, { description: `duplicate detected`, @@ -849,7 +849,7 @@ const authorizeDeviceActions = [ state: undefined, }), ], - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, deviceReducerResult: [ getSuiteDevice({ connected: true, @@ -891,7 +891,7 @@ const authorizeDeviceActions = [ state: undefined, }), ], - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, deviceReducerResult: [ getSuiteDevice({ connected: true, @@ -920,7 +920,7 @@ const authorizeDeviceActions = [ error: 'getDeviceState error', }, }, - result: authorizeDevice.rejected.type, + result: authorizeDeviceThunk.rejected.type, }, ]; diff --git a/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts b/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts index 83d42656ad5..8973a4ac757 100644 --- a/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts +++ b/packages/suite/src/actions/suite/__tests__/suiteActions.test.ts @@ -10,7 +10,7 @@ import { deviceActions, acquireDevice, authConfirm, - authorizeDevice, + authorizeDeviceThunk, createDeviceInstance, forgetDisconnectedDevices, handleDeviceConnect, @@ -245,7 +245,7 @@ describe('Suite Actions', () => { devices: f.devicesState ?? [], }); const store = initStore(state); - await store.dispatch(authorizeDevice()); + await store.dispatch(authorizeDeviceThunk()); if (!f.result) { expect(filterThunkActionTypes(store.getActions()).length).toEqual(0); } else { diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/PassphraseDuplicateModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/PassphraseDuplicateModal.tsx index 9d32a561e3b..6e538bd723b 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/PassphraseDuplicateModal.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/PassphraseDuplicateModal.tsx @@ -1,6 +1,7 @@ -import { authorizeDevice, switchDuplicatedDevice } from '@suite-common/wallet-core'; import { Button, Column, H3, Text } from '@trezor/components'; +import { authorizeDeviceThunk, switchDuplicatedDevice } from '@suite-common/wallet-core'; + import { Translation } from 'src/components/suite'; import { useDevice, useDispatch } from 'src/hooks/suite'; import { TrezorDevice } from 'src/types/suite'; @@ -18,7 +19,7 @@ export const PassphraseDuplicateModal = ({ device, duplicate }: PassphraseDuplic const isDeviceLocked = isLocked(); const handleSwitchDevice = () => dispatch(switchDuplicatedDevice({ device, duplicate })); - const handleAuthorizeDevice = () => dispatch(authorizeDevice()); + const handleAuthorizeDevice = () => dispatch(authorizeDeviceThunk()); return ( diff --git a/packages/suite/src/components/wallet/WalletLayout/AccountException/AuthFailed.tsx b/packages/suite/src/components/wallet/WalletLayout/AccountException/AuthFailed.tsx index e5809630edf..342f95ea337 100644 --- a/packages/suite/src/components/wallet/WalletLayout/AccountException/AuthFailed.tsx +++ b/packages/suite/src/components/wallet/WalletLayout/AccountException/AuthFailed.tsx @@ -1,4 +1,4 @@ -import { authorizeDevice } from '@suite-common/wallet-core'; +import { authorizeDeviceThunk } from '@suite-common/wallet-core'; import { useDevice, useDispatch } from 'src/hooks/suite'; import { Translation } from 'src/components/suite'; @@ -8,7 +8,7 @@ export const AuthFailed = () => { const dispatch = useDispatch(); const { isLocked } = useDevice(); - const handleClick = () => dispatch(authorizeDevice()); + const handleClick = () => dispatch(authorizeDeviceThunk()); return ( { if ( isAnyOf( - authorizeDevice.fulfilled, - authorizeDevice.rejected, + authorizeDeviceThunk.fulfilled, + authorizeDeviceThunk.rejected, deviceActions.selectDevice, deviceActions.receiveAuthConfirm, deviceActions.updatePassphraseMode, diff --git a/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts b/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts index 35c9bcf8316..a2ee83e8cbc 100644 --- a/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts +++ b/packages/suite/src/middlewares/wallet/discoveryMiddleware.ts @@ -1,5 +1,5 @@ import { - authorizeDevice, + authorizeDeviceThunk, deviceActions, selectDevice, selectDeviceDiscovery, @@ -10,7 +10,6 @@ import { stopDiscoveryThunk, updateNetworkSettingsThunk, } from '@suite-common/wallet-core'; -import { isAnyOf } from '@reduxjs/toolkit'; import * as discoveryActions from '@suite-common/wallet-core'; import { UI } from '@trezor/connect'; import { DiscoveryStatus } from '@suite-common/wallet-constants'; @@ -107,11 +106,11 @@ export const prepareDiscoveryMiddleware = createMiddlewareWithExtraDeps( // 3. begin auth process if (authorizationIntent) { - dispatch(authorizeDevice()); + dispatch(authorizeDeviceThunk()); } // 4. device state received - if (isAnyOf(authorizeDevice.fulfilled)(action)) { + if (authorizeDeviceThunk.fulfilled.match(action)) { // `device` is always present here // to avoid typescript conditioning use device from action as a fallback (never used) dispatch( @@ -139,7 +138,7 @@ export const prepareDiscoveryMiddleware = createMiddlewareWithExtraDeps( becomesConnected || action.type === SUITE.APP_CHANGED || deviceActions.selectDevice.match(action) || - isAnyOf(authorizeDevice.fulfilled)(action) || + authorizeDeviceThunk.fulfilled.match(action) || walletSettingsActions.changeNetworks.match(action) || accountsActions.changeAccountVisibility.match(action) ) { diff --git a/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts b/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts index a34f6e820ce..912c0fa2554 100644 --- a/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts +++ b/packages/suite/src/reducers/suite/__fixtures__/deviceReducer.ts @@ -1,5 +1,5 @@ import { testMocks } from '@suite-common/test-utils'; -import { authorizeDevice, deviceActions } from '@suite-common/wallet-core'; +import { authorizeDeviceThunk, deviceActions } from '@suite-common/wallet-core'; import { DEVICE } from '@trezor/connect'; const { getConnectDevice, getSuiteDevice } = testMocks; @@ -850,7 +850,7 @@ const authDevice = [ initialState: { devices: [SUITE_DEVICE] }, actions: [ { - type: authorizeDevice.fulfilled.type, + type: authorizeDeviceThunk.fulfilled.type, payload: { device: SUITE_DEVICE, state: 'A', @@ -875,7 +875,7 @@ const authDevice = [ }, actions: [ { - type: authorizeDevice.fulfilled.type, + type: authorizeDeviceThunk.fulfilled.type, payload: { device: SUITE_DEVICE, state: 'A', @@ -904,7 +904,7 @@ const authDevice = [ }, actions: [ { - type: authorizeDevice.fulfilled.type, + type: authorizeDeviceThunk.fulfilled.type, payload: { device: getSuiteDevice({ instance: 1 }), state: 'A', @@ -933,7 +933,7 @@ const authDevice = [ initialState: { devices: [SUITE_DEVICE] }, actions: [ { - type: authorizeDevice.fulfilled.type, + type: authorizeDeviceThunk.fulfilled.type, payload: { device: getConnectDevice({ type: 'unacquired', @@ -953,7 +953,7 @@ const authDevice = [ initialState: { devices: [] }, actions: [ { - type: authorizeDevice.fulfilled.type, + type: authorizeDeviceThunk.fulfilled.type, payload: { device: SUITE_DEVICE, state: 'A', diff --git a/packages/suite/src/utils/suite/logsUtils.ts b/packages/suite/src/utils/suite/logsUtils.ts index b85c6902010..e3cafc074a9 100644 --- a/packages/suite/src/utils/suite/logsUtils.ts +++ b/packages/suite/src/utils/suite/logsUtils.ts @@ -3,9 +3,8 @@ import { discoveryActions, accountsActions, selectDevices, - authorizeDevice, + authorizeDeviceThunk, } from '@suite-common/wallet-core'; -import { isAnyOf } from '@reduxjs/toolkit'; import { getEnvironment, getBrowserName, @@ -124,7 +123,7 @@ export const redactAction = (action: LogEntry) => { }; } - if (isAnyOf(authorizeDevice.fulfilled)(action)) { + if (authorizeDeviceThunk.fulfilled.match(action)) { payload = { state: REDACTED_REPLACEMENT, ...redactDevice(action.payload.device), diff --git a/packages/suite/src/views/dashboard/components/PortfolioCard/components/Exception.tsx b/packages/suite/src/views/dashboard/components/PortfolioCard/components/Exception.tsx index 94fc621a96b..584544e5597 100644 --- a/packages/suite/src/views/dashboard/components/PortfolioCard/components/Exception.tsx +++ b/packages/suite/src/views/dashboard/components/PortfolioCard/components/Exception.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components'; import { authConfirm, - authorizeDevice, + authorizeDeviceThunk, restartDiscoveryThunk as restartDiscovery, } from '@suite-common/wallet-core'; import * as accountUtils from '@suite-common/wallet-utils'; @@ -135,7 +135,7 @@ export const Exception = ({ exception, discovery }: ExceptionProps) => { title="TR_ACCOUNT_EXCEPTION_AUTH_ERROR" description="TR_ACCOUNT_EXCEPTION_AUTH_ERROR_DESC" cta={{ - action: () => dispatch(authorizeDevice()), + action: () => dispatch(authorizeDeviceThunk()), }} dataTestBase={exception.type} /> diff --git a/suite-common/toast-notifications/src/types.ts b/suite-common/toast-notifications/src/types.ts index 821838613df..389a41ce23f 100644 --- a/suite-common/toast-notifications/src/types.ts +++ b/suite-common/toast-notifications/src/types.ts @@ -140,10 +140,6 @@ export type ToastPayload = ( export const AUTH_DEVICE = 'auth-device'; export type NotificationEventPayload = ( | { - // only temporary, must be same as AUTH_DEVICE value in packages/suite/src/actions/suite/constants/suiteConstants.ts - // once that will be migrated to @suite-common, this should be replaced directly by suiteActions.authDevice.type - // this should not break type safety, if someone will change value of AUTH_DEVICE, it will throw error in place - // where action is used and you will need to change it also here type: typeof AUTH_DEVICE; } | ReceivedTransactionNotification diff --git a/suite-common/wallet-core/src/device/deviceReducer.ts b/suite-common/wallet-core/src/device/deviceReducer.ts index d70023f22e4..294ca164043 100644 --- a/suite-common/wallet-core/src/device/deviceReducer.ts +++ b/suite-common/wallet-core/src/device/deviceReducer.ts @@ -15,7 +15,7 @@ import { import { isNative } from '@trezor/env-utils'; import { deviceActions } from './deviceActions'; -import { authorizeDevice } from './deviceThunks'; +import { authorizeDeviceThunk } from './deviceThunks'; import { PORTFOLIO_TRACKER_DEVICE_ID } from './deviceConstants'; export type State = { @@ -341,7 +341,7 @@ const authFailed = (draft: State, device: TrezorDevice) => { }; /** - * Action handler: authorizeDevice.pending + * Action handler: authorizeDeviceThunk.pending * Reset authFailed flag * @param {State} draft * @returns @@ -516,13 +516,13 @@ export const prepareDeviceReducer = createReducerWithExtraDeps(initialState, (bu .addCase(deviceActions.updatePassphraseMode, (state, { payload }) => { changePassphraseMode(state, payload.device, payload.hidden, payload.alwaysOnDevice); }) - .addCase(authorizeDevice.pending, state => { + .addCase(authorizeDeviceThunk.pending, state => { resetAuthFailed(state); }) - .addCase(authorizeDevice.fulfilled, (state, { payload }) => { + .addCase(authorizeDeviceThunk.fulfilled, (state, { payload }) => { authDevice(state, payload.device, payload.state); }) - .addCase(authorizeDevice.rejected, (state, action) => { + .addCase(authorizeDeviceThunk.rejected, (state, action) => { if (action.payload && action.payload.error) { const { error } = action.payload; if (error === 'auth-failed' && action.payload.device) { diff --git a/suite-common/wallet-core/src/device/deviceThunks.ts b/suite-common/wallet-core/src/device/deviceThunks.ts index f9b53e3bab4..b0f643e984b 100644 --- a/suite-common/wallet-core/src/device/deviceThunks.ts +++ b/suite-common/wallet-core/src/device/deviceThunks.ts @@ -275,13 +275,13 @@ export const acquireDevice = createThunk( */ type AuthorizeDeviceParams = { shouldIgnoreDeviceState: boolean } | undefined; export type AuthorizeDeviceError = { - error: string; + error: 'no-device' | 'device-not-ready' | 'auth-failed' | 'passphrase-duplicate'; device?: TrezorDevice; duplicate?: TrezorDevice; }; type AuthorizeDeviceSuccess = { device: TrezorDevice; state: string }; -export const authorizeDevice = createThunk< +export const authorizeDeviceThunk = createThunk< AuthorizeDeviceSuccess, AuthorizeDeviceParams, { rejectValue: AuthorizeDeviceError } diff --git a/suite-native/device/src/hooks/useHandleDeviceConnection.ts b/suite-native/device/src/hooks/useHandleDeviceConnection.ts index 85bc76f6a94..5c6e1bb9f4a 100644 --- a/suite-native/device/src/hooks/useHandleDeviceConnection.ts +++ b/suite-native/device/src/hooks/useHandleDeviceConnection.ts @@ -13,13 +13,13 @@ import { StackToStackCompositeNavigationProps, } from '@suite-native/navigation'; import { - authorizeDevice, selectIsPortfolioTrackerDevice, selectDeviceRequestedPin, selectIsDeviceConnected, selectIsDeviceConnectedAndAuthorized, selectIsNoPhysicalDeviceConnected, selectIsDeviceUsingPassphrase, + authorizeDeviceThunk, } from '@suite-common/wallet-core'; import { selectIsOnboardingFinished } from '@suite-native/settings'; import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex'; @@ -53,7 +53,7 @@ export const useHandleDeviceConnection = () => { !isDeviceConnectedAndAuthorized && !isBiometricsOverlayVisible ) { - requestPrioritizedDeviceAccess(() => dispatch(authorizeDevice())); + requestPrioritizedDeviceAccess(() => dispatch(authorizeDeviceThunk())); // Note: Passphrase protected device (excluding empty passphrase, e. g. standard wallet with passphrase protection on device), // post auth navigation is handled in @suite-native/module-passphrase for custom UX flow. diff --git a/suite-native/device/src/middlewares/deviceMiddleware.ts b/suite-native/device/src/middlewares/deviceMiddleware.ts index f31909c3a79..309b32d0db1 100644 --- a/suite-native/device/src/middlewares/deviceMiddleware.ts +++ b/suite-native/device/src/middlewares/deviceMiddleware.ts @@ -4,7 +4,7 @@ import { createMiddlewareWithExtraDeps } from '@suite-common/redux-utils'; import { DEVICE } from '@trezor/connect'; import { accountsActions, - authorizeDevice, + authorizeDeviceThunk, deviceActions, forgetDisconnectedDevices, handleDeviceDisconnect, @@ -18,8 +18,8 @@ import { clearAndUnlockDeviceAccessQueue } from '@suite-native/device-mutex'; const isActionDeviceRelated = (action: AnyAction): boolean => { if ( isAnyOf( - authorizeDevice.fulfilled, - authorizeDevice.rejected, + authorizeDeviceThunk.fulfilled, + authorizeDeviceThunk.rejected, deviceActions.selectDevice, deviceActions.receiveAuthConfirm, deviceActions.updatePassphraseMode, diff --git a/suite-native/discovery/src/discoveryMiddleware.ts b/suite-native/discovery/src/discoveryMiddleware.ts index 05af9d38c5f..3454b534fc9 100644 --- a/suite-native/discovery/src/discoveryMiddleware.ts +++ b/suite-native/discovery/src/discoveryMiddleware.ts @@ -1,12 +1,10 @@ -import { isAnyOf } from '@reduxjs/toolkit'; - import { deviceActions, selectDevice, discoveryActions, selectDeviceModel, selectDeviceFirmwareVersion, - authorizeDevice, + authorizeDeviceThunk, } from '@suite-common/wallet-core'; import { createMiddlewareWithExtraDeps } from '@suite-common/redux-utils'; import { isFirmwareVersionSupported } from '@suite-native/device'; @@ -43,12 +41,12 @@ export const prepareDiscoveryMiddleware = createMiddlewareWithExtraDeps( } } - // We need to wait until `authorizeDevice` action is fulfilled, because we need + // We need to wait until `authorizeDeviceThunk` action is fulfilled, because we need // to know the device state when starting discovery of newly authorized device. next(action); // On successful authorization, create discovery instance and run it. - if (isAnyOf(authorizeDevice.fulfilled)(action) && isDeviceFirmwareVersionSupported) { + if (authorizeDeviceThunk.fulfilled.match(action) && isDeviceFirmwareVersionSupported) { dispatch( startDescriptorPreloadedDiscoveryThunk({ deviceState: action.payload.state, diff --git a/suite-native/module-connect-device/src/components/PinFormControlButtons.tsx b/suite-native/module-connect-device/src/components/PinFormControlButtons.tsx index 078419d5f3b..0fc17cfd255 100644 --- a/suite-native/module-connect-device/src/components/PinFormControlButtons.tsx +++ b/suite-native/module-connect-device/src/components/PinFormControlButtons.tsx @@ -26,7 +26,7 @@ import { selectDevice, selectDeviceAuthFailed, removeButtonRequests, - authorizeDevice, + authorizeDeviceThunk, } from '@suite-common/wallet-core'; import { useAlert } from '@suite-native/alerts'; import { useOpenLink } from '@suite-native/link'; @@ -104,7 +104,7 @@ export const PinFormControlButtons = () => { onPressPrimaryButton: () => { if (hasDeviceAuthFailed) { // Ask for new PIN entry after 3 wrong attempts. - requestPrioritizedDeviceAccess(() => dispatch(authorizeDevice())); + requestPrioritizedDeviceAccess(() => dispatch(authorizeDeviceThunk())); } }, secondaryButtonTitle: ( diff --git a/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx b/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx index dd09a981e9a..6796629722a 100644 --- a/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx +++ b/suite-native/module-connect-device/src/screens/ConnectAndUnlockDeviceScreen.tsx @@ -9,7 +9,11 @@ import { Translation } from '@suite-native/intl'; import { ConnectDeviceAnimation } from '@suite-native/device'; import { prepareNativeStyle, useNativeStyles } from '@trezor/styles'; import { Screen } from '@suite-native/navigation'; -import { selectDevice, selectIsDeviceAuthorized, authorizeDevice } from '@suite-common/wallet-core'; +import { + selectDevice, + selectIsDeviceAuthorized, + authorizeDeviceThunk, +} from '@suite-common/wallet-core'; import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex'; import { ConnectDeviceScreenHeader } from '../components/ConnectDeviceScreenHeader'; @@ -40,7 +44,7 @@ export const ConnectAndUnlockDeviceScreen = () => { useEffect(() => { if (isFocused && device && !isDeviceAuthorized) { // When user cancelled the authorization, we need to authorize the device again. - requestPrioritizedDeviceAccess(() => dispatch(authorizeDevice())); + requestPrioritizedDeviceAccess(() => dispatch(authorizeDeviceThunk())); } }, [isDeviceAuthorized, device, dispatch, isFocused]); diff --git a/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx b/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx index 7589a7cd6a2..0de1969575a 100644 --- a/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx +++ b/suite-native/module-passphrase/src/navigation/PassphraseStackNavigator.tsx @@ -5,7 +5,7 @@ import { PassphraseStackRoutes, stackNavigationOptionsConfig, } from '@suite-native/navigation'; -import { useHandleDuplicitPassphrase } from '@suite-native/passphrase'; +import { useHandleDuplicatePassphrase } from '@suite-native/passphrase'; import { PassphraseFormScreen } from '../screens/PassphraseFormScreen'; import { PassphraseLoadingScreen } from '../screens/PassphraseLoadingScreen'; @@ -17,7 +17,7 @@ import { PassphraseEnterOnTrezorScreen } from '../screens/PassphraseEnterOnTrezo export const PassphraseStack = createNativeStackNavigator(); export const PassphraseStackNavigator = () => { - useHandleDuplicitPassphrase(); + useHandleDuplicatePassphrase(); return ( diff --git a/suite-native/passphrase/src/index.ts b/suite-native/passphrase/src/index.ts index 023c0f1fc29..65ffdefe3a1 100644 --- a/suite-native/passphrase/src/index.ts +++ b/suite-native/passphrase/src/index.ts @@ -1,3 +1,3 @@ export * from './passphraseSlice'; export * from './passphraseThunks'; -export * from './useHandleDuplicitPassphrase'; +export * from './useHandleDuplicatePassphrase'; diff --git a/suite-native/passphrase/src/passphraseSlice.ts b/suite-native/passphrase/src/passphraseSlice.ts index 9a36a892fa0..d191d390ecd 100644 --- a/suite-native/passphrase/src/passphraseSlice.ts +++ b/suite-native/passphrase/src/passphraseSlice.ts @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; -import { AuthorizeDeviceError, authorizeDevice } from '@suite-common/wallet-core'; +import { AuthorizeDeviceError, authorizeDeviceThunk } from '@suite-common/wallet-core'; type PassphraseState = { error: AuthorizeDeviceError | null; @@ -20,10 +20,10 @@ export const passphraseSlice = createSlice({ reducers: {}, extraReducers: builder => { builder - .addCase(authorizeDevice.pending, state => { + .addCase(authorizeDeviceThunk.pending, state => { state.error = null; }) - .addCase(authorizeDevice.rejected, (state, { payload }) => { + .addCase(authorizeDeviceThunk.rejected, (state, { payload }) => { if (payload?.error === 'passphrase-duplicate') { state.error = payload; } diff --git a/suite-native/passphrase/src/passphraseThunks.ts b/suite-native/passphrase/src/passphraseThunks.ts index 90f277f50d2..42860c4c0b8 100644 --- a/suite-native/passphrase/src/passphraseThunks.ts +++ b/suite-native/passphrase/src/passphraseThunks.ts @@ -1,6 +1,6 @@ import { createThunk } from '@suite-common/redux-utils'; import { - authorizeDevice, + authorizeDeviceThunk, deviceActions, selectDevice, selectDeviceThunk, @@ -65,6 +65,6 @@ export const retryPassphraseAuthenticationThunk = createThunk( if (!device) return; dispatch(deviceActions.removeButtonRequests({ device })); - dispatch(authorizeDevice({ shouldIgnoreDeviceState: true })); + dispatch(authorizeDeviceThunk({ shouldIgnoreDeviceState: true })); }, ); diff --git a/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts b/suite-native/passphrase/src/useHandleDuplicatePassphrase.ts similarity index 93% rename from suite-native/passphrase/src/useHandleDuplicitPassphrase.ts rename to suite-native/passphrase/src/useHandleDuplicatePassphrase.ts index 263f32c0cc9..f7ee1cdfd35 100644 --- a/suite-native/passphrase/src/useHandleDuplicitPassphrase.ts +++ b/suite-native/passphrase/src/useHandleDuplicatePassphrase.ts @@ -25,7 +25,7 @@ type NavigationProp = StackToStackCompositeNavigationProps< RootStackParamList >; -export const useHandleDuplicitPassphrase = () => { +export const useHandleDuplicatePassphrase = () => { const dispatch = useDispatch(); const passphraseError = useSelector(selectPassphraseError); @@ -34,7 +34,7 @@ export const useHandleDuplicitPassphrase = () => { const navigation = useNavigation(); - const { showAlert, hideAlert } = useAlert(); + const { showAlert } = useAlert(); const handleDuplicateDevicePassphrase = useCallback( ({ device, duplicate }: { device?: TrezorDevice; duplicate?: TrezorDevice }) => { @@ -48,10 +48,9 @@ export const useHandleDuplicitPassphrase = () => { screen: HomeStackRoutes.Home, }, }); - hideAlert(); } }, - [dispatch, hideAlert, navigation], + [dispatch, navigation], ); useEffect(() => {