@onekeyfe/hd-core 1.2.0-alpha.1 → 1.2.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/__tests__/DeviceCommands.test.ts +25 -0
  2. package/__tests__/RequestQueue.test.ts +34 -0
  3. package/__tests__/connectSettings.test.ts +19 -0
  4. package/__tests__/device-wallet-session-store.test.ts +131 -0
  5. package/__tests__/preInitialize.test.ts +33 -0
  6. package/__tests__/pro2Wallpaper.test.ts +50 -0
  7. package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
  8. package/__tests__/protocol-v2.test.ts +3253 -1605
  9. package/dist/api/BaseMethod.d.ts +4 -0
  10. package/dist/api/BaseMethod.d.ts.map +1 -1
  11. package/dist/api/ClearSessionCache.d.ts +9 -0
  12. package/dist/api/ClearSessionCache.d.ts.map +1 -0
  13. package/dist/api/FileRead.d.ts.map +1 -1
  14. package/dist/api/FileWrite.d.ts.map +1 -1
  15. package/dist/api/FirmwareUpdateV4.d.ts +20 -3
  16. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  17. package/dist/api/GetPassphraseState.d.ts +1 -6
  18. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  19. package/dist/api/UploadPortfolio.d.ts +18 -0
  20. package/dist/api/UploadPortfolio.d.ts.map +1 -0
  21. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +1 -0
  22. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  23. package/dist/api/firmware/getBinary.d.ts +4 -0
  24. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  25. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  26. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  27. package/dist/api/index.d.ts +13 -10
  28. package/dist/api/index.d.ts.map +1 -1
  29. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts +6 -0
  30. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts.map +1 -0
  31. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts +7 -0
  32. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts.map +1 -0
  33. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +1 -1
  34. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  35. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +3 -2
  36. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  37. package/dist/api/protocol-v2/{DeviceGetDeviceInfo.d.ts → DeviceInfoGet.d.ts} +10 -10
  38. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -0
  39. package/dist/api/protocol-v2/DeviceSessionGet.d.ts +6 -0
  40. package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -0
  41. package/dist/api/protocol-v2/DeviceSettingsGet.d.ts +6 -0
  42. package/dist/api/protocol-v2/DeviceSettingsGet.d.ts.map +1 -0
  43. package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts +16 -0
  44. package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -0
  45. package/dist/api/protocol-v2/DeviceSettingsSet.d.ts +12 -0
  46. package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -0
  47. package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
  48. package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
  49. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +26 -0
  50. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -0
  51. package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
  52. package/dist/api/protocol-v2/{FilesystemFixPermission.d.ts → FilesystemPermissionFix.d.ts} +2 -2
  53. package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts.map +1 -0
  54. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts +6 -0
  55. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -0
  56. package/dist/api/protocol-v2/helpers.d.ts +27 -18
  57. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  58. package/dist/api/ton/TonSignData.d.ts +1 -5
  59. package/dist/api/ton/TonSignData.d.ts.map +1 -1
  60. package/dist/api/tron/TronSignMessage.d.ts +0 -1
  61. package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
  62. package/dist/api/tron/TronSignTransaction.d.ts +0 -1
  63. package/dist/api/tron/TronSignTransaction.d.ts.map +1 -1
  64. package/dist/core/RequestQueue.d.ts +2 -0
  65. package/dist/core/RequestQueue.d.ts.map +1 -1
  66. package/dist/core/index.d.ts +1 -0
  67. package/dist/core/index.d.ts.map +1 -1
  68. package/dist/data-manager/DataManager.d.ts +1 -1
  69. package/dist/data-manager/DataManager.d.ts.map +1 -1
  70. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  71. package/dist/device/Device.d.ts +6 -3
  72. package/dist/device/Device.d.ts.map +1 -1
  73. package/dist/device/DeviceCommands.d.ts +4 -4
  74. package/dist/device/DeviceCommands.d.ts.map +1 -1
  75. package/dist/device/DeviceWalletSessionStore.d.ts +15 -0
  76. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
  77. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  78. package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
  79. package/dist/events/call.d.ts +7 -0
  80. package/dist/events/call.d.ts.map +1 -1
  81. package/dist/events/core.d.ts +2 -2
  82. package/dist/events/core.d.ts.map +1 -1
  83. package/dist/events/iframe.d.ts +1 -0
  84. package/dist/events/iframe.d.ts.map +1 -1
  85. package/dist/index.d.ts +166 -70
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +2659 -1750
  88. package/dist/inject.d.ts +3 -2
  89. package/dist/inject.d.ts.map +1 -1
  90. package/dist/lowLevelInject.d.ts +2 -1
  91. package/dist/lowLevelInject.d.ts.map +1 -1
  92. package/dist/protocols/protocol-v2/features.d.ts +12 -14
  93. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  94. package/dist/protocols/protocol-v2/firmware.d.ts +11 -9
  95. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  96. package/dist/protocols/protocol-v2/index.d.ts +1 -0
  97. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  98. package/dist/protocols/protocol-v2/unlockRetry.d.ts +9 -0
  99. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
  100. package/dist/protocols/protocol-v2/walletSession.d.ts +13 -0
  101. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
  102. package/dist/topLevelInject.d.ts.map +1 -1
  103. package/dist/types/api/firmwareUpdate.d.ts +6 -6
  104. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  105. package/dist/types/api/getDeviceInfo.d.ts +2 -0
  106. package/dist/types/api/getDeviceInfo.d.ts.map +1 -1
  107. package/dist/types/api/getPassphraseState.d.ts +2 -10
  108. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  109. package/dist/types/api/index.d.ts +18 -12
  110. package/dist/types/api/index.d.ts.map +1 -1
  111. package/dist/types/api/protocolV2.d.ts +31 -17
  112. package/dist/types/api/protocolV2.d.ts.map +1 -1
  113. package/dist/types/api/sessionCache.d.ts +10 -0
  114. package/dist/types/api/sessionCache.d.ts.map +1 -0
  115. package/dist/types/device.d.ts +1 -0
  116. package/dist/types/device.d.ts.map +1 -1
  117. package/dist/types/params.d.ts +1 -0
  118. package/dist/types/params.d.ts.map +1 -1
  119. package/dist/types/settings.d.ts +21 -2
  120. package/dist/types/settings.d.ts.map +1 -1
  121. package/dist/utils/deviceFeaturesUtils.d.ts +2 -2
  122. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  123. package/dist/utils/index.d.ts +2 -0
  124. package/dist/utils/index.d.ts.map +1 -1
  125. package/dist/utils/patch.d.ts +1 -1
  126. package/dist/utils/patch.d.ts.map +1 -1
  127. package/dist/utils/pro2Wallpaper.d.ts +12 -0
  128. package/dist/utils/pro2Wallpaper.d.ts.map +1 -0
  129. package/package.json +4 -4
  130. package/src/api/BaseMethod.ts +18 -0
  131. package/src/api/ClearSessionCache.ts +28 -0
  132. package/src/api/FileRead.ts +16 -3
  133. package/src/api/FileWrite.ts +22 -1
  134. package/src/api/FirmwareUpdateV4.ts +1006 -265
  135. package/src/api/GetOnekeyFeatures.ts +1 -1
  136. package/src/api/GetPassphraseState.ts +9 -15
  137. package/src/api/UploadPortfolio.ts +37 -0
  138. package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
  139. package/src/api/device/DeviceRebootToBootloader.ts +4 -4
  140. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +18 -4
  141. package/src/api/firmware/progressThrottle.ts +44 -0
  142. package/src/api/index.ts +13 -10
  143. package/src/api/protocol-v2/{FactoryGetDeviceInfo.ts → DeviceFactoryInfoGet.ts} +3 -3
  144. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +32 -0
  145. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +3 -5
  146. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +39 -5
  147. package/src/api/protocol-v2/{DeviceGetDeviceInfo.ts → DeviceInfoGet.ts} +52 -22
  148. package/src/api/protocol-v2/DeviceReboot.ts +1 -1
  149. package/src/api/protocol-v2/DeviceSessionGet.ts +15 -0
  150. package/src/api/protocol-v2/{DeviceGetOnboardingStatus.ts → DeviceSettingsGet.ts} +3 -3
  151. package/src/api/protocol-v2/DeviceSettingsPageShow.ts +59 -0
  152. package/src/api/protocol-v2/DeviceSettingsSet.ts +39 -0
  153. package/src/api/protocol-v2/DeviceStatusGet.ts +14 -0
  154. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +160 -0
  155. package/src/api/protocol-v2/FilesystemFormat.ts +4 -1
  156. package/src/api/protocol-v2/{FilesystemFixPermission.ts → FilesystemPermissionFix.ts} +2 -2
  157. package/src/api/protocol-v2/{GetProtoVersion.ts → ProtocolInfoRequest.ts} +2 -2
  158. package/src/api/protocol-v2/helpers.ts +71 -47
  159. package/src/api/ton/TonSignData.ts +1 -5
  160. package/src/api/tron/TronSignMessage.ts +0 -1
  161. package/src/api/tron/TronSignTransaction.ts +0 -1
  162. package/src/core/RequestQueue.ts +19 -0
  163. package/src/core/index.ts +29 -6
  164. package/src/data/messages/messages-protocol-v2.json +760 -1110
  165. package/src/data/messages/messages.json +8 -0
  166. package/src/data-manager/DataManager.ts +7 -0
  167. package/src/data-manager/connectSettings.ts +2 -4
  168. package/src/device/Device.ts +103 -70
  169. package/src/device/DeviceCommands.ts +9 -3
  170. package/src/device/DeviceWalletSessionStore.ts +78 -0
  171. package/src/deviceProfile/buildDeviceFeatures.ts +60 -63
  172. package/src/deviceProfile/buildDeviceProfile.ts +77 -58
  173. package/src/events/call.ts +6 -0
  174. package/src/events/core.ts +2 -0
  175. package/src/events/iframe.ts +1 -0
  176. package/src/index.ts +4 -0
  177. package/src/inject.ts +26 -16
  178. package/src/lowLevelInject.ts +3 -0
  179. package/src/protocols/protocol-v2/features.ts +26 -63
  180. package/src/protocols/protocol-v2/firmware.ts +12 -36
  181. package/src/protocols/protocol-v2/index.ts +1 -0
  182. package/src/protocols/protocol-v2/unlockRetry.ts +46 -0
  183. package/src/protocols/protocol-v2/walletSession.ts +92 -0
  184. package/src/topLevelInject.ts +2 -0
  185. package/src/types/api/firmwareUpdate.ts +28 -20
  186. package/src/types/api/getDeviceInfo.ts +2 -0
  187. package/src/types/api/getPassphraseState.ts +2 -11
  188. package/src/types/api/index.ts +29 -21
  189. package/src/types/api/protocolV2.ts +74 -35
  190. package/src/types/api/sessionCache.ts +14 -0
  191. package/src/types/device.ts +1 -0
  192. package/src/types/params.ts +4 -0
  193. package/src/types/settings.ts +42 -35
  194. package/src/utils/deviceFeaturesUtils.ts +46 -20
  195. package/src/utils/index.ts +6 -0
  196. package/src/utils/pro2Wallpaper.ts +111 -0
  197. package/dist/api/protocol-v2/DevReboot.d.ts +0 -7
  198. package/dist/api/protocol-v2/DevReboot.d.ts.map +0 -1
  199. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts.map +0 -1
  200. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +0 -6
  201. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +0 -1
  202. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts +0 -7
  203. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts.map +0 -1
  204. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts +0 -6
  205. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts.map +0 -1
  206. package/dist/api/protocol-v2/FilesystemFixPermission.d.ts.map +0 -1
  207. package/dist/api/protocol-v2/GetProtoVersion.d.ts +0 -6
  208. package/dist/api/protocol-v2/GetProtoVersion.d.ts.map +0 -1
  209. package/src/api/protocol-v2/DevReboot.ts +0 -24
  210. package/src/api/protocol-v2/FactoryDeviceInfoSettings.ts +0 -29
@@ -7784,6 +7784,14 @@
7784
7784
  "passphrase_state": {
7785
7785
  "type": "string",
7786
7786
  "id": 1
7787
+ },
7788
+ "_only_main_pin": {
7789
+ "type": "bool",
7790
+ "id": 2
7791
+ },
7792
+ "allow_create_attach_pin": {
7793
+ "type": "bool",
7794
+ "id": 3
7787
7795
  }
7788
7796
  }
7789
7797
  },
@@ -34,6 +34,7 @@ const Log = getLogger(LoggerNames.Core);
34
34
 
35
35
  export const FIRMWARE_FIELDS = [
36
36
  'firmware',
37
+ 'firmware-v1',
37
38
  'firmware-v2',
38
39
  'firmware-v8',
39
40
  'firmware-btc-v8',
@@ -46,6 +47,7 @@ export type ProtobufMessageSchema = ProtocolV1MessageSchema | 'v2Schema';
46
47
 
47
48
  const FIRMWARE_FIELD_TYPE_MAP: Readonly<Record<IFirmwareField, EFirmwareType>> = {
48
49
  firmware: EFirmwareType.Universal,
50
+ 'firmware-v1': EFirmwareType.Universal,
49
51
  'firmware-v2': EFirmwareType.Universal,
50
52
  'firmware-v8': EFirmwareType.Universal,
51
53
  'firmware-btc-v8': EFirmwareType.BitcoinOnly,
@@ -87,6 +89,10 @@ export default class DataManager {
87
89
  firmware: [],
88
90
  ble: [],
89
91
  },
92
+ [EDeviceType.Pro2]: {
93
+ firmware: [],
94
+ ble: [],
95
+ },
90
96
  [EDeviceType.ClassicPure]: {
91
97
  firmware: [],
92
98
  ble: [],
@@ -447,6 +453,7 @@ export default class DataManager {
447
453
  [EDeviceType.Mini]: this.enrichFirmwareReleaseInfo(data.mini),
448
454
  [EDeviceType.Touch]: this.enrichFirmwareReleaseInfo(data.touch),
449
455
  [EDeviceType.Pro]: this.enrichFirmwareReleaseInfo(data.pro),
456
+ [EDeviceType.Pro2]: this.enrichFirmwareReleaseInfo(data.pro2),
450
457
  };
451
458
  this.assets = {
452
459
  bridge: data.bridge,
@@ -27,8 +27,6 @@ const initialSettings: ConnectSettings = {
27
27
  env: 'web',
28
28
  lazyLoad: false,
29
29
  timestamp: new Date().getTime(),
30
- // 临时开关:仅用于本地/测试固件兼容;正式链路默认调用真实 DevGetDeviceInfo。
31
- protocolV2DeviceInfoMockEnabled: false,
32
30
  };
33
31
 
34
32
  export const getEnv = () => {
@@ -117,8 +115,8 @@ export const parseConnectSettings = (input: Partial<ConnectSettings> = {}) => {
117
115
  settings.fetchConfig = input.fetchConfig;
118
116
  }
119
117
 
120
- if (typeof input.protocolV2DeviceInfoMockEnabled === 'boolean') {
121
- settings.protocolV2DeviceInfoMockEnabled = input.protocolV2DeviceInfoMockEnabled;
118
+ if (input.configFetcher) {
119
+ settings.configFetcher = input.configFetcher;
122
120
  }
123
121
 
124
122
  return settings;
@@ -9,6 +9,7 @@ import {
9
9
  HardwareError,
10
10
  HardwareErrorCode,
11
11
  createDeferred,
12
+ createDeviceNotSupportMethodError,
12
13
  } from '@onekeyfe/hd-shared';
13
14
 
14
15
  import {
@@ -31,6 +32,7 @@ import {
31
32
  import { generateInstanceId } from '../utils/tracing';
32
33
  // eslint-disable-next-line import/no-cycle
33
34
  import { DeviceCommands } from './DeviceCommands';
35
+ import { deviceWalletSessionStore } from './DeviceWalletSessionStore';
34
36
  import {
35
37
  type DeviceFirmwareRange,
36
38
  DeviceModelToTypes,
@@ -65,6 +67,8 @@ import type { PassphrasePromptResponse } from './DeviceCommands';
65
67
  import type { Deferred, HardwareConnectProtocol } from '@onekeyfe/hd-shared';
66
68
  import type {
67
69
  OneKeyDeviceInfo as DeviceDescriptor,
70
+ DeviceSessionPinResult,
71
+ DeviceStatus,
68
72
  ProtocolV2DeviceInfo,
69
73
  Success,
70
74
  } from '@onekeyfe/hd-transport';
@@ -76,6 +80,7 @@ export type InitOptions = {
76
80
  passphraseState?: string;
77
81
  deriveCardano?: boolean;
78
82
  connectProtocol?: HardwareConnectProtocol;
83
+ protocolV2DeviceInfoTimeoutMs?: number;
79
84
  };
80
85
 
81
86
  export type RunOptions = {
@@ -118,8 +123,6 @@ export interface Device {
118
123
  emit<K extends keyof DeviceEvents>(type: K, ...args: DeviceEvents[K]): boolean;
119
124
  }
120
125
 
121
- const deviceSessionCache: Record<string, string> = {};
122
-
123
126
  /**
124
127
  * Pre-populate the device session cache with a known session ID.
125
128
  *
@@ -137,8 +140,7 @@ export function preloadSessionCache(
137
140
  passphraseState: string,
138
141
  sessionId: string
139
142
  ): void {
140
- const key = `${deviceId}@${passphraseState}`;
141
- deviceSessionCache[key] = sessionId;
143
+ deviceWalletSessionStore.set(deviceId, passphraseState, sessionId);
142
144
  }
143
145
 
144
146
  export class Device extends EventEmitter {
@@ -187,7 +189,7 @@ export class Device extends EventEmitter {
187
189
  /**
188
190
  * 唯一设备状态缓存。
189
191
  *
190
- * V1 直接保存原生 Features;V2 保存由 DevGetDeviceInfo 映射出的
192
+ * V1 直接保存原生 Features;V2 保存由 DeviceInfoGet 映射出的
191
193
  * Features 视图。Device 不再保存 profile,结构化 DeviceProfile 只作为
192
194
  * getDeviceInfo() 的 API 返回值存在。
193
195
  */
@@ -197,7 +199,7 @@ export class Device extends EventEmitter {
197
199
  * 是否需要更新设备信息。
198
200
  *
199
201
  * 历史名称保留用于兼容现有调用语义;对 V2 表示 features 需要由
200
- * DevGetDeviceInfo 刷新。
202
+ * DeviceInfoGet 刷新。
201
203
  */
202
204
  featuresNeedsReload = false;
203
205
 
@@ -264,7 +266,7 @@ export class Device extends EventEmitter {
264
266
  const connectId = this.getConnectId();
265
267
  const deviceId = this.getCurrentDeviceId() || null;
266
268
 
267
- const features = this.features;
269
+ const { features } = this;
268
270
 
269
271
  return {
270
272
  /** Android uses Mac address, iOS uses uuid, USB uses uuid */
@@ -326,7 +328,10 @@ export class Device extends EventEmitter {
326
328
  });
327
329
  }
328
330
 
329
- async acquire(connectProtocol?: HardwareConnectProtocol) {
331
+ async acquire(
332
+ connectProtocol?: HardwareConnectProtocol,
333
+ options?: { throwOnRunPromiseError?: boolean }
334
+ ) {
330
335
  const env = DataManager.getSettings('env');
331
336
  const mainIdKey = DataManager.isBleConnect(env) ? 'id' : 'session';
332
337
  const expectedProtocol = connectProtocol ?? this.originalDescriptor.protocolType;
@@ -375,6 +380,9 @@ export class Device extends EventEmitter {
375
380
 
376
381
  this.commands = new DeviceCommands(this, this.mainId ?? '');
377
382
  } catch (error) {
383
+ if (options?.throwOnRunPromiseError) {
384
+ throw error;
385
+ }
378
386
  if (this.runPromise) {
379
387
  this.runPromise.reject(error);
380
388
  } else {
@@ -477,7 +485,7 @@ export class Device extends EventEmitter {
477
485
  /**
478
486
  * 唯一协议判别器。
479
487
  *
480
- * descriptor.protocolType 是协议探测后的结果;V2 features 由 DevGetDeviceInfo
488
+ * descriptor.protocolType 是协议探测后的结果;V2 features 由 DeviceInfoGet
481
489
  * 映射产生。
482
490
  * 全 SDK 的协议分支都必须走这里,不要直接读 originalDescriptor.protocolType
483
491
  * 或从 features 反推。
@@ -506,9 +514,8 @@ export class Device extends EventEmitter {
506
514
  const serialNo = this.getCurrentSerialNo();
507
515
  if (serialNo) return serialNo;
508
516
 
509
- // connectId 是 SDK 内部连接路由 key;Protocol V2 早期固件/mock
510
- // 可能还没有 serial_no,此时用 transport descriptor 兜底,不改变
511
- // features.serialNo / deviceId 的业务语义。
517
+ // connectId 是 SDK 内部连接路由 key。这里兜底到 transport descriptor,
518
+ // 不改变 features.serialNo / deviceId 的业务语义。
512
519
  return this.originalDescriptor.path || this.originalDescriptor.id || '';
513
520
  }
514
521
 
@@ -622,13 +629,6 @@ export class Device extends EventEmitter {
622
629
  };
623
630
  }
624
631
 
625
- private generateStateKey(deviceId: string, passphraseState?: string) {
626
- if (passphraseState) {
627
- return `${deviceId}@${passphraseState}`;
628
- }
629
- return deviceId;
630
- }
631
-
632
632
  private getSessionCacheDeviceKey(_deviceId?: string) {
633
633
  const deviceId = _deviceId || this.getCurrentDeviceId();
634
634
  if (deviceId) return deviceId;
@@ -639,7 +639,6 @@ export class Device extends EventEmitter {
639
639
  }
640
640
 
641
641
  getInternalState(_deviceId?: string) {
642
- Log.debug('getInternalState session cache: ', deviceSessionCache);
643
642
  Log.debug(
644
643
  'getInternalState session param: ',
645
644
  `device_id: ${_deviceId}`,
@@ -657,8 +656,7 @@ export class Device extends EventEmitter {
657
656
  // downstream call carries it and hits the primary lookup below.
658
657
  if (!this.passphraseState) return undefined;
659
658
 
660
- const usePassKey = this.generateStateKey(deviceId, this.passphraseState);
661
- return deviceSessionCache[usePassKey];
659
+ return deviceWalletSessionStore.get(deviceId, this.passphraseState);
662
660
  }
663
661
 
664
662
  // attach to pin to fix internal state
@@ -674,36 +672,26 @@ export class Device extends EventEmitter {
674
672
  `device_id: ${deviceId}`,
675
673
  `enablePassphrase: ${enablePassphrase}`,
676
674
  `passphraseState: ${passphraseState}`,
677
- `sessionId: ${sessionId}`,
678
- `featuresSessionId: ${featuresSessionId}`
675
+ `hasSessionId: ${Boolean(sessionId)}`,
676
+ `hasFeaturesSessionId: ${Boolean(featuresSessionId)}`
679
677
  );
680
678
 
681
679
  const cacheDeviceKey = this.getSessionCacheDeviceKey(deviceId);
682
680
  if (!cacheDeviceKey) return;
683
681
 
684
682
  if (enablePassphrase) {
685
- // update the sessionId
686
- if (sessionId) {
687
- deviceSessionCache[this.generateStateKey(cacheDeviceKey, passphraseState)] = sessionId;
688
- } else if (featuresSessionId) {
689
- deviceSessionCache[this.generateStateKey(cacheDeviceKey, passphraseState)] =
690
- featuresSessionId;
691
- }
692
- }
693
-
694
- // delete the old sessionId
695
- const oldKey = `${cacheDeviceKey}`;
696
- if (deviceSessionCache[oldKey]) {
697
- delete deviceSessionCache[oldKey];
683
+ const walletSessionId =
684
+ sessionId || featuresSessionId || deviceWalletSessionStore.getPending(cacheDeviceKey);
685
+ deviceWalletSessionStore.set(cacheDeviceKey, passphraseState, walletSessionId ?? undefined);
698
686
  }
699
687
 
700
- Log.debug('updateInternalState session cache: ', deviceSessionCache);
688
+ deviceWalletSessionStore.deletePending(cacheDeviceKey);
701
689
  }
702
690
 
703
691
  private setInternalState(state: string, initSession?: boolean) {
704
692
  Log.debug(
705
693
  'setInternalState session param: ',
706
- `state: ${state}`,
694
+ `hasState: ${Boolean(state)}`,
707
695
  `initSession: ${initSession}`,
708
696
  `deviceId: ${this.features?.deviceId}`,
709
697
  `passphraseState: ${this.passphraseState}`
@@ -714,12 +702,11 @@ export class Device extends EventEmitter {
714
702
  const deviceId = this.getSessionCacheDeviceKey();
715
703
  if (!deviceId) return;
716
704
 
717
- const key = this.generateStateKey(deviceId, this.passphraseState);
718
-
719
- if (state) {
720
- deviceSessionCache[key] = state;
705
+ if (this.passphraseState) {
706
+ deviceWalletSessionStore.set(deviceId, this.passphraseState, state);
707
+ } else if (initSession) {
708
+ deviceWalletSessionStore.setPending(deviceId, state);
721
709
  }
722
- Log.debug('setInternalState done session cache: ', deviceSessionCache);
723
710
  }
724
711
 
725
712
  clearInternalState(_deviceId?: string) {
@@ -727,12 +714,10 @@ export class Device extends EventEmitter {
727
714
 
728
715
  const deviceId = this.getSessionCacheDeviceKey(_deviceId);
729
716
  if (!deviceId) return;
730
- const key = `${deviceId}`;
731
- delete deviceSessionCache[key];
717
+ deviceWalletSessionStore.deletePending(deviceId);
732
718
 
733
719
  if (this.passphraseState) {
734
- const usePassKey = this.generateStateKey(deviceId, this.passphraseState);
735
- delete deviceSessionCache[usePassKey];
720
+ deviceWalletSessionStore.delete(deviceId, this.passphraseState);
736
721
  }
737
722
  }
738
723
 
@@ -741,13 +726,16 @@ export class Device extends EventEmitter {
741
726
  if (this.isProtocolV2()) {
742
727
  this.passphraseState = options?.passphraseState;
743
728
  if (this.features && !this.featuresNeedsReload && !options?.initSession) {
729
+ if (this.features.bootloaderMode) {
730
+ return;
731
+ }
744
732
  // 不能直接信任缓存 features:设备端 wipe / 完成初始化 / 改 label 后
745
733
  // features 会永久陈旧。每次 run 做一次轻量 status 刷新(不含 fw/SE),
746
734
  // 用字段级合并保留已有版本和 SE 信息。
747
- await this._refreshProtocolV2Status();
735
+ await this._refreshProtocolV2Status(options);
748
736
  return;
749
737
  }
750
- await this._initializeProtocolV2();
738
+ await this._initializeProtocolV2(options);
751
739
  return;
752
740
  }
753
741
 
@@ -797,10 +785,10 @@ export class Device extends EventEmitter {
797
785
  /**
798
786
  * Device initialization over Protocol V2.
799
787
  *
800
- * Protocol V2 不走传统 Initialize/GetFeatures;直接用 DevGetDeviceInfo
788
+ * Protocol V2 不走传统 Initialize/GetFeatures;直接用 DeviceInfoGet
801
789
  * 生成唯一的 features 状态。
802
790
  */
803
- private async _initializeProtocolV2() {
791
+ private async _initializeProtocolV2(options?: InitOptions) {
804
792
  Log.debug('Initialize device via Protocol V2 features adapter');
805
793
 
806
794
  try {
@@ -809,6 +797,7 @@ export class Device extends EventEmitter {
809
797
  // 且 reject 后底层调用仍会残留。
810
798
  const deviceInfo = await requestProtocolV2DeviceInfo({
811
799
  commands: this.commands,
800
+ timeoutMs: options?.protocolV2DeviceInfoTimeoutMs,
812
801
  });
813
802
  // 默认请求不含 SE/hash 数据,scope 如实标注为 basic;
814
803
  // 完整数据由 getDeviceInfo(scope:'verify'|'full') 获取。
@@ -824,15 +813,16 @@ export class Device extends EventEmitter {
824
813
  /**
825
814
  * Protocol V2 的轻量状态刷新(每次 run 前调用)。
826
815
  *
827
- * 请求 hw + bt + status(不含 fw/SE target):status 提供 init_states / label /
828
- * passphrase_protection 等会在设备端变化的字段;hw/bt 提供 serialNo / bleName。
816
+ * 请求 hw + coprocessor + status(不含 fw/SE target):status 提供 init_states / label /
817
+ * passphrase_enabled 等会在设备端变化的字段;hw/coprocessor 提供 serialNo / bleName。
829
818
  * versions 为空时按字段级合并保留旧值,verify 数据不会被降级。
830
819
  */
831
- private async _refreshProtocolV2Status() {
820
+ private async _refreshProtocolV2Status(options?: InitOptions) {
832
821
  try {
833
822
  const deviceInfo = await requestProtocolV2DeviceInfo({
834
823
  commands: this.commands,
835
824
  request: PROTOCOL_V2_STATUS_DEVICE_INFO_REQUEST,
825
+ timeoutMs: options?.protocolV2DeviceInfoTimeoutMs,
836
826
  });
837
827
  const features = this.updateProtocolV2Features(deviceInfo);
838
828
  Log.debug('Protocol V2 features (status refresh):', features);
@@ -856,6 +846,7 @@ export class Device extends EventEmitter {
856
846
  }
857
847
 
858
848
  _updateFeatures(protoFeatures: PROTO.Features | Features, initSession?: boolean) {
849
+ const previousCacheDeviceKey = this.getSessionCacheDeviceKey();
859
850
  let feat =
860
851
  'protocol' in protoFeatures
861
852
  ? protoFeatures
@@ -865,28 +856,50 @@ export class Device extends EventEmitter {
865
856
  if (this.features?.sessionId && !feat.sessionId) {
866
857
  feat.sessionId = this.features.sessionId;
867
858
  }
868
- if (this.getCurrentDeviceId() && feat.sessionId) {
869
- this.setInternalState(feat.sessionId, initSession);
870
- }
871
859
  feat.unlocked = feat.unlocked ?? true;
872
860
 
873
861
  feat = fixFeaturesFirmwareVersion(feat);
874
862
 
875
863
  this.features = feat;
864
+ const nextCacheDeviceKey = this.getSessionCacheDeviceKey();
865
+ if (previousCacheDeviceKey && nextCacheDeviceKey) {
866
+ deviceWalletSessionStore.migrateDeviceKey(previousCacheDeviceKey, nextCacheDeviceKey);
867
+ }
868
+ if (feat.deviceId && feat.sessionId) {
869
+ this.setInternalState(feat.sessionId, initSession);
870
+ }
876
871
  this.featuresNeedsReload = false;
877
872
  this.emit(DEVICE.FEATURES, this, feat);
878
873
  }
879
874
 
880
875
  updateProtocolV2Features(deviceInfo?: ProtocolV2DeviceInfo) {
876
+ const previousCacheDeviceKey = this.getSessionCacheDeviceKey();
881
877
  const features = fixFeaturesFirmwareVersion(
882
878
  buildProtocolV2FeaturesPayload(deviceInfo, this.features)
883
879
  );
884
880
  this.features = features;
881
+ const nextCacheDeviceKey = this.getSessionCacheDeviceKey();
882
+ if (previousCacheDeviceKey && nextCacheDeviceKey) {
883
+ deviceWalletSessionStore.migrateDeviceKey(previousCacheDeviceKey, nextCacheDeviceKey);
884
+ }
885
885
  this.featuresNeedsReload = false;
886
886
  this.emit(DEVICE.FEATURES, this, features);
887
887
  return features;
888
888
  }
889
889
 
890
+ updateProtocolV2Status(status: DeviceStatus) {
891
+ const previousDeviceInfo = this.features?.raw?.protocolV2DeviceInfo;
892
+ const previousStatus = previousDeviceInfo?.status;
893
+ return this.updateProtocolV2Features({
894
+ ...(previousDeviceInfo ?? {}),
895
+ protocol_version: previousDeviceInfo?.protocol_version ?? this.features?.protocolVersion ?? 2,
896
+ status: {
897
+ ...previousStatus,
898
+ ...status,
899
+ },
900
+ });
901
+ }
902
+
890
903
  /**
891
904
  * 暂时只在 acquire 后更新 Session ID
892
905
  * 后续看是否有需要依据 listen 返回结果更新
@@ -1167,8 +1180,7 @@ export class Device extends EventEmitter {
1167
1180
  }
1168
1181
 
1169
1182
  supportUnlockVersionRange(): DeviceFirmwareRange {
1170
- // 仅适用于 Protocol V1 的 Pro 系列;Pro2 走独立版本线,
1171
- // 且 Protocol V2 固件从首个版本即支持 UnLockDevice(见 unlockDevice 的 isProtocolV2 短路)。
1183
+ // 仅适用于 Protocol V1 的 Pro 系列;Pro2 走独立的 Protocol V2 解锁流程。
1172
1184
  return {
1173
1185
  pro: {
1174
1186
  min: '4.15.0',
@@ -1177,6 +1189,37 @@ export class Device extends EventEmitter {
1177
1189
  }
1178
1190
 
1179
1191
  async unlockDevice() {
1192
+ if (this.isProtocolV2()) {
1193
+ let pinResult: DeviceSessionPinResult;
1194
+ try {
1195
+ ({ message: pinResult } = await this.commands.typedCall(
1196
+ 'DeviceSessionAskPin',
1197
+ 'DeviceSessionPinResult'
1198
+ ));
1199
+ } catch (error) {
1200
+ const errorText =
1201
+ error instanceof Error
1202
+ ? `${error.name} ${error.message}`
1203
+ : String((error as { message?: unknown } | null)?.message ?? error ?? '');
1204
+ if (errorText.includes('Failure_UnexpectedMessage')) {
1205
+ throw createDeviceNotSupportMethodError('deviceUnlock', this.getCurrentFirmwareType());
1206
+ }
1207
+ throw error;
1208
+ }
1209
+
1210
+ const status: DeviceStatus = {};
1211
+ if (pinResult.unlocked != null) {
1212
+ status.unlocked = pinResult.unlocked;
1213
+ }
1214
+ if (pinResult.unlocked_attach_pin != null) {
1215
+ status.unlocked_by_attach_to_pin = pinResult.unlocked_attach_pin;
1216
+ }
1217
+ if (pinResult.passphrase_protection != null) {
1218
+ status.passphrase_enabled = pinResult.passphrase_protection;
1219
+ }
1220
+ return this.updateProtocolV2Status(status);
1221
+ }
1222
+
1180
1223
  const firmwareVersion = this.getCurrentFirmwareVersionString() ?? '0.0.0';
1181
1224
  const versionRange = this.getCurrentMethodVersionRange(
1182
1225
  type => this.supportUnlockVersionRange()[type]
@@ -1186,9 +1229,7 @@ export class Device extends EventEmitter {
1186
1229
  this.features,
1187
1230
  Enum_Capability.Capability_AttachToPin
1188
1231
  );
1189
- // Pro2 (Protocol V2) 版本线独立于 Pro 系列,固件从首个版本即支持 UnLockDevice
1190
1232
  const supportUnlock =
1191
- this.isProtocolV2() ||
1192
1233
  supportAttachPinCapability ||
1193
1234
  (versionRange &&
1194
1235
  semver.valid(firmwareVersion) &&
@@ -1210,14 +1251,6 @@ export class Device extends EventEmitter {
1210
1251
  return Promise.resolve(features);
1211
1252
  }
1212
1253
 
1213
- // legacy 解锁探测仅适用于 Protocol V1 老固件;V2 固件必然支持 UnLockDevice
1214
- if (this.isProtocolV2()) {
1215
- throw ERRORS.TypedError(
1216
- HardwareErrorCode.RuntimeError,
1217
- 'unlock device error: device firmware does not support UnLockDevice'
1218
- );
1219
- }
1220
-
1221
1254
  const { type } = await this.commands.typedCall('GetAddress', 'Address', {
1222
1255
  address_n: [toHardened(44), toHardened(1), toHardened(0), 0, 0],
1223
1256
  coin_name: 'Testnet',
@@ -532,8 +532,9 @@ export class DeviceCommands {
532
532
 
533
533
  this.device.clearCancelableAction();
534
534
  if (res.type === 'Failure') {
535
- const { code, message } = res.message as {
535
+ const { code, subcode, message } = res.message as {
536
536
  code?: string | FailureType;
537
+ subcode?: number;
537
538
  message?: string;
538
539
  };
539
540
  let error: HardwareError | null = null;
@@ -577,8 +578,13 @@ export class DeviceCommands {
577
578
  }
578
579
 
579
580
  if (code === 'Failure_ProcessError') {
580
- // Handle firmware verification failures
581
- if (
581
+ if (subcode === 9) {
582
+ error = ERRORS.TypedError(HardwareErrorCode.DeviceLocked, message, {
583
+ failureCode: code,
584
+ subcode,
585
+ firmwareMessage: message,
586
+ });
587
+ } else if (
582
588
  message?.includes('Bootloader file verify failed') ||
583
589
  message?.includes('verify failed')
584
590
  ) {
@@ -0,0 +1,78 @@
1
+ export class DeviceWalletSessionStore {
2
+ private readonly walletSessions = new Map<string, Map<string, string>>();
3
+
4
+ private readonly pendingSessions = new Map<string, string>();
5
+
6
+ get(deviceKey: string, passphraseState?: string) {
7
+ if (!deviceKey || !passphraseState) return undefined;
8
+ return this.walletSessions.get(deviceKey)?.get(passphraseState);
9
+ }
10
+
11
+ set(deviceKey: string, passphraseState?: string, sessionId?: string) {
12
+ if (!deviceKey || !passphraseState || !sessionId) return;
13
+ let deviceSessions = this.walletSessions.get(deviceKey);
14
+ if (!deviceSessions) {
15
+ deviceSessions = new Map<string, string>();
16
+ this.walletSessions.set(deviceKey, deviceSessions);
17
+ }
18
+ deviceSessions.set(passphraseState, sessionId);
19
+ }
20
+
21
+ setPending(deviceKey: string, sessionId?: string) {
22
+ if (!deviceKey || !sessionId) return;
23
+ this.pendingSessions.set(deviceKey, sessionId);
24
+ }
25
+
26
+ getPending(deviceKey: string) {
27
+ if (!deviceKey) return undefined;
28
+ return this.pendingSessions.get(deviceKey);
29
+ }
30
+
31
+ delete(deviceKey: string, passphraseState?: string) {
32
+ if (!deviceKey || !passphraseState) return;
33
+ const deviceSessions = this.walletSessions.get(deviceKey);
34
+ if (!deviceSessions) return;
35
+ deviceSessions.delete(passphraseState);
36
+ if (deviceSessions.size === 0) {
37
+ this.walletSessions.delete(deviceKey);
38
+ }
39
+ }
40
+
41
+ deletePending(deviceKey: string) {
42
+ if (!deviceKey) return;
43
+ this.pendingSessions.delete(deviceKey);
44
+ }
45
+
46
+ deleteDevice(deviceKey: string) {
47
+ if (!deviceKey) return;
48
+ this.walletSessions.delete(deviceKey);
49
+ this.pendingSessions.delete(deviceKey);
50
+ }
51
+
52
+ migrateDeviceKey(from: string, to: string) {
53
+ if (!from || !to || from === to) return;
54
+
55
+ const sourceSessions = this.walletSessions.get(from);
56
+ if (sourceSessions) {
57
+ const targetSessions = this.walletSessions.get(to) ?? new Map<string, string>();
58
+ this.walletSessions.set(to, targetSessions);
59
+ sourceSessions.forEach((sessionId, passphraseState) => {
60
+ targetSessions.set(passphraseState, sessionId);
61
+ });
62
+ this.walletSessions.delete(from);
63
+ }
64
+
65
+ const pendingSession = this.pendingSessions.get(from);
66
+ if (pendingSession) {
67
+ this.pendingSessions.set(to, pendingSession);
68
+ this.pendingSessions.delete(from);
69
+ }
70
+ }
71
+
72
+ clear() {
73
+ this.walletSessions.clear();
74
+ this.pendingSessions.clear();
75
+ }
76
+ }
77
+
78
+ export const deviceWalletSessionStore = new DeviceWalletSessionStore();