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

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 (226) hide show
  1. package/__tests__/DeviceCommands.test.ts +87 -2
  2. package/__tests__/connectSettings.test.ts +47 -5
  3. package/__tests__/device-identity.test.ts +24 -2
  4. package/__tests__/device-lifecycle-events.test.ts +1 -1
  5. package/__tests__/device-settings.test.ts +111 -6
  6. package/__tests__/device-state-events.test.ts +3 -3
  7. package/__tests__/device-state-mapper.test.ts +15 -3
  8. package/__tests__/device-state-store.test.ts +1 -1
  9. package/__tests__/device-wallet-session-store.test.ts +111 -7
  10. package/__tests__/deviceSettings.test.ts +55 -15
  11. package/__tests__/evmLedgerLegacySafety.test.ts +6 -5
  12. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  13. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  14. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  15. package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
  16. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +200 -0
  17. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
  18. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  19. package/__tests__/get-device-state.test.ts +7 -0
  20. package/__tests__/open-wallet-session.test.ts +253 -13
  21. package/__tests__/pro2Wallpaper.test.ts +7 -18
  22. package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
  23. package/__tests__/protocol-v2.test.ts +439 -282
  24. package/__tests__/public-device-state-api.test.ts +79 -68
  25. package/__tests__/public-pro2-api-boundary.test.ts +22 -29
  26. package/dist/api/BaseMethod.d.ts +2 -4
  27. package/dist/api/BaseMethod.d.ts.map +1 -1
  28. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  29. package/dist/api/ClearSessionCache.d.ts.map +1 -1
  30. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  31. package/dist/api/FirmwareUpdateV2.d.ts +11 -0
  32. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  33. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  34. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  35. package/dist/api/FirmwareUpdateV4.d.ts +29 -10
  36. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  37. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  38. package/dist/api/UploadPortfolio.d.ts +0 -1
  39. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  40. package/dist/api/conflux/ConfluxGetAddress.d.ts +1 -0
  41. package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
  42. package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
  43. package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
  44. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
  45. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
  46. package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
  47. package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
  48. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  49. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  50. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  51. package/dist/api/firmware/FirmwareArtifactSource.d.ts +26 -0
  52. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  53. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  54. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  55. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +7 -0
  56. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  57. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  58. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  59. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +24 -0
  60. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  61. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +25 -0
  62. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  63. package/dist/api/firmware/getBinary.d.ts +7 -0
  64. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  65. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  66. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  67. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  68. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  69. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  70. package/dist/api/index.d.ts +0 -20
  71. package/dist/api/index.d.ts.map +1 -1
  72. package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
  73. package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
  74. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  75. package/dist/core/RequestQueue.d.ts +1 -19
  76. package/dist/core/RequestQueue.d.ts.map +1 -1
  77. package/dist/core/index.d.ts +0 -1
  78. package/dist/core/index.d.ts.map +1 -1
  79. package/dist/data-manager/DataManager.d.ts +1 -0
  80. package/dist/data-manager/DataManager.d.ts.map +1 -1
  81. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  82. package/dist/device/Device.d.ts +1 -1
  83. package/dist/device/Device.d.ts.map +1 -1
  84. package/dist/device/DeviceCommands.d.ts.map +1 -1
  85. package/dist/device/DeviceFeaturesState.d.ts +0 -1
  86. package/dist/device/DeviceFeaturesState.d.ts.map +1 -1
  87. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  88. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  89. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  90. package/dist/events/call.d.ts +0 -7
  91. package/dist/events/call.d.ts.map +1 -1
  92. package/dist/events/core.d.ts +2 -2
  93. package/dist/events/core.d.ts.map +1 -1
  94. package/dist/events/iframe.d.ts +0 -1
  95. package/dist/events/iframe.d.ts.map +1 -1
  96. package/dist/events/logBlockEvent.d.ts.map +1 -1
  97. package/dist/index.d.ts +320 -277
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +3210 -1856
  100. package/dist/inject.d.ts +2 -3
  101. package/dist/inject.d.ts.map +1 -1
  102. package/dist/lowLevelInject.d.ts +1 -2
  103. package/dist/lowLevelInject.d.ts.map +1 -1
  104. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  105. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
  106. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
  107. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  108. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  109. package/dist/topLevelInject.d.ts.map +1 -1
  110. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -0
  111. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  112. package/dist/types/api/deviceSettings.d.ts +2 -2
  113. package/dist/types/api/deviceSettings.d.ts.map +1 -1
  114. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  115. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  116. package/dist/types/api/export.d.ts +4 -1
  117. package/dist/types/api/export.d.ts.map +1 -1
  118. package/dist/types/api/firmwareUpdate.d.ts +69 -0
  119. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  120. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  121. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  122. package/dist/types/api/firmwareUpdatePlan.d.ts +27 -0
  123. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  124. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  125. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  126. package/dist/types/api/index.d.ts +10 -23
  127. package/dist/types/api/index.d.ts.map +1 -1
  128. package/dist/types/api/openWalletSession.d.ts +1 -0
  129. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  130. package/dist/types/api/protocolV2.d.ts +3 -84
  131. package/dist/types/api/protocolV2.d.ts.map +1 -1
  132. package/dist/types/api/sessionCache.d.ts +4 -1
  133. package/dist/types/api/sessionCache.d.ts.map +1 -1
  134. package/dist/types/device.d.ts +4 -4
  135. package/dist/types/device.d.ts.map +1 -1
  136. package/dist/types/settings.d.ts +13 -0
  137. package/dist/types/settings.d.ts.map +1 -1
  138. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  139. package/dist/utils/deviceSettings.d.ts +36 -7
  140. package/dist/utils/deviceSettings.d.ts.map +1 -1
  141. package/dist/utils/index.d.ts +2 -1
  142. package/dist/utils/index.d.ts.map +1 -1
  143. package/dist/utils/patch.d.ts +1 -1
  144. package/dist/utils/patch.d.ts.map +1 -1
  145. package/package.json +4 -4
  146. package/src/api/BaseMethod.ts +11 -12
  147. package/src/api/CheckAllFirmwareRelease.ts +16 -3
  148. package/src/api/ClearSessionCache.ts +4 -0
  149. package/src/api/FirmwareUpdate.ts +8 -1
  150. package/src/api/FirmwareUpdateV2.ts +284 -119
  151. package/src/api/FirmwareUpdateV3.ts +248 -55
  152. package/src/api/FirmwareUpdateV4.ts +816 -328
  153. package/src/api/OpenWalletSession.ts +74 -25
  154. package/src/api/UploadPortfolio.ts +3 -13
  155. package/src/api/conflux/ConfluxGetAddress.ts +2 -0
  156. package/src/api/conflux/ConfluxSignMessage.ts +2 -0
  157. package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
  158. package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
  159. package/src/api/device/DeviceChangePin.ts +1 -1
  160. package/src/api/device/DeviceSettings.ts +66 -5
  161. package/src/api/device/DeviceUpdateBootloader.ts +114 -6
  162. package/src/api/device/DeviceWipe.ts +1 -1
  163. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  164. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  165. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
  166. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  167. package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
  168. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +378 -0
  169. package/src/api/firmware/updateBootloader.ts +19 -4
  170. package/src/api/firmware/uploadFirmware.ts +140 -22
  171. package/src/api/helpers/protocolV2FileWrite.ts +6 -0
  172. package/src/api/index.ts +0 -21
  173. package/src/api/nexa/NexaSignTransaction.ts +2 -0
  174. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  175. package/src/core/RequestQueue.ts +4 -123
  176. package/src/core/index.ts +25 -31
  177. package/src/data/messages/messages-protocol-v2.json +77 -4
  178. package/src/data-manager/DataManager.ts +33 -33
  179. package/src/data-manager/connectSettings.ts +11 -0
  180. package/src/device/Device.ts +51 -61
  181. package/src/device/DeviceCommands.ts +58 -14
  182. package/src/device/DeviceFeaturesState.ts +0 -9
  183. package/src/device/DeviceStateMapper.ts +26 -8
  184. package/src/device/DeviceStateProjector.ts +7 -1
  185. package/src/deviceProfile/buildDeviceFeatures.ts +2 -1
  186. package/src/events/call.ts +0 -6
  187. package/src/events/core.ts +0 -2
  188. package/src/events/iframe.ts +0 -1
  189. package/src/events/logBlockEvent.ts +1 -0
  190. package/src/index.ts +5 -4
  191. package/src/inject.ts +22 -35
  192. package/src/lowLevelInject.ts +5 -4
  193. package/src/protocols/protocol-v2/deviceSession.ts +1 -1
  194. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -2
  195. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
  196. package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
  197. package/src/protocols/protocol-v2/walletSession.ts +68 -12
  198. package/src/topLevelInject.ts +5 -3
  199. package/src/types/api/checkAllFirmwareRelease.ts +3 -0
  200. package/src/types/api/deviceSettings.ts +17 -2
  201. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  202. package/src/types/api/export.ts +18 -0
  203. package/src/types/api/firmwareUpdate.ts +76 -0
  204. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  205. package/src/types/api/firmwareUpdatePlan.ts +36 -0
  206. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  207. package/src/types/api/index.ts +14 -41
  208. package/src/types/api/openWalletSession.ts +2 -0
  209. package/src/types/api/protocolV2.ts +3 -187
  210. package/src/types/api/sessionCache.ts +9 -4
  211. package/src/types/device.ts +5 -3
  212. package/src/types/settings.ts +13 -0
  213. package/src/utils/deviceFeaturesUtils.ts +5 -1
  214. package/src/utils/deviceSettings.ts +130 -56
  215. package/src/utils/index.ts +18 -1
  216. package/__tests__/RequestQueue.test.ts +0 -140
  217. package/__tests__/UploadPortfolio.test.ts +0 -46
  218. package/dist/api/protocol-v2/DeviceSettingsGet.d.ts +0 -6
  219. package/dist/api/protocol-v2/DeviceSettingsGet.d.ts.map +0 -1
  220. package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts +0 -16
  221. package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +0 -1
  222. package/dist/api/protocol-v2/DeviceSettingsSet.d.ts +0 -12
  223. package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +0 -1
  224. package/src/api/protocol-v2/DeviceSettingsGet.ts +0 -18
  225. package/src/api/protocol-v2/DeviceSettingsPageShow.ts +0 -70
  226. package/src/api/protocol-v2/DeviceSettingsSet.ts +0 -44
@@ -19,7 +19,7 @@ const createCommands = () => {
19
19
  };
20
20
 
21
21
  describe('DeviceCommands failure mapping', () => {
22
- it('does not log DeviceSessionGet response secrets', async () => {
22
+ it('does not log DeviceSessionOpen response secrets', async () => {
23
23
  const commands = createCommands();
24
24
  const log = getLogger(LoggerNames.DeviceCommands);
25
25
  log.messages.length = 0;
@@ -33,7 +33,7 @@ describe('DeviceCommands failure mapping', () => {
33
33
  btc_test_address: 'secret-wallet-address',
34
34
  },
35
35
  } as any,
36
- 'DeviceSessionGet'
36
+ 'DeviceSessionOpen'
37
37
  )
38
38
  ).resolves.toMatchObject({ type: 'DeviceSession' });
39
39
 
@@ -41,6 +41,68 @@ describe('DeviceCommands failure mapping', () => {
41
41
  expect(JSON.stringify(log.messages)).not.toContain('secret-wallet-address');
42
42
  });
43
43
 
44
+ it('maps an invalid DeviceSessionOpen resume to WalletSessionInvalid', async () => {
45
+ const commands = createCommands();
46
+
47
+ await expect(
48
+ commands._filterCommonTypes(
49
+ {
50
+ type: 'Failure',
51
+ message: {
52
+ code: 'Failure_ProcessError',
53
+ subcode: 2,
54
+ message: 'Invalid session',
55
+ },
56
+ } as any,
57
+ 'DeviceSessionOpen'
58
+ )
59
+ ).rejects.toMatchObject({
60
+ errorCode: HardwareErrorCode.WalletSessionInvalid,
61
+ params: {
62
+ failureCode: 'Failure_ProcessError',
63
+ subcode: 2,
64
+ firmwareMessage: 'Invalid session',
65
+ },
66
+ });
67
+ });
68
+
69
+ it('maps DeviceSessionOpen user cancellation to ActionCancelled', async () => {
70
+ const commands = createCommands();
71
+
72
+ await expect(
73
+ commands._filterCommonTypes(
74
+ {
75
+ type: 'Failure',
76
+ message: {
77
+ code: 'Failure_ProcessError',
78
+ subcode: 1,
79
+ message: 'Cancelled on device',
80
+ },
81
+ } as any,
82
+ 'DeviceSessionOpen'
83
+ )
84
+ ).rejects.toMatchObject({
85
+ errorCode: HardwareErrorCode.ActionCancelled,
86
+ });
87
+ });
88
+
89
+ it.each([
90
+ [3, HardwareErrorCode.DeviceCheckUnlockTypeError],
91
+ [4, HardwareErrorCode.DeviceNotOpenedPassphrase],
92
+ ])('maps DeviceSessionOpen subcode %s to its canonical wallet error', async (subcode, code) => {
93
+ const commands = createCommands();
94
+
95
+ await expect(
96
+ commands._filterCommonTypes(
97
+ {
98
+ type: 'Failure',
99
+ message: { code: 'Failure_ProcessError', subcode, message: 'Session selection failed' },
100
+ } as any,
101
+ 'DeviceSessionOpen'
102
+ )
103
+ ).rejects.toMatchObject({ errorCode: code });
104
+ });
105
+
44
106
  it('rejects the Pro2 bootloader DeviceStatusGet unsupported response', async () => {
45
107
  const commands = createCommands();
46
108
 
@@ -382,3 +444,26 @@ describe('DeviceCommands Protocol V2 interactive response compatibility', () =>
382
444
  });
383
445
  });
384
446
  });
447
+
448
+ describe('DeviceCommands cancellation', () => {
449
+ it('stops waiting after the bounded cancellation timeout', async () => {
450
+ jest.useFakeTimers({ doNotFake: ['performance'] });
451
+ try {
452
+ const commands = createCommands();
453
+ commands.disposed = false;
454
+ commands.callPromise = new Promise(() => {});
455
+ const dispose = jest.fn().mockResolvedValue(undefined);
456
+ commands.dispose = dispose;
457
+
458
+ const cancellation = commands.cancel();
459
+ jest.advanceTimersByTime(10_000);
460
+ await Promise.resolve();
461
+
462
+ await expect(cancellation).resolves.toBeUndefined();
463
+ expect(dispose).toHaveBeenCalledWith(true);
464
+ expect(commands.callPromise).toBeUndefined();
465
+ } finally {
466
+ jest.useRealTimers();
467
+ }
468
+ });
469
+ });
@@ -1,19 +1,61 @@
1
+ import DataManager from '../src/data-manager/DataManager';
1
2
  import { parseConnectSettings } from '../src/data-manager/connectSettings';
2
3
 
4
+ import type { RemoteConfigResponse } from '../src/types';
5
+
6
+ jest.mock('axios', () => ({
7
+ get: jest.fn(),
8
+ }));
9
+
3
10
  jest.mock('../src/data/config', () => ({
4
11
  getSDKVersion: jest.fn(() => '1.0.0'),
5
12
  DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
6
13
  }));
7
14
 
15
+ const mockAxiosGet: jest.Mock = jest.requireMock('axios').get;
16
+
8
17
  describe('parseConnectSettings', () => {
9
- it('keeps custom configFetcher in parsed settings', () => {
10
- const configFetcher = jest.fn();
18
+ const preloadedConfig = {
19
+ bridge: {},
20
+ classic: { firmware: [], ble: [] },
21
+ classic1s: { firmware: [], ble: [] },
22
+ classicpure: { firmware: [], ble: [] },
23
+ mini: { firmware: [], ble: [] },
24
+ touch: { firmware: [], ble: [] },
25
+ pro: { firmware: [], ble: [] },
26
+ pro2: { firmware: [], ble: [] },
27
+ } as unknown as RemoteConfigResponse;
28
+
29
+ it('keeps a serializable preloaded config in parsed settings', () => {
30
+ const settings = parseConnectSettings({
31
+ firmwareManifestMode: 'sdk-managed',
32
+ preloadedConfig,
33
+ });
34
+
35
+ expect(settings.firmwareManifestMode).toBe('sdk-managed');
36
+ expect(settings.preloadedConfig).toBe(preloadedConfig);
37
+ });
38
+
39
+ it('uses the preloaded config before the SDK network client', async () => {
40
+ const settings = parseConnectSettings({
41
+ firmwareManifestMode: 'sdk-managed',
42
+ preloadedConfig,
43
+ });
44
+
45
+ await DataManager.load(settings);
46
+
47
+ expect(mockAxiosGet).not.toHaveBeenCalled();
48
+ expect(DataManager.deviceMap.classic).toEqual({ firmware: [], ble: [] });
49
+ });
11
50
 
51
+ it('fails closed without a preloaded config in external-only mode', async () => {
12
52
  const settings = parseConnectSettings({
13
- fetchConfig: true,
14
- configFetcher,
53
+ firmwareManifestMode: 'external-only',
15
54
  });
16
55
 
17
- expect(settings.configFetcher).toBe(configFetcher);
56
+ await expect(DataManager.load(settings)).rejects.toThrow(
57
+ 'External firmware config snapshot is required'
58
+ );
59
+ expect(mockAxiosGet).not.toHaveBeenCalled();
18
60
  });
19
61
  });
@@ -1,13 +1,35 @@
1
1
  import { checkLiveDeviceId } from '../src/device/deviceIdentity';
2
+ import ConfluxGetAddress from '../src/api/conflux/ConfluxGetAddress';
3
+ import ConfluxSignMessage from '../src/api/conflux/ConfluxSignMessage';
4
+ import ConfluxSignMessageCIP23 from '../src/api/conflux/ConfluxSignMessageCIP23';
5
+ import ConfluxSignTransaction from '../src/api/conflux/ConfluxSignTransaction';
6
+ import NexaSignTransaction from '../src/api/nexa/NexaSignTransaction';
7
+
8
+ jest.mock('../src/data/config', () => ({
9
+ getSDKVersion: jest.fn(() => '1.0.0'),
10
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
11
+ }));
2
12
 
3
13
  describe('live device identity', () => {
14
+ test.each([
15
+ ['confluxGetAddress', ConfluxGetAddress],
16
+ ['confluxSignMessage', ConfluxSignMessage],
17
+ ['confluxSignMessageCIP23', ConfluxSignMessageCIP23],
18
+ ['confluxSignTransaction', ConfluxSignTransaction],
19
+ ['nexaSignTransaction', NexaSignTransaction],
20
+ ])('%s requires a live device id check', (methodName, Method) => {
21
+ const method = new Method({ payload: { method: methodName } });
22
+
23
+ expect(method.checkDeviceId).toBe(true);
24
+ });
25
+
4
26
  test('refreshes Protocol V2 status before comparing the expected device id', async () => {
5
27
  let currentDeviceId = 'OLD_DEVICE_ID';
6
28
  const device = {
7
29
  isProtocolV2: () => true,
8
- getDeviceState: jest.fn().mockImplementation(async () => {
30
+ getDeviceState: jest.fn().mockImplementation(() => {
9
31
  currentDeviceId = 'NEW_DEVICE_ID';
10
- return { identity: { deviceId: currentDeviceId } };
32
+ return Promise.resolve({ identity: { deviceId: currentDeviceId } });
11
33
  }),
12
34
  checkDeviceId: jest.fn((expected: string) => currentDeviceId === expected),
13
35
  };
@@ -83,7 +83,7 @@ describe('public device lifecycle events', () => {
83
83
  const internalDevice = createInitializedDevice(protocol);
84
84
 
85
85
  DevicePool.emitter.emit(DEVICE.CONNECT, internalDevice);
86
- internalDevice.updateState({ identity: { label: 'Updated label' } }, 'apply-settings');
86
+ internalDevice.updateState({ identity: { label: 'Updated label' } }, 'settings-write');
87
87
  DevicePool.emitter.emit(DEVICE.DISCONNECT, internalDevice);
88
88
 
89
89
  const connectMessage = messages.find(message => message.type === DEVICE.CONNECT);
@@ -1,4 +1,4 @@
1
- import { HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
  import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
3
3
 
4
4
  import DeviceSettings from '../src/api/device/DeviceSettings';
@@ -24,6 +24,7 @@ function createDevice({ protocol }: { protocol: 'V1' | 'V2' }) {
24
24
  device: {
25
25
  features,
26
26
  isProtocolV2: () => protocol === 'V2',
27
+ getCurrentDeviceType: () => (protocol === 'V2' ? EDeviceType.Pro2 : EDeviceType.Pro),
27
28
  commands: { typedCall },
28
29
  updateState,
29
30
  getDeviceState,
@@ -69,7 +70,7 @@ describe('DeviceSettings protocol routing', () => {
69
70
 
70
71
  method.init();
71
72
 
72
- expect(method.unlockPolicy).toBe('retry-on-locked');
73
+ expect(method.unlockPolicy).toBe('unlock-before-run');
73
74
  });
74
75
 
75
76
  it('uses ApplySettings and commits the confirmed patch for Protocol V1', async () => {
@@ -80,6 +81,7 @@ describe('DeviceSettings protocol routing', () => {
80
81
  method: 'deviceSettings',
81
82
  label: 'Shared Label',
82
83
  language: 'ja',
84
+ bluetoothEnabled: true,
83
85
  },
84
86
  });
85
87
  method.init();
@@ -98,14 +100,15 @@ describe('DeviceSettings protocol routing', () => {
98
100
  safety_checks: undefined,
99
101
  experimental_features: undefined,
100
102
  auto_shutdown_delay_ms: undefined,
103
+ use_ble: true,
101
104
  haptic_feedback: undefined,
102
105
  });
103
106
  expect(updateState).toHaveBeenCalledWith(
104
107
  {
105
108
  identity: { label: 'Shared Label' },
106
- settings: { language: 'ja' },
109
+ settings: { language: 'ja', bleEnabled: true },
107
110
  },
108
- 'apply-settings'
111
+ 'settings-write'
109
112
  );
110
113
  });
111
114
 
@@ -119,6 +122,7 @@ describe('DeviceSettings protocol routing', () => {
119
122
  language: 'ja',
120
123
  autoLockDelayMs: 60_000,
121
124
  hapticFeedback: true,
125
+ bluetoothEnabled: true,
122
126
  },
123
127
  });
124
128
  method.init();
@@ -129,6 +133,7 @@ describe('DeviceSettings protocol routing', () => {
129
133
  settings: {
130
134
  label: 'Shared Label',
131
135
  language: 'ja-Jpan-JP',
136
+ bt_enable: true,
132
137
  autolock_delay_ms: 60_000,
133
138
  haptic_feedback: true,
134
139
  },
@@ -137,12 +142,13 @@ describe('DeviceSettings protocol routing', () => {
137
142
  {
138
143
  identity: { label: 'Shared Label' },
139
144
  settings: {
140
- language: 'ja-Jpan-JP',
145
+ language: 'ja',
146
+ bleEnabled: true,
141
147
  autoLockDelayMs: 60_000,
142
148
  hapticFeedback: true,
143
149
  },
144
150
  },
145
- 'apply-settings'
151
+ 'settings-write'
146
152
  );
147
153
  expect(typedCall.mock.calls.map(call => call[0])).not.toEqual(
148
154
  expect.arrayContaining(['DeviceInfoGet', 'DeviceStatusGet', 'DeviceSettingsGet'])
@@ -283,4 +289,103 @@ describe('DeviceSettings protocol routing', () => {
283
289
  });
284
290
  expect(typedCall).not.toHaveBeenCalled();
285
291
  });
292
+
293
+ it('rejects Protocol V1-only settings on Protocol V2 without partially applying the request', async () => {
294
+ const { device, typedCall } = createDevice({ protocol: 'V2' });
295
+ const method = new DeviceSettings({
296
+ id: 9,
297
+ payload: {
298
+ method: 'deviceSettings',
299
+ label: 'Must not be partially applied',
300
+ safetyChecks: 0,
301
+ },
302
+ });
303
+ method.init();
304
+ (method as any).device = device;
305
+
306
+ await expect(method.run()).rejects.toMatchObject({
307
+ errorCode: HardwareErrorCode.CallMethodInvalidParameter,
308
+ message: 'Protocol V2 does not support settings: safetyChecks.',
309
+ });
310
+ expect(typedCall).not.toHaveBeenCalled();
311
+ });
312
+
313
+ it('rejects Protocol V2-only settings on Protocol V1 without partially applying the request', async () => {
314
+ const { device, typedCall } = createDevice({ protocol: 'V1' });
315
+ const method = new DeviceSettings({
316
+ id: 10,
317
+ payload: {
318
+ method: 'deviceSettings',
319
+ label: 'Must not be partially applied',
320
+ brightness: 80,
321
+ },
322
+ });
323
+ method.init();
324
+ (method as any).device = device;
325
+
326
+ await expect(method.run()).rejects.toMatchObject({
327
+ errorCode: HardwareErrorCode.CallMethodInvalidParameter,
328
+ message: 'Protocol V1 does not support settings: brightness.',
329
+ });
330
+ expect(typedCall).not.toHaveBeenCalled();
331
+ });
332
+
333
+ it('accepts the Protocol V2 never timeout wire value', async () => {
334
+ const { device, typedCall } = createDevice({ protocol: 'V2' });
335
+ const method = new DeviceSettings({
336
+ id: 11,
337
+ payload: {
338
+ method: 'deviceSettings',
339
+ autoLockDelayMs: 0x10000000,
340
+ autoShutdownDelayMs: 0x10000000,
341
+ },
342
+ });
343
+ method.init();
344
+ (method as any).device = device;
345
+
346
+ await method.run();
347
+
348
+ expect(typedCall).toHaveBeenCalledWith('DeviceSettingsSet', 'Success', {
349
+ settings: {
350
+ autolock_delay_ms: 0x10000000,
351
+ autoshutdown_delay_ms: 0x10000000,
352
+ },
353
+ });
354
+ });
355
+
356
+ it('rejects unsupported Protocol V2 timeout values before sending a command', async () => {
357
+ const { device, typedCall } = createDevice({ protocol: 'V2' });
358
+ const method = new DeviceSettings({
359
+ id: 12,
360
+ payload: {
361
+ method: 'deviceSettings',
362
+ autoLockDelayMs: 0,
363
+ },
364
+ });
365
+ method.init();
366
+ (method as any).device = device;
367
+
368
+ await expect(method.run()).rejects.toMatchObject({
369
+ errorCode: HardwareErrorCode.CallMethodInvalidParameter,
370
+ });
371
+ expect(typedCall).not.toHaveBeenCalled();
372
+ });
373
+
374
+ it('rejects unknown Protocol V1 safety check levels before sending a command', async () => {
375
+ const { device, typedCall } = createDevice({ protocol: 'V1' });
376
+ const method = new DeviceSettings({
377
+ id: 13,
378
+ payload: {
379
+ method: 'deviceSettings',
380
+ safetyChecks: 3,
381
+ },
382
+ } as never);
383
+ method.init();
384
+ (method as any).device = device;
385
+
386
+ await expect(method.run()).rejects.toMatchObject({
387
+ errorCode: HardwareErrorCode.CallMethodInvalidParameter,
388
+ });
389
+ expect(typedCall).not.toHaveBeenCalled();
390
+ });
286
391
  });
@@ -27,7 +27,7 @@ describe('Device state events', () => {
27
27
  protocolV2DeviceInfo: { protocol_version: 2 },
28
28
  },
29
29
  },
30
- 'apply-settings'
30
+ 'settings-write'
31
31
  );
32
32
 
33
33
  expect(state?.identity.label).toBe('Renamed');
@@ -36,7 +36,7 @@ describe('Device state events', () => {
36
36
  expect.objectContaining({
37
37
  state: expect.objectContaining({ revision: 1 }),
38
38
  revision: 1,
39
- source: 'apply-settings',
39
+ source: 'settings-write',
40
40
  })
41
41
  );
42
42
  expect(onFeatures).not.toHaveBeenCalled();
@@ -96,7 +96,7 @@ describe('Device state events', () => {
96
96
  protocol: 'V1',
97
97
  identity: { label: 'Legacy' },
98
98
  },
99
- 'apply-settings'
99
+ 'settings-write'
100
100
  );
101
101
 
102
102
  expect(onFeatures).toHaveBeenCalledWith(device, expect.objectContaining({ label: 'Legacy' }));
@@ -1,5 +1,6 @@
1
1
  import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
2
 
3
+ import { Enum_SafetyCheckLevel } from '@onekeyfe/hd-transport';
3
4
  import {
4
5
  mapApplySettingsToState,
5
6
  mapDeviceSettingsToState,
@@ -208,22 +209,33 @@ describe('DeviceStateMapper', () => {
208
209
 
209
210
  test('maps common settings into identity, status and settings sections', () => {
210
211
  expect(
211
- mapApplySettingsToState({ label: 'Renamed', use_passphrase: true, haptic_feedback: false })
212
+ mapApplySettingsToState({
213
+ label: 'Renamed',
214
+ use_passphrase: true,
215
+ haptic_feedback: false,
216
+ use_ble: true,
217
+ safety_checks: 'Strict',
218
+ })
212
219
  ).toEqual({
213
220
  identity: { label: 'Renamed' },
214
221
  status: { passphraseProtection: true },
215
- settings: { hapticFeedback: false },
222
+ settings: {
223
+ hapticFeedback: false,
224
+ bleEnabled: true,
225
+ safetyChecks: Enum_SafetyCheckLevel.Strict,
226
+ },
216
227
  });
217
228
 
218
229
  expect(
219
230
  mapDeviceSettingsToState({
220
231
  label: 'Pro2',
232
+ language: 'en-Latn-US',
221
233
  brightness: 80,
222
234
  autolock_delay_ms: 30_000,
223
235
  } as DeviceSettings)
224
236
  ).toEqual({
225
237
  identity: { label: 'Pro2' },
226
- settings: { brightness: 80, autoLockDelayMs: 30_000 },
238
+ settings: { language: 'en', brightness: 80, autoLockDelayMs: 30_000 },
227
239
  });
228
240
  });
229
241
  });
@@ -14,7 +14,7 @@ describe('DeviceStateStore', () => {
14
14
  store.update({ identity: { bleName: 'Pro2 1234', label: null } }, 'initialize');
15
15
  const result = store.update(
16
16
  { identity: { label: 'My Wallet', bleName: undefined } },
17
- 'apply-settings'
17
+ 'settings-write'
18
18
  );
19
19
 
20
20
  expect(result.state.identity).toMatchObject({
@@ -1,3 +1,5 @@
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
1
3
  import ClearSessionCache from '../src/api/ClearSessionCache';
2
4
  import {
3
5
  DeviceWalletSessionStore,
@@ -5,6 +7,7 @@ import {
5
7
  } from '../src/device/DeviceWalletSessionStore';
6
8
  import { Device } from '../src/device/Device';
7
9
  import { createCoreApi } from '../src/inject';
10
+ import TransportManager from '../src/data-manager/TransportManager';
8
11
 
9
12
  jest.mock('../src/data/config', () => ({
10
13
  getSDKVersion: jest.fn(() => '1.0.0'),
@@ -116,7 +119,19 @@ describe('ClearSessionCache', () => {
116
119
  expect(deviceWalletSessionStore.get('device-2', 'hidden-a')).toBe('session-b');
117
120
  });
118
121
 
119
- test('keeps legacy Features session fields as projections of the store', async () => {
122
+ test('rejects passphraseState without a deviceId instead of clearing every device', () => {
123
+ deviceWalletSessionStore.set('device-1', 'hidden-a', 'session-a');
124
+ deviceWalletSessionStore.set('device-2', 'hidden-a', 'session-b');
125
+ const method = new ClearSessionCache({
126
+ payload: { method: 'clearSessionCache', passphraseState: 'hidden-a' },
127
+ });
128
+
129
+ expect(() => method.init()).toThrow('Parameter [deviceId] is required with [passphraseState].');
130
+ expect(deviceWalletSessionStore.get('device-1', 'hidden-a')).toBe('session-a');
131
+ expect(deviceWalletSessionStore.get('device-2', 'hidden-a')).toBe('session-b');
132
+ });
133
+
134
+ test('keeps wallet sessions internal instead of projecting them through Features', async () => {
120
135
  const device = Device.fromDescriptor({ id: 'one', path: 'one' } as never);
121
136
  device.features = {
122
137
  protocol: 'V1',
@@ -133,11 +148,14 @@ describe('ClearSessionCache', () => {
133
148
  } as never;
134
149
 
135
150
  expect(device.state).not.toHaveProperty('session');
151
+ expect(device.getInternalState()).toBe('session-a');
136
152
  expect(device.features).toMatchObject({
137
- sessionId: 'session-a',
138
- session_id: 'session-a',
139
- passphraseState: 'hidden-a',
153
+ sessionId: null,
154
+ session_id: null,
140
155
  });
156
+ expect(device.features).not.toHaveProperty('passphraseState');
157
+ expect(device.features?.raw?.protocolV1Features).not.toHaveProperty('sessionId');
158
+ expect(device.features?.raw?.protocolV1Features).not.toHaveProperty('session_id');
141
159
 
142
160
  const method = new ClearSessionCache({
143
161
  payload: {
@@ -151,10 +169,20 @@ describe('ClearSessionCache', () => {
151
169
 
152
170
  expect(device.features?.sessionId).toBeNull();
153
171
  expect(device.features?.session_id).toBeNull();
154
- expect(device.state?.raw?.protocolV1Features).toMatchObject({
172
+ expect(device.state?.raw?.protocolV1Features).not.toHaveProperty('sessionId');
173
+ expect(device.state?.raw?.protocolV1Features).not.toHaveProperty('session_id');
174
+ });
175
+
176
+ test('uses only the transport path as the temporary Protocol V2 cache key', () => {
177
+ const device = Device.fromDescriptor({ id: 'descriptor-id' } as never);
178
+ device.features = {
179
+ protocol: 'V2',
180
+ passphraseState: 'hidden-a',
155
181
  sessionId: 'session-a',
156
- session_id: 'session-a',
157
- });
182
+ } as never;
183
+
184
+ expect(device.getInternalState()).toBeUndefined();
185
+ expect(deviceWalletSessionStore.get('descriptor-id', 'hidden-a')).toBeUndefined();
158
186
  });
159
187
 
160
188
  test('clears one wallet without using a device', async () => {
@@ -211,3 +239,79 @@ describe('ClearSessionCache', () => {
211
239
  });
212
240
  });
213
241
  });
242
+
243
+ describe('Protocol V1 wallet identity initialization', () => {
244
+ beforeEach(() => {
245
+ deviceWalletSessionStore.clear();
246
+ jest.restoreAllMocks();
247
+ });
248
+
249
+ test('verifies the live device id before sending a cached wallet session', async () => {
250
+ const device = Device.fromDescriptor({ id: 'connect-b', path: 'connect-b' } as never);
251
+ device.features = {
252
+ protocol: 'V1',
253
+ deviceId: 'cached-device-a',
254
+ unlocked: true,
255
+ passphraseProtection: true,
256
+ } as never;
257
+ deviceWalletSessionStore.set('cached-device-a', 'hidden-a', 'session-a');
258
+ const typedCall = jest.fn().mockResolvedValue({
259
+ type: 'Features',
260
+ message: { device_id: 'live-device-b' },
261
+ });
262
+ device.commands = { typedCall } as never;
263
+ jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
264
+
265
+ await expect(
266
+ device.initialize({ deviceId: 'cached-device-a', passphraseState: 'hidden-a' })
267
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceCheckDeviceIdError });
268
+ expect(typedCall).toHaveBeenCalledTimes(1);
269
+ expect(typedCall).toHaveBeenCalledWith(
270
+ 'Initialize',
271
+ 'Features',
272
+ expect.not.objectContaining({ session_id: 'session-a' }),
273
+ expect.any(Object)
274
+ );
275
+ });
276
+
277
+ test('resumes a cached V1 wallet only after the live device id matches', async () => {
278
+ const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
279
+ device.features = {
280
+ protocol: 'V1',
281
+ deviceId: 'device-a',
282
+ unlocked: true,
283
+ passphraseProtection: true,
284
+ } as never;
285
+ deviceWalletSessionStore.set('device-a', 'hidden-a', 'session-a');
286
+ const typedCall = jest
287
+ .fn()
288
+ .mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } })
289
+ .mockResolvedValueOnce({
290
+ type: 'Features',
291
+ message: { device_id: 'device-a', session_id: 'session-a' },
292
+ });
293
+ device.commands = { typedCall } as never;
294
+ jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
295
+
296
+ await device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' });
297
+
298
+ expect(typedCall).toHaveBeenCalledTimes(2);
299
+ expect(typedCall).toHaveBeenNthCalledWith(
300
+ 1,
301
+ 'Initialize',
302
+ 'Features',
303
+ expect.not.objectContaining({ session_id: 'session-a' }),
304
+ expect.any(Object)
305
+ );
306
+ expect(typedCall).toHaveBeenNthCalledWith(
307
+ 2,
308
+ 'Initialize',
309
+ 'Features',
310
+ expect.objectContaining({
311
+ session_id: 'session-a',
312
+ passphrase_state: 'hidden-a',
313
+ }),
314
+ expect.any(Object)
315
+ );
316
+ });
317
+ });