import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared'; import { DeviceSessionPinType } from '@onekeyfe/hd-transport'; import { LoggerNames, getLogger } from '../../utils'; import { isProtocolV2UiEnabled, resolveProtocolV2UiInteraction } from './uiInteraction'; import { refreshProtocolV2DeviceStatus } from './walletSession'; import type { BaseMethod } from '../../api/BaseMethod'; import type { Device } from '../../device/Device'; import type { HardwareUiInteractionMeta } from '../../events'; import type { ProtocolV2UiInteractionCoordinator } from './uiInteraction'; const Log = getLogger(LoggerNames.Core); export type ProtocolV2UnlockContext = { preflightCompleted: boolean; preflightStatusRefreshed?: boolean; preflightMainPinSelected?: boolean; }; export const createProtocolV2UnlockContext = (): ProtocolV2UnlockContext => ({ preflightCompleted: false, preflightStatusRefreshed: false, preflightMainPinSelected: false, }); type RunnableMethod = Pick< BaseMethod, | 'run' | 'unlockPolicy' | 'useDevicePassphraseState' | 'protocolV2UiInteraction' | 'protocolV2UiMode' | 'protocolV2PreUnlockPinType' | 'params' | 'payload' > & { name?: string; protocolV2UnlockContext?: ProtocolV2UnlockContext; }; type UiInteractionCoordinator = Pick< ProtocolV2UiInteractionCoordinator, 'enterMethodInteraction' | 'enterUnlockInteraction' >; type RunMethodWithUnlockPolicyOptions = { context?: ProtocolV2UnlockContext; uiCoordinator?: UiInteractionCoordinator; afterStatusBeforeUnlock?: () => void | Promise; prepare?: () => Promise; run?: () => Promise; }; const resolvePreUnlockPinType = (method: RunnableMethod) => { if (method.protocolV2PreUnlockPinType !== undefined) { return method.protocolV2PreUnlockPinType; } const targetsKnownHiddenWallet = method.useDevicePassphraseState && method.payload?.useEmptyPassphrase !== true && typeof method.payload?.passphraseState === 'string' && method.payload.passphraseState.length > 0; // A hidden-wallet call may be unlocked by either its Attach PIN or the main // PIN followed by passphrase selection. Wallet-session verification still // rejects a different hidden wallet after the device is unlocked. return targetsKnownHiddenWallet ? DeviceSessionPinType.Any : DeviceSessionPinType.Main; }; export async function runMethodWithUnlockPolicy( method: RunnableMethod, device: Device, options: RunMethodWithUnlockPolicyOptions = {} ): Promise { const { context = createProtocolV2UnlockContext(), uiCoordinator, afterStatusBeforeUnlock, prepare, run: configuredRun, } = options; const run = configuredRun ?? (() => method.run() as Promise); method.protocolV2UnlockContext = context; const shouldEmitUi = isProtocolV2UiEnabled(method); const shouldCoordinateUi = shouldEmitUi && uiCoordinator !== undefined; const requiresPreUnlock = device.isProtocolV2() && (method.useDevicePassphraseState || method.unlockPolicy === 'unlock-before-run') && device.state?.status.initialized !== false && !device.isBootloader?.() && !device.isRomloader?.(); if (requiresPreUnlock && !context.preflightCompleted) { const status = await refreshProtocolV2DeviceStatus(device); if (typeof status?.unlocked !== 'boolean') { throw ERRORS.TypedError( HardwareErrorCode.RuntimeError, 'Protocol V2 DeviceStatus did not report an explicit unlock state.' ); } await afterStatusBeforeUnlock?.(); if (!status.unlocked) { const preUnlockPinType = resolvePreUnlockPinType(method); const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi ? uiCoordinator.enterUnlockInteraction(method.name) : undefined; const unlockedStatus = await device.unlockDevice( preUnlockPinType, shouldCoordinateUi ? { emitUiEvent: false, interaction: unlockInteraction } : { source: 'unlock-coordinator', reason: 'device-locked', deviceOnly: true, method: method.name, } ); if (unlockedStatus?.unlocked !== true) { throw ERRORS.TypedError( HardwareErrorCode.RuntimeError, 'Protocol V2 device remained locked after the unlock flow.' ); } context.preflightMainPinSelected = preUnlockPinType === DeviceSessionPinType.Main; Log.debug('Protocol V2 pre-unlock completed', { method: method.name }); } context.preflightStatusRefreshed = true; context.preflightCompleted = true; } await prepare?.(); if (shouldCoordinateUi) { uiCoordinator.enterMethodInteraction(resolveProtocolV2UiInteraction(method)); } return run(); }