@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.23

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 (192) hide show
  1. package/__tests__/DeviceCommands.test.ts +85 -0
  2. package/__tests__/connectSettings.test.ts +47 -5
  3. package/__tests__/device-identity.test.ts +24 -2
  4. package/__tests__/device-settings.test.ts +2 -2
  5. package/__tests__/device-wallet-session-store.test.ts +152 -7
  6. package/__tests__/firmware-update/check-all-firmware-release.test.ts +86 -0
  7. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  8. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  9. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  10. package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
  11. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
  12. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
  13. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  14. package/__tests__/logBlockEvent.test.ts +17 -0
  15. package/__tests__/open-wallet-session.test.ts +311 -12
  16. package/__tests__/pro2Wallpaper.test.ts +7 -18
  17. package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
  18. package/__tests__/protocol-v2.test.ts +685 -177
  19. package/__tests__/public-device-state-api.test.ts +69 -62
  20. package/__tests__/public-pro2-api-boundary.test.ts +8 -34
  21. package/dist/api/BaseMethod.d.ts +2 -1
  22. package/dist/api/BaseMethod.d.ts.map +1 -1
  23. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  24. package/dist/api/ClearSessionCache.d.ts.map +1 -1
  25. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV2.d.ts +11 -0
  27. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  28. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  29. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  30. package/dist/api/FirmwareUpdateV4.d.ts +29 -10
  31. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  32. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  33. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  34. package/dist/api/conflux/ConfluxGetAddress.d.ts +1 -0
  35. package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
  36. package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
  37. package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
  38. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
  39. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
  40. package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
  41. package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
  42. package/dist/api/device/DeviceUnlock.d.ts +1 -1
  43. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  44. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  45. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  46. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  47. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  48. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  49. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  50. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  51. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +7 -0
  52. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  53. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  54. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  55. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +23 -0
  56. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  57. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
  58. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  59. package/dist/api/firmware/getBinary.d.ts +7 -0
  60. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  61. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  62. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  63. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  64. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  65. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  66. package/dist/api/index.d.ts +0 -17
  67. package/dist/api/index.d.ts.map +1 -1
  68. package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
  69. package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
  70. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  71. package/dist/core/index.d.ts.map +1 -1
  72. package/dist/data-manager/DataManager.d.ts +1 -0
  73. package/dist/data-manager/DataManager.d.ts.map +1 -1
  74. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  75. package/dist/device/Device.d.ts +3 -2
  76. package/dist/device/Device.d.ts.map +1 -1
  77. package/dist/device/DeviceCommands.d.ts.map +1 -1
  78. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  79. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  80. package/dist/events/device.d.ts +1 -0
  81. package/dist/events/device.d.ts.map +1 -1
  82. package/dist/events/logBlockEvent.d.ts.map +1 -1
  83. package/dist/events/ui-request.d.ts +1 -0
  84. package/dist/events/ui-request.d.ts.map +1 -1
  85. package/dist/index.d.ts +273 -230
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +3060 -1676
  88. package/dist/inject.d.ts.map +1 -1
  89. package/dist/lowLevelInject.d.ts.map +1 -1
  90. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  91. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
  92. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
  93. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  94. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  95. package/dist/topLevelInject.d.ts.map +1 -1
  96. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -0
  97. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  98. package/dist/types/api/deviceUnlock.d.ts +2 -2
  99. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  100. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  101. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  102. package/dist/types/api/export.d.ts +4 -1
  103. package/dist/types/api/export.d.ts.map +1 -1
  104. package/dist/types/api/firmwareUpdate.d.ts +69 -0
  105. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  106. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  107. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  108. package/dist/types/api/firmwareUpdatePlan.d.ts +27 -0
  109. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  110. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  111. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  112. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  113. package/dist/types/api/index.d.ts +10 -19
  114. package/dist/types/api/index.d.ts.map +1 -1
  115. package/dist/types/api/openWalletSession.d.ts +1 -0
  116. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  117. package/dist/types/api/protocolV2.d.ts +3 -76
  118. package/dist/types/api/protocolV2.d.ts.map +1 -1
  119. package/dist/types/api/sessionCache.d.ts +4 -1
  120. package/dist/types/api/sessionCache.d.ts.map +1 -1
  121. package/dist/types/settings.d.ts +13 -0
  122. package/dist/types/settings.d.ts.map +1 -1
  123. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  124. package/dist/utils/deviceInfoUtils.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/package.json +4 -4
  128. package/src/api/BaseMethod.ts +8 -3
  129. package/src/api/CheckAllFirmwareRelease.ts +23 -3
  130. package/src/api/ClearSessionCache.ts +4 -0
  131. package/src/api/FirmwareUpdate.ts +8 -1
  132. package/src/api/FirmwareUpdateV2.ts +292 -120
  133. package/src/api/FirmwareUpdateV3.ts +256 -55
  134. package/src/api/FirmwareUpdateV4.ts +821 -331
  135. package/src/api/GetPassphraseState.ts +11 -5
  136. package/src/api/OpenWalletSession.ts +74 -25
  137. package/src/api/UploadPortfolio.ts +1 -1
  138. package/src/api/conflux/ConfluxGetAddress.ts +2 -0
  139. package/src/api/conflux/ConfluxSignMessage.ts +2 -0
  140. package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
  141. package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
  142. package/src/api/device/DeviceChangePin.ts +1 -1
  143. package/src/api/device/DeviceUnlock.ts +1 -2
  144. package/src/api/device/DeviceUpdateBootloader.ts +122 -6
  145. package/src/api/device/DeviceWipe.ts +3 -1
  146. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  147. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  148. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
  149. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  150. package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
  151. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
  152. package/src/api/firmware/updateBootloader.ts +19 -4
  153. package/src/api/firmware/uploadFirmware.ts +140 -22
  154. package/src/api/helpers/protocolV2FileWrite.ts +6 -0
  155. package/src/api/index.ts +0 -18
  156. package/src/api/nexa/NexaSignTransaction.ts +2 -0
  157. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  158. package/src/core/index.ts +1 -0
  159. package/src/data/messages/messages-protocol-v2.json +35 -1
  160. package/src/data-manager/DataManager.ts +33 -33
  161. package/src/data-manager/connectSettings.ts +11 -0
  162. package/src/device/Device.ts +80 -67
  163. package/src/device/DeviceCommands.ts +76 -159
  164. package/src/device/DeviceStateMapper.ts +18 -1
  165. package/src/device/DeviceStateProjector.ts +7 -1
  166. package/src/events/device.ts +1 -0
  167. package/src/events/logBlockEvent.ts +1 -14
  168. package/src/events/ui-request.ts +1 -0
  169. package/src/index.ts +5 -0
  170. package/src/inject.ts +22 -25
  171. package/src/lowLevelInject.ts +5 -1
  172. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -3
  173. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
  174. package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
  175. package/src/protocols/protocol-v2/walletSession.ts +86 -9
  176. package/src/topLevelInject.ts +5 -1
  177. package/src/types/api/checkAllFirmwareRelease.ts +3 -0
  178. package/src/types/api/deviceUnlock.ts +2 -5
  179. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  180. package/src/types/api/export.ts +18 -0
  181. package/src/types/api/firmwareUpdate.ts +76 -0
  182. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  183. package/src/types/api/firmwareUpdatePlan.ts +36 -0
  184. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  185. package/src/types/api/getPassphraseState.ts +4 -3
  186. package/src/types/api/index.ts +14 -34
  187. package/src/types/api/openWalletSession.ts +2 -0
  188. package/src/types/api/protocolV2.ts +3 -156
  189. package/src/types/api/sessionCache.ts +9 -4
  190. package/src/types/settings.ts +13 -0
  191. package/src/utils/deviceFeaturesUtils.ts +5 -1
  192. package/src/utils/deviceInfoUtils.ts +3 -16
@@ -1,6 +1,7 @@
1
1
  import { ERRORS, HardwareError, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
  import {
3
3
  DeviceErrorCode,
4
+ DeviceSessionErrorCode,
4
5
  type FailureType,
5
6
  type Messages,
6
7
  type Transport,
@@ -8,7 +9,6 @@ import {
8
9
  } from '@onekeyfe/hd-transport';
9
10
 
10
11
  import TransportManager from '../data-manager/TransportManager';
11
- import DataManager from '../data-manager/DataManager';
12
12
  import { LoggerNames, getLogger, patchFeatures } from '../utils';
13
13
  import { DEVICE, type PassphraseRequestPayload } from '../events';
14
14
  import { DeviceModelToTypes } from '../types';
@@ -38,10 +38,6 @@ type TypedCallResponseMap = {
38
38
  };
39
39
  export type DefaultMessageResponse = TypedCallResponseMap[MessageKey];
40
40
 
41
- const MAX_DEBUG_ARRAY_ITEMS = 20;
42
- const MAX_DEBUG_OBJECT_KEYS = 40;
43
- const MAX_DEBUG_STRING_LENGTH = 512;
44
- const MAX_DEBUG_DEPTH = 4;
45
41
  const HIGH_VOLUME_DEBUG_CALLS = new Set([
46
42
  'FilesystemFileRead',
47
43
  'FilesystemFileWrite',
@@ -52,106 +48,16 @@ const HIGH_VOLUME_DEBUG_CALLS = new Set([
52
48
  'FirmwareUpload',
53
49
  'ResourceAck',
54
50
  ]);
55
- const SENSITIVE_DEBUG_CALLS = new Set(['DeviceSessionGet']);
51
+ const DEVICE_SESSION_CALLS = new Set([
52
+ 'DeviceSessionGet',
53
+ 'DeviceSessionAskPin',
54
+ 'DeviceSessionAskPassphrase',
55
+ ]);
56
56
 
57
57
  function shouldReduceDebugForCall(type: string) {
58
58
  return HIGH_VOLUME_DEBUG_CALLS.has(type);
59
59
  }
60
60
 
61
- function shouldBlockDebugForCall(type: string) {
62
- return SENSITIVE_DEBUG_CALLS.has(type);
63
- }
64
-
65
- function getBinaryByteLength(value: unknown): number | undefined {
66
- if (value instanceof ArrayBuffer) {
67
- return value.byteLength;
68
- }
69
-
70
- if (ArrayBuffer.isView(value)) {
71
- return value.byteLength;
72
- }
73
-
74
- if (typeof Blob !== 'undefined' && value instanceof Blob) {
75
- return value.size;
76
- }
77
-
78
- return undefined;
79
- }
80
-
81
- function summarizeRedactedData(value: unknown): string {
82
- const byteLength = getBinaryByteLength(value);
83
- if (byteLength !== undefined) {
84
- return `[redacted data: ${byteLength} bytes]`;
85
- }
86
-
87
- if (typeof value === 'string') {
88
- return `[redacted data: string length=${value.length}]`;
89
- }
90
-
91
- if (Array.isArray(value)) {
92
- return `[redacted data: array length=${value.length}]`;
93
- }
94
-
95
- if (value && typeof value === 'object') {
96
- return `[redacted data: object keys=${Object.keys(value).length}]`;
97
- }
98
-
99
- return `[redacted data: ${typeof value}]`;
100
- }
101
-
102
- function sanitizeDebugPayload(value: unknown, key = '', depth = 0): unknown {
103
- if (key === 'data' && value !== null && value !== undefined) {
104
- return summarizeRedactedData(value);
105
- }
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
-
113
- const byteLength = getBinaryByteLength(value);
114
- if (byteLength !== undefined) {
115
- return `[binary: ${byteLength} bytes]`;
116
- }
117
-
118
- if (typeof value === 'string') {
119
- return value.length > MAX_DEBUG_STRING_LENGTH
120
- ? `${value.slice(0, MAX_DEBUG_STRING_LENGTH)}... (len=${value.length})`
121
- : value;
122
- }
123
-
124
- if (!value || typeof value !== 'object') {
125
- return value;
126
- }
127
-
128
- if (depth >= MAX_DEBUG_DEPTH) {
129
- return Array.isArray(value)
130
- ? `[array length=${value.length}]`
131
- : `[object keys=${Object.keys(value).length}]`;
132
- }
133
-
134
- if (Array.isArray(value)) {
135
- const items = value
136
- .slice(0, MAX_DEBUG_ARRAY_ITEMS)
137
- .map(item => sanitizeDebugPayload(item, key, depth + 1));
138
- if (value.length > MAX_DEBUG_ARRAY_ITEMS) {
139
- items.push(`... (${value.length - MAX_DEBUG_ARRAY_ITEMS} more)`);
140
- }
141
- return items;
142
- }
143
-
144
- const entries = Object.entries(value).slice(0, MAX_DEBUG_OBJECT_KEYS);
145
- const sanitized: Record<string, unknown> = {};
146
- entries.forEach(([entryKey, entryValue]) => {
147
- sanitized[entryKey] = sanitizeDebugPayload(entryValue, entryKey, depth + 1);
148
- });
149
- if (Object.keys(value).length > MAX_DEBUG_OBJECT_KEYS) {
150
- sanitized.__truncated__ = `${Object.keys(value).length - MAX_DEBUG_OBJECT_KEYS} more keys`;
151
- }
152
- return sanitized;
153
- }
154
-
155
61
  /**
156
62
  * Interactive acknowledgements must not inherit timeoutMs from the original call.
157
63
  * User confirmation and PIN/passphrase entry have unbounded think time. Preserve only
@@ -337,20 +243,27 @@ export class DeviceCommands {
337
243
  if (this.disposed) {
338
244
  return;
339
245
  }
340
- this.dispose(true);
341
- if (this.callPromise) {
342
- try {
343
- await Promise.all([
344
- new Promise((_resolve, reject) =>
345
- // eslint-disable-next-line no-promise-executor-return
346
- setTimeout(() => reject(new Error('cancel timeout')), 10 * 1000)
347
- ),
348
- await this.callPromise,
349
- ]);
350
- } catch {
351
- // device error
246
+ const activeCall = this.callPromise;
247
+ let timer: ReturnType<typeof setTimeout> | undefined;
248
+ const cancellation = (async () => {
249
+ await this.dispose(true);
250
+ await activeCall?.catch(() => undefined);
251
+ })();
252
+
253
+ try {
254
+ await Promise.race([
255
+ cancellation,
256
+ new Promise<void>(resolve => {
257
+ timer = setTimeout(resolve, 10 * 1000);
258
+ }),
259
+ ]);
260
+ } finally {
261
+ if (timer) clearTimeout(timer);
262
+ if (this.callPromise === activeCall) {
352
263
  this.callPromise = undefined;
353
264
  }
265
+ // The cancellation work may finish after the bounded wait.
266
+ cancellation.catch(() => undefined);
354
267
  }
355
268
  }
356
269
 
@@ -369,14 +282,15 @@ export class DeviceCommands {
369
282
  const promise = this.transport.call(this.mainId, type, msg ?? {}, options) as any;
370
283
  this.callPromise = promise;
371
284
  const res = await promise;
372
- if (res.type === 'Failure') {
373
- LogCore.debug('[DeviceCommands] [call] Received', res.type, res.message);
374
- } else if (!shouldReduceDebug) {
285
+ if (!shouldReduceDebug) {
375
286
  LogCore.debug('[DeviceCommands] [call] Received', res.type);
376
287
  }
377
288
  return res;
378
289
  } catch (error) {
379
- LogCore.debug('[DeviceCommands] [call] Received error', error);
290
+ LogCore.debug('[DeviceCommands] [call] Received error', {
291
+ request: type,
292
+ errorCode: error?.errorCode,
293
+ });
380
294
  if (error.errorCode === HardwareErrorCode.BleDeviceBondError) {
381
295
  return {
382
296
  type: 'BleDeviceBondError',
@@ -397,9 +311,6 @@ export class DeviceCommands {
397
311
  }
398
312
  }
399
313
 
400
- if (responseData) {
401
- Log.debug('error response', responseData);
402
- }
403
314
  if (responseError === 'device disconnected during action') {
404
315
  return { type: 'BridgeDeviceDisconnected', message: { error: responseError } } as any;
405
316
  }
@@ -444,32 +355,6 @@ export class DeviceCommands {
444
355
  );
445
356
  }
446
357
 
447
- // Structured log of actual outgoing payloads (skip acks)
448
- try {
449
- const skipTypes: MessageKey[] = [
450
- 'ButtonAck',
451
- 'PinMatrixAck',
452
- 'PassphraseAck',
453
- 'Cancel',
454
- 'DeviceSessionGet',
455
- 'BixinPinInputOnDevice',
456
- 'FilesystemFileRead',
457
- 'FilesystemFileWrite',
458
- 'FileRead',
459
- 'FileWrite',
460
- 'EmmcFileRead',
461
- 'EmmcFileWrite',
462
- 'FirmwareUpload',
463
- 'ResourceAck',
464
- ] as any;
465
- if (!skipTypes.includes(type) && !shouldBlockDebugForCall(type) && msg) {
466
- // Use debug channel to avoid noise escalation
467
- Log.debug('[DeviceCommands] [typedCall] Sending payload', type, sanitizeDebugPayload(msg));
468
- }
469
- } catch (e) {
470
- // ignore logging errors
471
- }
472
-
473
358
  const expectedTypes = Array.isArray(resType) ? resType : resType.split('|');
474
359
  const response = await this._commonCall(type, msg, {
475
360
  ...options,
@@ -487,14 +372,11 @@ export class DeviceCommands {
487
372
  // throw bridge network error
488
373
  if (error instanceof HardwareError) {
489
374
  if (error.errorCode === HardwareErrorCode.ResponseUnexpectTypeError) {
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
- }
375
+ Log.debug('[DeviceCommands] [typedCall] Unexpected response type', {
376
+ request: type,
377
+ expected: resType,
378
+ received: response.type,
379
+ });
498
380
  // Do not intercept CallMethodError
499
381
  // Do not intercept “assertType: Response of unexpected type” error
500
382
  // 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.
@@ -533,12 +415,11 @@ export class DeviceCommands {
533
415
  options?: TransportCallOptions
534
416
  ): Promise<DefaultMessageResponse> {
535
417
  try {
536
- if (shouldReduceDebugForCall(callType) || shouldBlockDebugForCall(callType)) {
537
- // Skip high-volume or security-sensitive response payloads.
538
- } else if (DataManager.getSettings('env') === 'react-native') {
539
- Log.debug('_filterCommonTypes: ', JSON.stringify(sanitizeDebugPayload(res)));
540
- } else {
541
- Log.debug('_filterCommonTypes: ', sanitizeDebugPayload(res));
418
+ if (!shouldReduceDebugForCall(callType)) {
419
+ Log.debug('_filterCommonTypes: ', {
420
+ request: callType,
421
+ response: res.type,
422
+ });
542
423
  }
543
424
  } catch (error) {
544
425
  // ignore
@@ -599,6 +480,42 @@ export class DeviceCommands {
599
480
  const isLegacyProtocolV2LockedFailure =
600
481
  this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(normalizedMessage);
601
482
  if (
483
+ DEVICE_SESSION_CALLS.has(callType) &&
484
+ subcode === DeviceSessionErrorCode.DeviceSessionError_InvalidSession
485
+ ) {
486
+ error = ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid, message, {
487
+ failureCode: code,
488
+ subcode,
489
+ firmwareMessage: message,
490
+ });
491
+ } else if (
492
+ DEVICE_SESSION_CALLS.has(callType) &&
493
+ subcode === DeviceSessionErrorCode.DeviceSessionError_UserCancelled
494
+ ) {
495
+ error = ERRORS.TypedError(HardwareErrorCode.ActionCancelled, message, {
496
+ failureCode: code,
497
+ subcode,
498
+ firmwareMessage: message,
499
+ });
500
+ } else if (
501
+ DEVICE_SESSION_CALLS.has(callType) &&
502
+ subcode === DeviceSessionErrorCode.DeviceSessionError_AttachPinUnavailable
503
+ ) {
504
+ error = ERRORS.TypedError(HardwareErrorCode.DeviceCheckUnlockTypeError, message, {
505
+ failureCode: code,
506
+ subcode,
507
+ firmwareMessage: message,
508
+ });
509
+ } else if (
510
+ DEVICE_SESSION_CALLS.has(callType) &&
511
+ subcode === DeviceSessionErrorCode.DeviceSessionError_PassphraseDisabled
512
+ ) {
513
+ error = ERRORS.TypedError(HardwareErrorCode.DeviceNotOpenedPassphrase, message, {
514
+ failureCode: code,
515
+ subcode,
516
+ firmwareMessage: message,
517
+ });
518
+ } else if (
602
519
  subcode === DeviceErrorCode.DeviceError_ActionCancelled ||
603
520
  isLegacyProtocolV2ActionCancelledFailure
604
521
  ) {
@@ -29,6 +29,23 @@ const definedEntries = <T extends Record<string, unknown>>(value: T): T =>
29
29
  Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined)
30
30
  ) as T;
31
31
 
32
+ const sanitizeRawFeatures = (features: Features): Features['raw'] => {
33
+ if (!features.raw) return undefined;
34
+
35
+ const raw = { ...features.raw };
36
+ if (raw.protocolV1Features) {
37
+ const { session_id: _sessionId, ...protocolV1Features } = raw.protocolV1Features;
38
+ const compatibilityFields = protocolV1Features as typeof protocolV1Features & {
39
+ sessionId?: string;
40
+ passphraseState?: string;
41
+ };
42
+ delete compatibilityFields.sessionId;
43
+ delete compatibilityFields.passphraseState;
44
+ raw.protocolV1Features = compatibilityFields as PROTO.Features;
45
+ }
46
+ return raw;
47
+ };
48
+
32
49
  const imageVersion = (image?: DeviceFirmwareImageInfo | null) => image?.version ?? undefined;
33
50
 
34
51
  const bytesToHex = (value: unknown): string | undefined => {
@@ -127,7 +144,7 @@ export const mapFeaturesToState = (features: Features): DeviceStatePatch => ({
127
144
  },
128
145
  capabilities: features.capabilities,
129
146
  verification: definedEntries(features.verify ?? {}),
130
- raw: features.raw,
147
+ raw: sanitizeRawFeatures(features),
131
148
  });
132
149
 
133
150
  export const mapProtocolV1FeaturesToState = (
@@ -16,6 +16,12 @@ export const projectFeatures = (state: StoredDeviceState): Features => {
16
16
  delete publicRawFeatures.session_id;
17
17
  delete publicRawFeatures.sessionId;
18
18
  delete publicRawFeatures.passphraseState;
19
+ const publicRaw = snapshot.raw
20
+ ? {
21
+ ...snapshot.raw,
22
+ protocolV1Features: publicRawFeatures,
23
+ }
24
+ : undefined;
19
25
  const rawOneKeyFeatures = snapshot.raw?.protocolV1OneKeyFeatures ?? {};
20
26
  const bootloaderMode =
21
27
  snapshot.protocol === 'V1'
@@ -84,7 +90,7 @@ export const projectFeatures = (state: StoredDeviceState): Features => {
84
90
  se04BootVersion: snapshot.versions.se04Boot,
85
91
  seVersion: snapshot.versions.se ?? null,
86
92
  verify: snapshot.verification,
87
- raw: snapshot.raw,
93
+ raw: publicRaw,
88
94
  device_id: snapshot.identity.deviceId ?? undefined,
89
95
  ble_name: snapshot.identity.bleName ?? undefined,
90
96
  passphrase_protection: snapshot.status.passphraseProtection ?? undefined,
@@ -54,6 +54,7 @@ export interface DeviceButtonRequestPayload extends Omit<PROTO.ButtonRequest, 'c
54
54
 
55
55
  export type PassphraseRequestPayload = {
56
56
  existsAttachPinUser?: boolean;
57
+ deviceOnly?: boolean;
57
58
  source?: 'wallet-session-coordinator';
58
59
  reason?: 'open-wallet' | 'session-recovery';
59
60
  expectedPassphraseState?: string;
@@ -5,15 +5,6 @@ export const LogBlockEvent: Set<string> = new Set([
5
5
  UI_RESPONSE.RECEIVE_PASSPHRASE,
6
6
  ]);
7
7
 
8
- const LogBlockMethod: Set<string> = new Set([
9
- 'evmSignTypedData',
10
- 'openWalletSession',
11
- 'deviceUploadWallpaper',
12
- 'uploadPortfolio',
13
- 'fileWrite',
14
- 'fileRead',
15
- ]);
16
-
17
8
  export function getLogBlockLabel(message: unknown): string | undefined {
18
9
  if (!message || typeof message !== 'object') return undefined;
19
10
 
@@ -28,9 +19,5 @@ export function getLogBlockLabel(message: unknown): string | undefined {
28
19
  }
29
20
 
30
21
  const methodName = method ?? payload?.method;
31
- if (methodName && LogBlockMethod.has(methodName)) {
32
- return methodName;
33
- }
34
-
35
- return undefined;
22
+ return methodName;
36
23
  }
@@ -107,6 +107,7 @@ export interface UiRequestPassphrase {
107
107
  device: Device;
108
108
  passphraseState?: string;
109
109
  existsAttachPinUser?: boolean;
110
+ deviceOnly?: boolean;
110
111
  source?: 'wallet-session-coordinator';
111
112
  reason?: 'open-wallet' | 'session-recovery';
112
113
  expectedPassphraseState?: string;
package/src/index.ts CHANGED
@@ -19,6 +19,11 @@ export * from './types';
19
19
  export { whitelist, whitelistExtension } from './data/config';
20
20
  export { executeCallback, cleanupCallback };
21
21
  export { preloadSessionCache } from './device/Device';
22
+ export {
23
+ getFirmwareUpdateHostBindingGeneration,
24
+ registerFirmwareUpdateHostBinding,
25
+ unregisterFirmwareUpdateHostBinding,
26
+ } from './api/firmware/FirmwareHostBinding';
22
27
 
23
28
  const HardwareSdk = ({
24
29
  init,
package/src/inject.ts CHANGED
@@ -1,6 +1,17 @@
1
- import type { Unsuccessful } from './types';
1
+ import { getFirmwareUpdateCapabilities } from './api/firmware/FirmwareUpdateCapabilities';
2
+ import {
3
+ prepareFirmwareUpdatePlan,
4
+ validateFirmwareUpdatePreparedPlan,
5
+ } from './api/firmware/FirmwareUpdatePreparedPlan';
6
+ import {
7
+ getFirmwareUpdateHostBindingGeneration,
8
+ registerFirmwareUpdateHostBinding,
9
+ unregisterFirmwareUpdateHostBinding,
10
+ } from './api/firmware/FirmwareHostBinding';
11
+
2
12
  import type { EventEmitter } from 'events';
3
13
  import type { CallMethod } from './events';
14
+ import type { Unsuccessful } from './types';
4
15
  import type { CoreApi } from './types/api';
5
16
  import type { AllNetworkAddress } from './types/api/allNetworkGetAddress';
6
17
 
@@ -68,7 +79,10 @@ export const inject = ({
68
79
 
69
80
  call,
70
81
 
71
- dispose,
82
+ dispose: async () => {
83
+ unregisterFirmwareUpdateHostBinding();
84
+ await dispose();
85
+ },
72
86
 
73
87
  uiResponse,
74
88
 
@@ -99,6 +113,12 @@ export const createCoreApi = (
99
113
  | 'updateSettings'
100
114
  | 'switchTransport'
101
115
  > => ({
116
+ getFirmwareUpdateCapabilities,
117
+ registerFirmwareUpdateHostBinding,
118
+ unregisterFirmwareUpdateHostBinding,
119
+ getFirmwareUpdateHostBindingGeneration,
120
+ prepareFirmwareUpdatePlan,
121
+ validateFirmwareUpdatePreparedPlan,
102
122
  getLogs: () => call({ method: 'getLogs' }),
103
123
  clearSessionCache: params => call({ ...params, method: 'clearSessionCache' }),
104
124
  /**
@@ -160,30 +180,7 @@ export const createCoreApi = (
160
180
  call({ ...params, connectId, method: 'deviceGetOnboardingStatus' }),
161
181
  deviceUploadWallpaper: (connectId, params) =>
162
182
  call({ ...params, connectId, method: 'deviceUploadWallpaper' }),
163
- deviceInfoGet: (connectId, params) => call({ ...params, connectId, method: 'deviceInfoGet' }),
164
- deviceStatusGet: (connectId, params) => call({ ...params, connectId, method: 'deviceStatusGet' }),
165
- protocolInfoRequest: (connectId, params) =>
166
- call({ ...params, connectId, method: 'protocolInfoRequest' }),
167
- ping: (connectId, params) => call({ ...params, connectId, method: 'ping' }),
168
- deviceFirmwareUpdate: (connectId, params) =>
169
- call({ ...params, connectId, method: 'deviceFirmwareUpdate' }),
170
- deviceGetFirmwareUpdateStatus: (connectId, params) =>
171
- call({ ...params, connectId, method: 'deviceGetFirmwareUpdateStatus' }),
172
- deviceFactoryInfoSet: (connectId, params) =>
173
- call({ ...params, connectId, method: 'deviceFactoryInfoSet' }),
174
- deviceFactoryInfoGet: (connectId, params) =>
175
- call({ ...params, connectId, method: 'deviceFactoryInfoGet' }),
176
- filesystemPermissionFix: (connectId, params) =>
177
- call({ ...params, connectId, method: 'filesystemPermissionFix' }),
178
- filesystemFormat: connectId => call({ connectId, method: 'filesystemFormat' }),
179
183
  uploadPortfolio: (connectId, params) => call({ ...params, connectId, method: 'uploadPortfolio' }),
180
- fileRead: (connectId, params) => call({ ...params, connectId, method: 'fileRead' }),
181
- fileWrite: (connectId, params) => call({ ...params, connectId, method: 'fileWrite' }),
182
- fileDelete: (connectId, params) => call({ ...params, connectId, method: 'fileDelete' }),
183
- dirList: (connectId, params) => call({ ...params, connectId, method: 'dirList' }),
184
- dirMake: (connectId, params) => call({ ...params, connectId, method: 'dirMake' }),
185
- dirRemove: (connectId, params) => call({ ...params, connectId, method: 'dirRemove' }),
186
- pathInfo: (connectId, params) => call({ ...params, connectId, method: 'pathInfo' }),
187
184
  deviceRecovery: (connectId, params) => call({ ...params, connectId, method: 'deviceRecovery' }),
188
185
  deviceReset: (connectId, params) => call({ ...params, connectId, method: 'deviceReset' }),
189
186
  deviceSettings: (connectId, params) => call({ ...params, connectId, method: 'deviceSettings' }),
@@ -1,4 +1,5 @@
1
1
  import { createCoreApi } from './inject';
2
+ import { unregisterFirmwareUpdateHostBinding } from './api/firmware/FirmwareHostBinding';
2
3
 
3
4
  import type { EventEmitter } from 'events';
4
5
  import type { CallMethod, CoreMessage } from './events';
@@ -43,7 +44,10 @@ export const lowLevelInject = ({
43
44
 
44
45
  call,
45
46
 
46
- dispose,
47
+ dispose: async () => {
48
+ unregisterFirmwareUpdateHostBinding();
49
+ await dispose();
50
+ },
47
51
 
48
52
  uiResponse,
49
53
 
@@ -3,7 +3,6 @@ import type { UnlockPolicy } from '../../api/BaseMethod';
3
3
  import type { ProtocolV2InteractionDescriptor } from './uiInteraction';
4
4
 
5
5
  const LOCK_FREE_DEVICE_SETTINGS = new Set<keyof DeviceSettings>([
6
- 'label',
7
6
  'language',
8
7
  'brightness',
9
8
  'haptic_feedback',
@@ -12,7 +11,7 @@ const LOCK_FREE_DEVICE_SETTINGS = new Set<keyof DeviceSettings>([
12
11
  export const getProtocolV2SettingsUnlockPolicy = (settings: DeviceSettings): UnlockPolicy =>
13
12
  Object.keys(settings).every(key => LOCK_FREE_DEVICE_SETTINGS.has(key as keyof DeviceSettings))
14
13
  ? 'none'
15
- : 'retry-on-locked';
14
+ : 'unlock-before-run';
16
15
 
17
16
  export type ProtocolV2SettingsOperation =
18
17
  | {
@@ -32,7 +31,7 @@ export const getProtocolV2SettingsBehavior = (
32
31
  } => {
33
32
  if (operation.kind === 'page') {
34
33
  return {
35
- unlockPolicy: 'retry-on-locked',
34
+ unlockPolicy: 'unlock-before-run',
36
35
  uiInteraction: {
37
36
  request: 'button',
38
37
  source: 'method-lifecycle',
@@ -0,0 +1,105 @@
1
+ export type UnlockPolicy = 'none' | 'unlock-before-run' | 'retry-on-locked';
2
+
3
+ /**
4
+ * Protocol V2 methods that are safe to replay after a locked response.
5
+ *
6
+ * Keep this as an exact allowlist. Name-pattern matching can silently make a new
7
+ * state-changing method replayable when it happens to share a suffix.
8
+ */
9
+ export const PROTOCOL_V2_RETRY_ON_LOCKED_METHODS = [
10
+ 'cipherKeyValue',
11
+ 'allNetworkGetAddress',
12
+ 'allNetworkGetAddressByLoop',
13
+ 'btcGetAddress',
14
+ 'btcGetPublicKey',
15
+ 'btcSignMessage',
16
+ 'btcSignPsbt',
17
+ 'btcSignTransaction',
18
+ 'btcVerifyMessage',
19
+ 'confluxGetAddress',
20
+ 'confluxSignMessage',
21
+ 'confluxSignTransaction',
22
+ 'evmGetAddress',
23
+ 'evmGetPublicKey',
24
+ 'evmSignMessage',
25
+ 'evmSignTransaction',
26
+ 'evmSignTypedData',
27
+ 'evmVerifyMessage',
28
+ 'starcoinGetAddress',
29
+ 'starcoinGetPublicKey',
30
+ 'starcoinSignMessage',
31
+ 'starcoinSignTransaction',
32
+ 'starcoinVerifyMessage',
33
+ 'nemGetAddress',
34
+ 'nemSignTransaction',
35
+ 'solGetAddress',
36
+ 'solSignTransaction',
37
+ 'solSignOffchainMessage',
38
+ 'solSignMessage',
39
+ 'stellarGetAddress',
40
+ 'stellarSignTransaction',
41
+ 'tronGetAddress',
42
+ 'tronSignMessage',
43
+ 'tronSignTransaction',
44
+ 'nearGetAddress',
45
+ 'nearSignTransaction',
46
+ 'aptosGetAddress',
47
+ 'aptosGetPublicKey',
48
+ 'aptosSignTransaction',
49
+ 'aptosSignMessage',
50
+ 'aptosSignInMessage',
51
+ 'algoGetAddress',
52
+ 'algoSignTransaction',
53
+ 'cosmosGetAddress',
54
+ 'cosmosGetPublicKey',
55
+ 'cosmosSignTransaction',
56
+ 'xrpGetAddress',
57
+ 'xrpSignTransaction',
58
+ 'suiGetAddress',
59
+ 'suiGetPublicKey',
60
+ 'suiSignMessage',
61
+ 'suiSignTransaction',
62
+ 'cardanoGetAddress',
63
+ 'cardanoGetPublicKey',
64
+ 'cardanoSignTransaction',
65
+ 'cardanoSignMessage',
66
+ 'filecoinGetAddress',
67
+ 'filecoinSignTransaction',
68
+ 'polkadotGetAddress',
69
+ 'polkadotSignTransaction',
70
+ 'kaspaGetAddress',
71
+ 'kaspaSignTransaction',
72
+ 'nexaGetAddress',
73
+ 'nexaSignTransaction',
74
+ 'nostrGetPublicKey',
75
+ 'nostrSignEvent',
76
+ 'nostrEncryptMessage',
77
+ 'nostrDecryptMessage',
78
+ 'nostrSignSchnorr',
79
+ 'lnurlAuth',
80
+ 'nervosGetAddress',
81
+ 'nervosSignTransaction',
82
+ 'dnxGetAddress',
83
+ 'dnxSignTransaction',
84
+ 'tonGetAddress',
85
+ 'tonSignMessage',
86
+ 'tonSignProof',
87
+ 'tonSignData',
88
+ 'scdoGetAddress',
89
+ 'scdoSignTransaction',
90
+ 'scdoSignMessage',
91
+ 'alephiumGetAddress',
92
+ 'alephiumSignTransaction',
93
+ 'alephiumSignMessage',
94
+ 'benfenGetAddress',
95
+ 'benfenGetPublicKey',
96
+ 'benfenSignMessage',
97
+ 'benfenSignTransaction',
98
+ 'neoGetAddress',
99
+ 'neoSignTransaction',
100
+ ] as const;
101
+
102
+ const retryOnLockedMethods = new Set<string>(PROTOCOL_V2_RETRY_ON_LOCKED_METHODS);
103
+
104
+ export const getProtocolV2UnlockPolicy = (methodName: string): UnlockPolicy =>
105
+ retryOnLockedMethods.has(methodName) ? 'retry-on-locked' : 'none';
@@ -25,9 +25,7 @@ export async function runMethodWithUnlockRetry(
25
25
  ) {
26
26
  const shouldEmitUi = isProtocolV2UiEnabled(method);
27
27
  const shouldUnlockBeforeRun =
28
- device.isProtocolV2() &&
29
- method.unlockPolicy === 'retry-on-locked' &&
30
- device.features?.unlocked === false;
28
+ device.isProtocolV2() && method.unlockPolicy !== 'none' && device.features?.unlocked === false;
31
29
 
32
30
  if (shouldUnlockBeforeRun) {
33
31
  if (shouldEmitUi) {