@onekeyfe/hd-core 1.2.0-alpha.19 → 1.2.0-alpha.20

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 (256) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +75 -0
  2. package/__tests__/DeviceCommands.test.ts +143 -7
  3. package/__tests__/RequestQueue.test.ts +106 -0
  4. package/__tests__/UploadPortfolio.test.ts +46 -0
  5. package/__tests__/core-dispose.test.ts +34 -0
  6. package/__tests__/core-error-output.test.ts +39 -0
  7. package/__tests__/device-lifecycle-events.test.ts +117 -0
  8. package/__tests__/device-pool-state.test.ts +20 -1
  9. package/__tests__/device-settings.test.ts +184 -6
  10. package/__tests__/device-state-events.test.ts +1 -2
  11. package/__tests__/device-state-mapper.test.ts +33 -26
  12. package/__tests__/device-state-store.test.ts +1 -34
  13. package/__tests__/device-utils.test.ts +106 -0
  14. package/__tests__/device-wallet-session-store.test.ts +65 -29
  15. package/__tests__/deviceSettings.test.ts +21 -12
  16. package/__tests__/evmLedgerLegacySafety.test.ts +2 -2
  17. package/__tests__/evmSignTypedData.test.ts +80 -0
  18. package/__tests__/filesystemValidation.test.ts +53 -0
  19. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +402 -0
  20. package/__tests__/get-device-state.test.ts +77 -41
  21. package/__tests__/logBlockEvent.test.ts +24 -11
  22. package/__tests__/networkUtils.test.ts +386 -0
  23. package/__tests__/open-wallet-session-error-response.test.ts +43 -0
  24. package/__tests__/open-wallet-session.test.ts +559 -0
  25. package/__tests__/protocol-v2.test.ts +756 -452
  26. package/__tests__/public-device-state-api.test.ts +113 -13
  27. package/__tests__/public-pro2-api-boundary.test.ts +91 -0
  28. package/__tests__/search-devices.test.ts +43 -0
  29. package/dist/api/BaseMethod.d.ts +3 -0
  30. package/dist/api/BaseMethod.d.ts.map +1 -1
  31. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  32. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  33. package/dist/api/ClearSessionCache.d.ts.map +1 -1
  34. package/dist/api/DirList.d.ts.map +1 -1
  35. package/dist/api/FileRead.d.ts.map +1 -1
  36. package/dist/api/FirmwareUpdateV2.d.ts +2 -2
  37. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  38. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  39. package/dist/api/FirmwareUpdateV4.d.ts +3 -2
  40. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  41. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  42. package/dist/api/OpenWalletSession.d.ts +14 -0
  43. package/dist/api/OpenWalletSession.d.ts.map +1 -0
  44. package/dist/api/PathInfo.d.ts.map +1 -1
  45. package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
  46. package/dist/api/SearchDevices.d.ts +3 -0
  47. package/dist/api/SearchDevices.d.ts.map +1 -1
  48. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  49. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  50. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  51. package/dist/api/device/DeviceFullyUploadResource.d.ts +1 -2
  52. package/dist/api/device/DeviceFullyUploadResource.d.ts.map +1 -1
  53. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  54. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  55. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  56. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  57. package/dist/api/firmware/getBinary.d.ts +7 -3
  58. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  59. package/dist/api/helpers/filesystemValidation.d.ts +3 -0
  60. package/dist/api/helpers/filesystemValidation.d.ts.map +1 -1
  61. package/dist/api/helpers/paramsValidator.d.ts +1 -0
  62. package/dist/api/helpers/paramsValidator.d.ts.map +1 -1
  63. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  64. package/dist/api/index.d.ts +7 -11
  65. package/dist/api/index.d.ts.map +1 -1
  66. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  67. package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -1
  68. package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -1
  69. package/dist/api/utils.d.ts.map +1 -1
  70. package/dist/core/RequestQueue.d.ts +17 -1
  71. package/dist/core/RequestQueue.d.ts.map +1 -1
  72. package/dist/core/index.d.ts +3 -1
  73. package/dist/core/index.d.ts.map +1 -1
  74. package/dist/device/Device.d.ts +6 -5
  75. package/dist/device/Device.d.ts.map +1 -1
  76. package/dist/device/DeviceCommands.d.ts +5 -5
  77. package/dist/device/DeviceCommands.d.ts.map +1 -1
  78. package/dist/device/DevicePool.d.ts +1 -0
  79. package/dist/device/DevicePool.d.ts.map +1 -1
  80. package/dist/device/DeviceStateMapper.d.ts +2 -2
  81. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  82. package/dist/device/DeviceStateProjector.d.ts +1 -2
  83. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  84. package/dist/device/DeviceStateStore.d.ts +1 -3
  85. package/dist/device/DeviceStateStore.d.ts.map +1 -1
  86. package/dist/device/DeviceWalletSessionStore.d.ts +6 -1
  87. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  88. package/dist/device/cloneDeviceState.d.ts +2 -0
  89. package/dist/device/cloneDeviceState.d.ts.map +1 -0
  90. package/dist/deviceProfile/buildDeviceFeatures.d.ts +3 -1
  91. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  92. package/dist/events/logBlockEvent.d.ts.map +1 -1
  93. package/dist/index.d.ts +608 -75
  94. package/dist/index.js +2287 -1706
  95. package/dist/inject.d.ts.map +1 -1
  96. package/dist/protocols/protocol-v2/deviceSession.d.ts +7 -0
  97. package/dist/protocols/protocol-v2/deviceSession.d.ts.map +1 -0
  98. package/dist/protocols/protocol-v2/features.d.ts +11 -4
  99. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  100. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  101. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  102. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +16 -0
  103. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -0
  104. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  105. package/dist/protocols/protocol-v2/walletSession.d.ts +8 -3
  106. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  107. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -2
  108. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  109. package/dist/types/api/deviceSettings.d.ts +1 -0
  110. package/dist/types/api/deviceSettings.d.ts.map +1 -1
  111. package/dist/types/api/deviceUnlock.d.ts +1 -1
  112. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  113. package/dist/types/api/getFeatures.d.ts.map +1 -1
  114. package/dist/types/api/getOnekeyFeatures.d.ts.map +1 -1
  115. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  116. package/dist/types/api/index.d.ts +13 -15
  117. package/dist/types/api/index.d.ts.map +1 -1
  118. package/dist/types/api/openWalletSession.d.ts +41 -0
  119. package/dist/types/api/openWalletSession.d.ts.map +1 -0
  120. package/dist/types/api/protocolV2.d.ts +6 -11
  121. package/dist/types/api/protocolV2.d.ts.map +1 -1
  122. package/dist/types/device.d.ts +2 -9
  123. package/dist/types/device.d.ts.map +1 -1
  124. package/dist/utils/assets.d.ts +2 -1
  125. package/dist/utils/assets.d.ts.map +1 -1
  126. package/dist/utils/deviceFeaturesCompat.d.ts +13 -0
  127. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -0
  128. package/dist/utils/deviceFeaturesUtils.d.ts +11 -1
  129. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  130. package/dist/utils/deviceInfoUtils.d.ts +9 -8
  131. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  132. package/dist/utils/deviceSettings.d.ts +2 -0
  133. package/dist/utils/deviceSettings.d.ts.map +1 -1
  134. package/dist/utils/deviceVersionUtils.d.ts +6 -5
  135. package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
  136. package/dist/utils/index.d.ts +2 -2
  137. package/dist/utils/index.d.ts.map +1 -1
  138. package/dist/utils/networkUtils.d.ts +9 -1
  139. package/dist/utils/networkUtils.d.ts.map +1 -1
  140. package/dist/utils/patch.d.ts +1 -1
  141. package/dist/utils/patch.d.ts.map +1 -1
  142. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  143. package/package.json +4 -4
  144. package/src/api/BaseMethod.ts +17 -10
  145. package/src/api/CheckBootloaderRelease.ts +1 -2
  146. package/src/api/CheckFirmwareRelease.ts +1 -2
  147. package/src/api/ClearSessionCache.ts +0 -4
  148. package/src/api/DirList.ts +5 -3
  149. package/src/api/DirMake.ts +3 -3
  150. package/src/api/DirRemove.ts +3 -3
  151. package/src/api/FileDelete.ts +3 -3
  152. package/src/api/FileRead.ts +9 -6
  153. package/src/api/FileWrite.ts +3 -3
  154. package/src/api/FirmwareUpdate.ts +4 -4
  155. package/src/api/FirmwareUpdateV2.ts +133 -45
  156. package/src/api/FirmwareUpdateV3.ts +3 -5
  157. package/src/api/FirmwareUpdateV4.ts +192 -154
  158. package/src/api/GetPassphraseState.ts +10 -16
  159. package/src/api/OpenWalletSession.ts +221 -0
  160. package/src/api/PathInfo.ts +5 -3
  161. package/src/api/PromptWebDeviceAccess.ts +2 -3
  162. package/src/api/SearchDevices.ts +6 -2
  163. package/src/api/UploadPortfolio.ts +12 -3
  164. package/src/api/alephium/AlephiumSignTransaction.ts +2 -2
  165. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +4 -8
  166. package/src/api/benfen/BenfenGetAddress.ts +1 -1
  167. package/src/api/btc/BTCGetPublicKey.ts +1 -1
  168. package/src/api/device/DeviceChangePin.ts +1 -0
  169. package/src/api/device/DeviceFullyUploadResource.ts +4 -5
  170. package/src/api/device/DeviceSettings.ts +93 -0
  171. package/src/api/device/DeviceUpdateBootloader.ts +1 -2
  172. package/src/api/device/DeviceWipe.ts +1 -0
  173. package/src/api/evm/EVMSignTypedData.ts +3 -5
  174. package/src/api/evm/legacyV1/getAddress.ts +2 -2
  175. package/src/api/evm/legacyV1/getPublicKey.ts +2 -2
  176. package/src/api/evm/legacyV1/signMessage.ts +2 -2
  177. package/src/api/evm/legacyV1/signTypedData.ts +2 -2
  178. package/src/api/evm/legacyV1/signTypedHash.ts +2 -2
  179. package/src/api/evm/legacyV1/verifyMessage.ts +2 -2
  180. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +3 -3
  181. package/src/api/firmware/getBinary.ts +8 -3
  182. package/src/api/helpers/batchGetPublickeys.ts +2 -2
  183. package/src/api/helpers/filesystemValidation.ts +61 -0
  184. package/src/api/helpers/paramsValidator.ts +1 -1
  185. package/src/api/helpers/protocolV2FileWrite.ts +3 -2
  186. package/src/api/index.ts +9 -12
  187. package/src/api/nervos/NervosSignTransaction.ts +2 -2
  188. package/src/api/nexa/NexaSignTransaction.ts +1 -1
  189. package/src/api/protocol-v2/DeviceFactoryInfoGet.ts +1 -1
  190. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  191. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +4 -4
  192. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +6 -5
  193. package/src/api/protocol-v2/DeviceInfoGet.ts +4 -4
  194. package/src/api/protocol-v2/DeviceReboot.ts +1 -1
  195. package/src/api/protocol-v2/DeviceSettingsPageShow.ts +11 -8
  196. package/src/api/protocol-v2/DeviceSettingsSet.ts +4 -11
  197. package/src/api/protocol-v2/FilesystemFormat.ts +1 -1
  198. package/src/api/protocol-v2/FilesystemPermissionFix.ts +1 -1
  199. package/src/api/protocol-v2/Ping.ts +1 -1
  200. package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -1
  201. package/src/api/utils.ts +1 -3
  202. package/src/core/RequestQueue.ts +107 -6
  203. package/src/core/index.ts +91 -46
  204. package/src/data/messages/messages-protocol-v2.json +25 -79
  205. package/src/data-manager/DataManager.ts +6 -6
  206. package/src/device/Device.ts +186 -102
  207. package/src/device/DeviceCommands.ts +51 -21
  208. package/src/device/DeviceFeaturesState.ts +4 -4
  209. package/src/device/DevicePool.ts +17 -3
  210. package/src/device/DeviceStateMapper.ts +13 -29
  211. package/src/device/DeviceStateProjector.ts +11 -8
  212. package/src/device/DeviceStateStore.ts +11 -52
  213. package/src/device/DeviceWalletSessionStore.ts +31 -4
  214. package/src/device/cloneDeviceState.ts +39 -0
  215. package/src/device/deviceIdentity.ts +2 -2
  216. package/src/deviceProfile/buildDeviceFeatures.ts +29 -88
  217. package/src/events/logBlockEvent.ts +8 -1
  218. package/src/inject.ts +14 -24
  219. package/src/protocols/protocol-v2/deviceSession.ts +24 -0
  220. package/src/protocols/protocol-v2/features.ts +37 -49
  221. package/src/protocols/protocol-v2/firmware.ts +1 -1
  222. package/src/protocols/protocol-v2/index.ts +2 -3
  223. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +61 -0
  224. package/src/protocols/protocol-v2/uiInteraction.ts +4 -3
  225. package/src/protocols/protocol-v2/walletSession.ts +57 -146
  226. package/src/types/api/checkAllFirmwareRelease.ts +1 -2
  227. package/src/types/api/deviceSettings.ts +1 -0
  228. package/src/types/api/deviceUnlock.ts +1 -1
  229. package/src/types/api/firmwareUpdate.ts +5 -5
  230. package/src/types/api/getDeviceState.ts +1 -1
  231. package/src/types/api/getFeatures.ts +3 -0
  232. package/src/types/api/getOnekeyFeatures.ts +3 -0
  233. package/src/types/api/getPassphraseState.ts +6 -0
  234. package/src/types/api/index.ts +16 -23
  235. package/src/types/api/openWalletSession.ts +64 -0
  236. package/src/types/api/protocolV2.ts +36 -18
  237. package/src/types/device.ts +20 -14
  238. package/src/types/params.ts +1 -1
  239. package/src/types/settings.ts +8 -8
  240. package/src/utils/assets.ts +4 -1
  241. package/src/utils/deviceFeaturesCompat.ts +173 -0
  242. package/src/utils/deviceFeaturesUtils.ts +9 -8
  243. package/src/utils/deviceInfoUtils.ts +26 -64
  244. package/src/utils/deviceSettings.ts +18 -11
  245. package/src/utils/deviceVersionUtils.ts +25 -44
  246. package/src/utils/findDefectiveBatchDevice.ts +3 -3
  247. package/src/utils/index.ts +2 -5
  248. package/src/utils/networkUtils.ts +270 -14
  249. package/src/utils/pro2Wallpaper.ts +11 -13
  250. package/src/utils/tracing.ts +2 -2
  251. package/dist/api/internal.d.ts +0 -4
  252. package/dist/api/internal.d.ts.map +0 -1
  253. package/dist/api/protocol-v2/DeviceSessionOpen.d.ts +0 -28
  254. package/dist/api/protocol-v2/DeviceSessionOpen.d.ts.map +0 -1
  255. package/src/api/internal.ts +0 -9
  256. package/src/api/protocol-v2/DeviceSessionOpen.ts +0 -100
@@ -1,4 +1,11 @@
1
1
  import { ERRORS, HardwareError, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import {
3
+ DeviceErrorCode,
4
+ type FailureType,
5
+ type Messages,
6
+ type Transport,
7
+ type TransportCallOptions,
8
+ } from '@onekeyfe/hd-transport';
2
9
 
3
10
  import TransportManager from '../data-manager/TransportManager';
4
11
  import DataManager from '../data-manager/DataManager';
@@ -12,12 +19,6 @@ import {
12
19
  } from '../utils/tracing';
13
20
 
14
21
  import type { Device } from './Device';
15
- import type {
16
- FailureType,
17
- Messages,
18
- Transport,
19
- TransportCallOptions,
20
- } from '@onekeyfe/hd-transport';
21
22
 
22
23
  export type PassphrasePromptResponse = {
23
24
  passphrase?: string;
@@ -51,11 +52,16 @@ const HIGH_VOLUME_DEBUG_CALLS = new Set([
51
52
  'FirmwareUpload',
52
53
  'ResourceAck',
53
54
  ]);
55
+ const SENSITIVE_DEBUG_CALLS = new Set(['DeviceSessionGet']);
54
56
 
55
57
  function shouldReduceDebugForCall(type: string) {
56
58
  return HIGH_VOLUME_DEBUG_CALLS.has(type);
57
59
  }
58
60
 
61
+ function shouldBlockDebugForCall(type: string) {
62
+ return SENSITIVE_DEBUG_CALLS.has(type);
63
+ }
64
+
59
65
  function getBinaryByteLength(value: unknown): number | undefined {
60
66
  if (value instanceof ArrayBuffer) {
61
67
  return value.byteLength;
@@ -98,6 +104,12 @@ function sanitizeDebugPayload(value: unknown, key = '', depth = 0): unknown {
98
104
  return summarizeRedactedData(value);
99
105
  }
100
106
 
107
+ // Redact passphrase-bearing keys as defense in depth so wallet identifiers and
108
+ // host passphrases cannot reach diagnostic logs through a future call path.
109
+ if (key && /passphrase/i.test(key)) {
110
+ return '[redacted passphrase]';
111
+ }
112
+
101
113
  const byteLength = getBinaryByteLength(value);
102
114
  if (byteLength !== undefined) {
103
115
  return `[binary: ${byteLength} bytes]`;
@@ -141,10 +153,9 @@ function sanitizeDebugPayload(value: unknown, key = '', depth = 0): unknown {
141
153
  }
142
154
 
143
155
  /**
144
- * 交互式 Ack(ButtonAck / PinMatrixAck / PassphraseAck 等)不应继承原调用的
145
- * timeoutMs:设备在等待用户操作(确认、输入 PIN/passphrase),思考时间不可预估,
146
- * 沿用业务调用的超时会把用户操作截断。仅保留 expectedTypes / intermediateTypes
147
- * 等与响应类型相关的选项。
156
+ * Interactive acknowledgements must not inherit timeoutMs from the original call.
157
+ * User confirmation and PIN/passphrase entry have unbounded think time. Preserve only
158
+ * response-related options such as expectedTypes and intermediateTypes.
148
159
  */
149
160
  const stripInteractiveAckTimeout = (
150
161
  options?: TransportCallOptions
@@ -440,6 +451,7 @@ export class DeviceCommands {
440
451
  'PinMatrixAck',
441
452
  'PassphraseAck',
442
453
  'Cancel',
454
+ 'DeviceSessionGet',
443
455
  'BixinPinInputOnDevice',
444
456
  'FilesystemFileRead',
445
457
  'FilesystemFileWrite',
@@ -450,7 +462,7 @@ export class DeviceCommands {
450
462
  'FirmwareUpload',
451
463
  'ResourceAck',
452
464
  ] as any;
453
- if (!skipTypes.includes(type) && msg) {
465
+ if (!skipTypes.includes(type) && !shouldBlockDebugForCall(type) && msg) {
454
466
  // Use debug channel to avoid noise escalation
455
467
  Log.debug('[DeviceCommands] [typedCall] Sending payload', type, sanitizeDebugPayload(msg));
456
468
  }
@@ -475,12 +487,14 @@ export class DeviceCommands {
475
487
  // throw bridge network error
476
488
  if (error instanceof HardwareError) {
477
489
  if (error.errorCode === HardwareErrorCode.ResponseUnexpectTypeError) {
478
- Log.debug('[DeviceCommands] [typedCall] Unexpected response type', {
479
- request: type,
480
- expected: resType,
481
- received: response.type,
482
- response: sanitizeDebugPayload(response.message),
483
- });
490
+ if (!shouldBlockDebugForCall(type)) {
491
+ Log.debug('[DeviceCommands] [typedCall] Unexpected response type', {
492
+ request: type,
493
+ expected: resType,
494
+ received: response.type,
495
+ response: sanitizeDebugPayload(response.message),
496
+ });
497
+ }
484
498
  // Do not intercept CallMethodError
485
499
  // Do not intercept “assertType: Response of unexpected type” error
486
500
  // Blocking the above two messages will not know what the specific error message is, and the specific error should be handled by the subsequent business logic.
@@ -519,8 +533,8 @@ export class DeviceCommands {
519
533
  options?: TransportCallOptions
520
534
  ): Promise<DefaultMessageResponse> {
521
535
  try {
522
- if (shouldReduceDebugForCall(callType)) {
523
- // 高频文件写入每个 chunk 都会经过这里,避免 debug log 反向拖慢传输。
536
+ if (shouldReduceDebugForCall(callType) || shouldBlockDebugForCall(callType)) {
537
+ // Skip high-volume or security-sensitive response payloads.
524
538
  } else if (DataManager.getSettings('env') === 'react-native') {
525
539
  Log.debug('_filterCommonTypes: ', JSON.stringify(sanitizeDebugPayload(res)));
526
540
  } else {
@@ -578,9 +592,25 @@ export class DeviceCommands {
578
592
  }
579
593
 
580
594
  if (code === 'Failure_ProcessError') {
595
+ const normalizedMessage = message?.trim() ?? '';
596
+ const isLegacyProtocolV2ActionCancelledFailure =
597
+ this.device.isProtocolV2() &&
598
+ /^(?:cancelled on device|confirm dismissed)$/i.test(normalizedMessage);
581
599
  const isLegacyProtocolV2LockedFailure =
582
- this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(message?.trim() ?? '');
583
- if (subcode === 9 || isLegacyProtocolV2LockedFailure) {
600
+ this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(normalizedMessage);
601
+ if (
602
+ subcode === DeviceErrorCode.DeviceError_ActionCancelled ||
603
+ isLegacyProtocolV2ActionCancelledFailure
604
+ ) {
605
+ error = ERRORS.TypedError(HardwareErrorCode.ActionCancelled, message, {
606
+ failureCode: code,
607
+ subcode,
608
+ firmwareMessage: message,
609
+ });
610
+ } else if (
611
+ subcode === DeviceErrorCode.DeviceError_DeviceLocked ||
612
+ isLegacyProtocolV2LockedFailure
613
+ ) {
584
614
  error = ERRORS.TypedError(HardwareErrorCode.DeviceLocked, message, {
585
615
  failureCode: code,
586
616
  subcode,
@@ -10,11 +10,11 @@ export type DeviceFeaturesUpdateSource =
10
10
  | 'cache';
11
11
 
12
12
  /**
13
- * 合并设备状态的字段级 patch
13
+ * Merge a field-level device-state patch.
14
14
  *
15
- * `undefined` 表示本次响应未提供该字段,不覆盖缓存;`null` 表示设备明确
16
- * 返回空值,需要写入缓存。raw 等对象字段由调用方构造完整值后再传入,
17
- * 这里不做隐式深合并。
15
+ * `undefined` means the response omitted a field and preserves the cache; `null`
16
+ * means the device explicitly returned an empty value and must be cached. Callers
17
+ * provide complete object fields such as raw; this function does not deep-merge.
18
18
  */
19
19
  export function mergeDeviceFeaturesPatch(previous: Features, patch: Partial<Features>): Features {
20
20
  let next = previous;
@@ -160,7 +160,7 @@ export class DevicePool extends EventEmitter {
160
160
  try {
161
161
  await device.initialize(initOptions);
162
162
  if (initOptions?.refreshRuntimeState && device.isProtocolV2()) {
163
- await device.getDeviceState({ refreshSections: ['status'] });
163
+ await this._refreshProtocolV2DiscoveryState(device);
164
164
  }
165
165
  } finally {
166
166
  await device.release();
@@ -175,9 +175,9 @@ export class DevicePool extends EventEmitter {
175
175
  await device.run(
176
176
  async () => {
177
177
  try {
178
- await device.getDeviceState({ refreshSections: ['status'] });
178
+ await this._refreshProtocolV2DiscoveryState(device);
179
179
  } catch (error) {
180
- // Device.run 在回调正常结束后统一 release;随后再把真实读取错误交给调用方。
180
+ // Device.run releases after the callback; then propagate the actual read error.
181
181
  refreshError = error;
182
182
  }
183
183
  },
@@ -187,6 +187,20 @@ export class DevicePool extends EventEmitter {
187
187
  if (refreshError) throw new Error(String(refreshError));
188
188
  }
189
189
 
190
+ /**
191
+ * Device lists require live mode and name data. A status failure remains a connection
192
+ * error; settings only supplies a label, so its failure retains the existing name.
193
+ * Device.getDeviceState skips unsupported status/settings calls in loader mode.
194
+ */
195
+ static async _refreshProtocolV2DiscoveryState(device: Device) {
196
+ await device.getDeviceState({ refreshSections: ['status'] });
197
+ try {
198
+ await device.getDeviceState({ refreshSections: ['settings'] });
199
+ } catch (error) {
200
+ Log.debug('Unable to refresh Protocol V2 device label during discovery', error);
201
+ }
202
+ }
203
+
190
204
  static async _checkDevicePool(initOptions?: InitOptions, connectId?: string) {
191
205
  await this._sendConnectMessage(initOptions, connectId);
192
206
  this._sendDisconnectMessage();
@@ -1,11 +1,15 @@
1
1
  import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { buildProtocolV1FeaturesPayload } from '../deviceProfile/buildDeviceFeatures';
4
- import {
5
- isProtocolV2BootloaderDeviceInfo,
6
- isProtocolV2RomloaderDeviceInfo,
7
- } from '../protocols/protocol-v2/features';
4
+ import { mapLanguageToProtocolV2 } from '../utils/deviceSettings';
8
5
 
6
+ import type {
7
+ ApplySettings,
8
+ DeviceFirmwareImageInfo,
9
+ DeviceSettings,
10
+ DeviceStatus,
11
+ ProtocolV2DeviceInfo,
12
+ } from '@onekeyfe/hd-transport';
9
13
  import type { PROTO } from '../constants';
10
14
  import type {
11
15
  DeviceFeaturesVerify,
@@ -15,13 +19,6 @@ import type {
15
19
  OnekeyFeatures,
16
20
  } from '../types';
17
21
  import type { DeviceSettingsParams } from '../types/api/deviceSettings';
18
- import type {
19
- ApplySettings,
20
- DeviceFirmwareImageInfo,
21
- DeviceSettings,
22
- DeviceStatus,
23
- ProtocolV2DeviceInfo,
24
- } from '@onekeyfe/hd-transport';
25
22
 
26
23
  const definedEntries = <T extends Record<string, unknown>>(value: T): T =>
27
24
  Object.fromEntries(
@@ -113,8 +110,9 @@ export const mapFeaturesToState = (features: Features): DeviceStatePatch => ({
113
110
  bootloader: features.bootloaderVersion,
114
111
  board: features.boardVersion,
115
112
  ble: features.bleVersion,
113
+ // Legacy devices expose one SE version; project it to se01 and keep se as an alias.
116
114
  se: features.seVersion,
117
- se01: features.se01Version,
115
+ se01: features.se01Version ?? features.seVersion,
118
116
  se02: features.se02Version,
119
117
  se03: features.se03Version,
120
118
  se04: features.se04Version,
@@ -125,10 +123,6 @@ export const mapFeaturesToState = (features: Features): DeviceStatePatch => ({
125
123
  },
126
124
  capabilities: features.capabilities,
127
125
  verification: definedEntries(features.verify ?? {}),
128
- session: {
129
- sessionId: features.sessionId,
130
- passphraseState: features.passphraseState,
131
- },
132
126
  raw: features.raw,
133
127
  });
134
128
 
@@ -193,19 +187,9 @@ export const mapProtocolV1OnekeyFeaturesToState = (features: OnekeyFeatures): De
193
187
 
194
188
  export const mapProtocolV2DeviceInfoToState = (
195
189
  info: ProtocolV2DeviceInfo,
196
- currentMode?: DeviceStateMode
190
+ mode: DeviceStateMode = 'unknown'
197
191
  ): DeviceStatePatch => {
198
- const romloader = isProtocolV2RomloaderDeviceInfo(info);
199
- const bootloader = isProtocolV2BootloaderDeviceInfo(info);
200
- const loader = romloader || bootloader;
201
- let mode: DeviceStateMode = 'normal';
202
- if (romloader) {
203
- mode = 'romloader';
204
- } else if (bootloader) {
205
- mode = 'bootloader';
206
- } else if (currentMode === 'notInitialized' || currentMode === 'backupMode') {
207
- mode = currentMode;
208
- }
192
+ const loader = mode === 'bootloader' || mode === 'romloader';
209
193
 
210
194
  return {
211
195
  protocol: 'V2',
@@ -350,7 +334,7 @@ export const mapCommonSettingsToProtocolV2 = (settings: DeviceSettingsParams): D
350
334
  definedEntries({
351
335
  label: settings.label,
352
336
  bt_enable: settings.bluetoothEnabled,
353
- language: settings.language,
337
+ language: mapLanguageToProtocolV2(settings.language),
354
338
  brightness: settings.brightness,
355
339
  autolock_delay_ms: settings.autoLockDelayMs,
356
340
  autoshutdown_delay_ms: settings.autoShutdownDelayMs,
@@ -1,25 +1,29 @@
1
- import type { DeviceFeaturesRaw, DeviceState, DeviceStateSession, Features } from '../types';
1
+ import { cloneDeviceState } from './cloneDeviceState';
2
+
3
+ import type { DeviceFeaturesRaw, DeviceState, Features } from '../types';
2
4
 
3
5
  type StoredDeviceState = DeviceState & {
4
6
  raw?: DeviceFeaturesRaw;
5
- session?: DeviceStateSession;
6
7
  };
7
8
 
8
9
  const getBootloaderMode = (state: StoredDeviceState) =>
9
10
  state.status.mode === 'bootloader' || state.status.mode === 'romloader';
10
11
 
11
12
  export const projectFeatures = (state: StoredDeviceState): Features => {
12
- const snapshot = structuredClone(state);
13
+ const snapshot = cloneDeviceState(state);
13
14
  const rawFeatures = snapshot.raw?.protocolV1Features ?? {};
15
+ const publicRawFeatures = { ...rawFeatures } as Record<string, unknown>;
16
+ delete publicRawFeatures.session_id;
17
+ delete publicRawFeatures.sessionId;
18
+ delete publicRawFeatures.passphraseState;
14
19
  const rawOneKeyFeatures = snapshot.raw?.protocolV1OneKeyFeatures ?? {};
15
- const sessionId = snapshot.session?.sessionId ?? null;
16
20
  const bootloaderMode =
17
21
  snapshot.protocol === 'V1'
18
22
  ? (rawFeatures as { bootloader_mode?: boolean | null }).bootloader_mode ?? null
19
23
  : getBootloaderMode(snapshot);
20
24
 
21
25
  return {
22
- ...rawFeatures,
26
+ ...publicRawFeatures,
23
27
  ...rawOneKeyFeatures,
24
28
  protocol: snapshot.protocol,
25
29
  protocolVersion: snapshot.protocolVersion ?? (snapshot.protocol === 'V1' ? 1 : null),
@@ -80,13 +84,12 @@ export const projectFeatures = (state: StoredDeviceState): Features => {
80
84
  se04BootVersion: snapshot.versions.se04Boot,
81
85
  seVersion: snapshot.versions.se ?? null,
82
86
  verify: snapshot.verification,
83
- sessionId,
84
- passphraseState: snapshot.session?.passphraseState,
85
87
  raw: snapshot.raw,
86
88
  device_id: snapshot.identity.deviceId ?? undefined,
87
- session_id: sessionId ?? undefined,
88
89
  ble_name: snapshot.identity.bleName ?? undefined,
89
90
  passphrase_protection: snapshot.status.passphraseProtection ?? undefined,
90
91
  bootloader_mode: bootloaderMode,
92
+ sessionId: null,
93
+ session_id: null,
91
94
  } as unknown as Features;
92
95
  };
@@ -1,17 +1,17 @@
1
1
  import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
2
 
3
+ import { cloneDeviceState } from './cloneDeviceState';
4
+
3
5
  import type {
4
6
  DeviceFeaturesRaw,
5
7
  DeviceState,
6
8
  DeviceStateIdentity,
7
9
  DeviceStatePatch,
8
- DeviceStateSession,
9
10
  DeviceStateUpdateSource,
10
11
  } from '../types';
11
12
 
12
13
  type StoredDeviceState = DeviceState & {
13
14
  raw?: DeviceFeaturesRaw;
14
- session?: DeviceStateSession;
15
15
  };
16
16
 
17
17
  export type DeviceStateUpdateResult = {
@@ -21,22 +21,6 @@ export type DeviceStateUpdateResult = {
21
21
  changedKeys: string[];
22
22
  };
23
23
 
24
- const PRODUCT_DISPLAY_NAMES: Partial<Record<DeviceStateIdentity['deviceType'], string>> = {
25
- [EDeviceType.Classic]: 'OneKey Classic',
26
- [EDeviceType.Classic1s]: 'OneKey Classic 1S',
27
- [EDeviceType.ClassicPure]: 'OneKey Classic 1S Pure',
28
- [EDeviceType.Mini]: 'OneKey Mini',
29
- [EDeviceType.Touch]: 'OneKey Touch',
30
- [EDeviceType.Pro]: 'OneKey Pro',
31
- [EDeviceType.Pro2]: 'OneKey Pro 2',
32
- };
33
-
34
- const getModelDisplayName = (deviceType: DeviceStateIdentity['deviceType']) =>
35
- PRODUCT_DISPLAY_NAMES[deviceType] ?? 'OneKey';
36
-
37
- const getDisplayName = (identity: DeviceStateIdentity) =>
38
- identity.label || identity.bleName || getModelDisplayName(identity.deviceType);
39
-
40
24
  export function createEmptyDeviceState(
41
25
  identity: Partial<DeviceStateIdentity> = {}
42
26
  ): StoredDeviceState {
@@ -49,10 +33,8 @@ export function createEmptyDeviceState(
49
33
  serialNo: '',
50
34
  label: null,
51
35
  bleName: null,
52
- displayName: '',
53
36
  ...identity,
54
37
  };
55
- normalizedIdentity.displayName = getDisplayName(normalizedIdentity);
56
38
 
57
39
  return {
58
40
  schemaVersion: 1,
@@ -128,7 +110,7 @@ export class DeviceStateStore {
128
110
  private state: StoredDeviceState | undefined;
129
111
 
130
112
  constructor(initial?: StoredDeviceState) {
131
- this.state = initial ? structuredClone(initial) : undefined;
113
+ this.state = initial ? cloneDeviceState(initial) : undefined;
132
114
  }
133
115
 
134
116
  getState() {
@@ -138,7 +120,7 @@ export class DeviceStateStore {
138
120
  replace(state: StoredDeviceState, source: DeviceStateUpdateSource): DeviceStateUpdateResult {
139
121
  const revision = (this.state?.revision ?? 0) + 1;
140
122
  this.state = {
141
- ...structuredClone(state),
123
+ ...cloneDeviceState(state),
142
124
  revision,
143
125
  updatedAt: Date.now(),
144
126
  };
@@ -151,7 +133,7 @@ export class DeviceStateStore {
151
133
  }
152
134
 
153
135
  update(patch: DeviceStatePatch, source: DeviceStateUpdateSource): DeviceStateUpdateResult {
154
- const next = structuredClone(this.state ?? createEmptyDeviceState());
136
+ const next = cloneDeviceState(this.state ?? createEmptyDeviceState());
155
137
  const changedKeys: string[] = [];
156
138
 
157
139
  if (patch.protocol !== undefined && patch.protocol !== next.protocol) {
@@ -168,13 +150,13 @@ export class DeviceStateStore {
168
150
  if (patch.versions) applySectionPatch(next.versions, patch.versions, 'versions', changedKeys);
169
151
 
170
152
  if (patch.capabilities !== undefined && !isEqual(next.capabilities, patch.capabilities)) {
171
- next.capabilities = structuredClone(patch.capabilities);
153
+ next.capabilities = cloneDeviceState(patch.capabilities);
172
154
  changedKeys.push('capabilities');
173
155
  }
174
156
  if (patch.verification !== undefined) {
175
157
  const mergedVerification = {
176
- ...structuredClone(next.verification ?? {}),
177
- ...structuredClone(patch.verification),
158
+ ...cloneDeviceState(next.verification ?? {}),
159
+ ...cloneDeviceState(patch.verification),
178
160
  };
179
161
  if (!isEqual(next.verification, mergedVerification)) {
180
162
  next.verification = mergedVerification;
@@ -182,12 +164,12 @@ export class DeviceStateStore {
182
164
  }
183
165
  }
184
166
  if (patch.raw !== undefined) {
185
- const mergedRaw = structuredClone(next.raw ?? {});
167
+ const mergedRaw = cloneDeviceState(next.raw ?? {});
186
168
  for (const [key, value] of Object.entries(patch.raw)) {
187
169
  if (value === null) {
188
170
  delete mergedRaw[key as keyof typeof mergedRaw];
189
171
  } else if (value !== undefined) {
190
- mergedRaw[key as keyof typeof mergedRaw] = structuredClone(value) as never;
172
+ mergedRaw[key as keyof typeof mergedRaw] = cloneDeviceState(value) as never;
191
173
  }
192
174
  }
193
175
  if (!isEqual(next.raw, mergedRaw)) {
@@ -195,23 +177,6 @@ export class DeviceStateStore {
195
177
  changedKeys.push('raw');
196
178
  }
197
179
  }
198
- if (patch.session === null) {
199
- if (next.session !== undefined) {
200
- delete next.session;
201
- changedKeys.push('session');
202
- }
203
- } else if (patch.session) {
204
- const session = next.session ?? { sessionId: null };
205
- applySectionPatch(session, patch.session, 'session', changedKeys);
206
- next.session = session;
207
- }
208
-
209
- const displayName = getDisplayName(next.identity);
210
- if (displayName !== next.identity.displayName) {
211
- next.identity.displayName = displayName;
212
- changedKeys.push('identity.displayName');
213
- }
214
-
215
180
  if (changedKeys.length > 0) {
216
181
  next.revision = (this.state?.revision ?? 0) + 1;
217
182
  next.updatedAt = Date.now();
@@ -227,16 +192,10 @@ export class DeviceStateStore {
227
192
  changedKeys,
228
193
  };
229
194
  }
230
-
231
- clearSession(source: DeviceStateUpdateSource) {
232
- if (!this.state?.session) return undefined;
233
- return this.update({ session: null }, source);
234
- }
235
195
  }
236
196
 
237
197
  export const createPublicDeviceState = (state: StoredDeviceState): DeviceState => {
238
- const snapshot = structuredClone(state);
198
+ const snapshot = cloneDeviceState(state);
239
199
  delete snapshot.raw;
240
- delete snapshot.session;
241
200
  return snapshot;
242
201
  };
@@ -49,7 +49,7 @@ export class DeviceWalletSessionStore {
49
49
  this.pendingSessions.delete(deviceKey);
50
50
  }
51
51
 
52
- migrateDeviceKey(from: string, to: string) {
52
+ private migrateTemporaryDeviceKey(from: string, to: string) {
53
53
  if (!from || !to || from === to) return;
54
54
 
55
55
  const sourceSessions = this.walletSessions.get(from);
@@ -57,15 +57,42 @@ export class DeviceWalletSessionStore {
57
57
  const targetSessions = this.walletSessions.get(to) ?? new Map<string, string>();
58
58
  this.walletSessions.set(to, targetSessions);
59
59
  sourceSessions.forEach((sessionId, passphraseState) => {
60
- targetSessions.set(passphraseState, sessionId);
60
+ if (!targetSessions.has(passphraseState)) {
61
+ targetSessions.set(passphraseState, sessionId);
62
+ }
61
63
  });
62
64
  this.walletSessions.delete(from);
63
65
  }
64
66
 
65
67
  const pendingSession = this.pendingSessions.get(from);
66
- if (pendingSession) {
68
+ if (pendingSession && !this.pendingSessions.has(to)) {
67
69
  this.pendingSessions.set(to, pendingSession);
68
- this.pendingSessions.delete(from);
70
+ }
71
+ this.pendingSessions.delete(from);
72
+ }
73
+
74
+ reconcileDeviceIdentity({
75
+ temporaryKey,
76
+ previousDeviceId,
77
+ nextDeviceId,
78
+ }: {
79
+ temporaryKey?: string;
80
+ previousDeviceId?: string;
81
+ nextDeviceId?: string;
82
+ }) {
83
+ if (!nextDeviceId) return;
84
+
85
+ // A stable identity changing from A to B means the descriptor now refers to a
86
+ // different physical device. Never copy A's wallet sessions into B's cache.
87
+ if (previousDeviceId && previousDeviceId !== nextDeviceId) {
88
+ this.deleteDevice(previousDeviceId);
89
+ return;
90
+ }
91
+
92
+ // Migration is allowed only for the first promotion from a transport-local
93
+ // descriptor key to a firmware-provided stable identity.
94
+ if (!previousDeviceId && temporaryKey) {
95
+ this.migrateTemporaryDeviceKey(temporaryKey, nextDeviceId);
69
96
  }
70
97
  }
71
98
 
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Host-independent deep clone for DeviceState snapshots.
3
+ *
4
+ * Avoid the global structuredClone API because older Hermes and WebView hosts do not
5
+ * provide it. Explicitly clone plain objects, arrays, binary buffers, and common
6
+ * built-ins so initialization and state snapshots remain portable.
7
+ */
8
+ export const cloneDeviceState = <T>(value: T): T => {
9
+ if (value === null || typeof value !== 'object') {
10
+ return value;
11
+ }
12
+
13
+ // Copy underlying binary memory so external mutation cannot alter a snapshot.
14
+ if (value instanceof Uint8Array) {
15
+ return new Uint8Array(value) as unknown as T;
16
+ }
17
+ if (value instanceof ArrayBuffer) {
18
+ return value.slice(0) as unknown as T;
19
+ }
20
+
21
+ if (value instanceof Date) {
22
+ return new Date(value.getTime()) as unknown as T;
23
+ }
24
+ if (value instanceof RegExp) {
25
+ return new RegExp(value.source, value.flags) as unknown as T;
26
+ }
27
+
28
+ if (Array.isArray(value)) {
29
+ return value.map(item => cloneDeviceState(item)) as unknown as T;
30
+ }
31
+
32
+ // DeviceState contains only plain objects at this boundary.
33
+ const cloned: Record<string, unknown> = {};
34
+ const source = value as Record<string, unknown>;
35
+ for (const key of Object.keys(source)) {
36
+ cloned[key] = cloneDeviceState(source[key]);
37
+ }
38
+ return cloned as unknown as T;
39
+ };
@@ -3,8 +3,8 @@ import type { Device } from './Device';
3
3
  type DeviceIdentityReader = Pick<Device, 'isProtocolV2' | 'getDeviceState' | 'checkDeviceId'>;
4
4
 
5
5
  /**
6
- * Protocol V2 deviceId 来自运行态状态,比较前必须读取一次实时 status
7
- * Protocol V1 继续复用 Initialize/GetFeatures 已经返回的设备身份。
6
+ * Protocol V2 deviceId comes from runtime state and requires a live status read.
7
+ * Protocol V1 reuses the identity returned by Initialize/GetFeatures.
8
8
  */
9
9
  export async function checkLiveDeviceId(
10
10
  device: DeviceIdentityReader,