@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
@@ -30,58 +30,55 @@ const createV1Device = (typedCall: jest.Mock) => {
30
30
  describe('getDeviceState', () => {
31
31
  test('does not expose the internal wallet session', async () => {
32
32
  const device = createV2Device(jest.fn());
33
- device.updateState(
34
- {
35
- protocol: 'V2',
36
- session: { sessionId: 'private-session', passphraseState: 'private-state' },
37
- },
38
- 'initialize'
39
- );
33
+ device.updateState({ protocol: 'V2' }, 'initialize');
40
34
 
41
35
  const state = await device.getDeviceState();
42
36
 
43
37
  expect(state).not.toHaveProperty('session');
44
38
  });
45
39
 
46
- test('hydrates Protocol V2 without status target or DeviceStatusGet by default', async () => {
47
- const typedCall = jest.fn().mockResolvedValue({
48
- message: {
49
- protocol_version: 2,
50
- hw: { serial_no: 'SERIAL-1' },
51
- fw: { application: { version: '5.0.0' } },
52
- },
53
- });
40
+ test('hydrates Protocol V2 with separate DeviceInfoGet and DeviceStatusGet', async () => {
41
+ const typedCall = jest.fn().mockImplementation((requestType: string) => ({
42
+ message:
43
+ requestType === 'DeviceInfoGet'
44
+ ? {
45
+ protocol_version: 2,
46
+ hw: { serial_no: 'SERIAL-1' },
47
+ fw: { application: { version: '5.0.0' } },
48
+ }
49
+ : { init_states: true, unlocked: true },
50
+ }));
54
51
  const device = createV2Device(typedCall);
55
52
 
56
53
  const state = await device.getDeviceState();
57
54
 
58
55
  expect(state.identity.serialNo).toBe('SERIAL-1');
59
- expect(typedCall).toHaveBeenCalledTimes(1);
56
+ expect(typedCall).toHaveBeenCalledTimes(2);
60
57
  expect(typedCall.mock.calls[0][0]).toBe('DeviceInfoGet');
61
58
  expect(typedCall.mock.calls[0][2]?.targets?.status).not.toBe(true);
62
- expect(typedCall).not.toHaveBeenCalledWith(
63
- 'DeviceStatusGet',
64
- expect.anything(),
65
- expect.anything()
66
- );
59
+ expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
67
60
  });
68
61
 
69
- test('uses a single full DeviceInfoGet when firmware refresh initializes the state', async () => {
70
- const typedCall = jest.fn().mockResolvedValue({
71
- message: {
72
- protocol_version: 2,
73
- hw: { serial_no: 'SERIAL-1' },
74
- fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
75
- },
76
- });
62
+ test('uses one full DeviceInfoGet plus DeviceStatusGet when firmware refresh initializes state', async () => {
63
+ const typedCall = jest.fn().mockImplementation((requestType: string) => ({
64
+ message:
65
+ requestType === 'DeviceInfoGet'
66
+ ? {
67
+ protocol_version: 2,
68
+ hw: { serial_no: 'SERIAL-1' },
69
+ fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
70
+ }
71
+ : { init_states: true, unlocked: true },
72
+ }));
77
73
  const device = createV2Device(typedCall);
78
74
 
79
75
  await device.getDeviceState({
80
76
  refreshSections: ['identity', 'versions', 'verification'],
81
77
  });
82
78
 
83
- expect(typedCall).toHaveBeenCalledTimes(1);
84
- expect(typedCall).toHaveBeenCalledWith(
79
+ expect(typedCall).toHaveBeenCalledTimes(2);
80
+ expect(typedCall).toHaveBeenNthCalledWith(
81
+ 1,
85
82
  'DeviceInfoGet',
86
83
  'DeviceInfo',
87
84
  expect.objectContaining({
@@ -117,16 +114,32 @@ describe('getDeviceState', () => {
117
114
  });
118
115
 
119
116
  test.each(['bootloader', 'romloader'] as const)(
120
- 'does not call DeviceStatusGet in %s mode',
117
+ 'temporarily treats failed DeviceStatusGet with %s metadata as bootloader',
121
118
  async mode => {
122
- const typedCall = jest.fn();
119
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
120
+ if (requestType === 'DeviceInfoGet') {
121
+ return {
122
+ message: {
123
+ hw: { serial_no: 'SERIAL-1' },
124
+ fw:
125
+ mode === 'romloader'
126
+ ? { romloader: { version: '1.0.0' } }
127
+ : { bootloader: { version: '1.0.0' } },
128
+ },
129
+ };
130
+ }
131
+ throw new Error('DeviceStatusGet unavailable');
132
+ });
123
133
  const device = createV2Device(typedCall);
124
134
  device.updateState({ protocol: 'V2', status: { mode } }, 'initialize');
125
135
 
126
136
  const state = await device.getDeviceState({ refreshSections: ['status'] });
127
137
 
128
- expect(state.status.mode).toBe(mode);
129
- expect(typedCall).not.toHaveBeenCalled();
138
+ expect(state.status.mode).toBe('bootloader');
139
+ expect(typedCall.mock.calls.map(call => call[0])).toEqual([
140
+ 'DeviceInfoGet',
141
+ 'DeviceStatusGet',
142
+ ]);
130
143
  }
131
144
  );
132
145
 
@@ -170,6 +183,14 @@ describe('getDeviceState', () => {
170
183
 
171
184
  test('refreshes Protocol V2 status before settings in a combined read', async () => {
172
185
  const typedCall = jest.fn().mockImplementation((requestType: string) => {
186
+ if (requestType === 'DeviceInfoGet') {
187
+ return {
188
+ message: {
189
+ hw: { serial_no: 'SERIAL-1' },
190
+ fw: { application: { version: '5.0.0' } },
191
+ },
192
+ };
193
+ }
173
194
  if (requestType === 'DeviceStatusGet') {
174
195
  return {
175
196
  message: { init_states: true, unlocked: false, device_id: 'device-1' },
@@ -186,6 +207,7 @@ describe('getDeviceState', () => {
186
207
  const state = await device.getDeviceState({ refreshSections: ['status', 'settings'] });
187
208
 
188
209
  expect(typedCall.mock.calls.map(call => call[0])).toEqual([
210
+ 'DeviceInfoGet',
189
211
  'DeviceStatusGet',
190
212
  'DeviceSettingsGet',
191
213
  ]);
@@ -195,6 +217,14 @@ describe('getDeviceState', () => {
195
217
 
196
218
  test('keeps the refreshed status when a following settings read fails', async () => {
197
219
  const typedCall = jest.fn().mockImplementation((requestType: string) => {
220
+ if (requestType === 'DeviceInfoGet') {
221
+ return {
222
+ message: {
223
+ hw: { serial_no: 'SERIAL-1' },
224
+ fw: { application: { version: '5.0.0' } },
225
+ },
226
+ };
227
+ }
198
228
  if (requestType === 'DeviceStatusGet') {
199
229
  return {
200
230
  message: { init_states: true, unlocked: false, device_id: 'device-1' },
@@ -335,12 +365,18 @@ describe('getDeviceState', () => {
335
365
  });
336
366
 
337
367
  test('reloads Protocol V2 DeviceInfo after a physical reconnect', async () => {
338
- const typedCall = jest.fn().mockResolvedValue({
339
- message: {
340
- protocol_version: 1,
341
- hw: { serial_no: 'SERIAL-1' },
342
- fw: { bootloader: { version: '1.0.0' } },
343
- },
368
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
369
+ if (requestType === 'DeviceInfoGet') {
370
+ return {
371
+ message: {
372
+ protocol_version: 1,
373
+ hw: { serial_no: 'SERIAL-1' },
374
+ fw: { bootloader: { version: '1.0.0' } },
375
+ se1: {},
376
+ },
377
+ };
378
+ }
379
+ throw new Error('DeviceStatusGet unavailable in bootloader');
344
380
  });
345
381
  const device = createV2Device(typedCall);
346
382
  device.updateState(
@@ -31,20 +31,33 @@ describe('getLogBlockLabel', () => {
31
31
  ).toBe('evmSignTypedData');
32
32
  });
33
33
 
34
- it('blocks deviceSessionOpen params before logging a host passphrase', () => {
34
+ it('keeps existing sensitive UI response blocking', () => {
35
+ expect(getLogBlockLabel({ type: UI_RESPONSE.RECEIVE_PIN })).toBe(UI_RESPONSE.RECEIVE_PIN);
36
+ });
37
+
38
+ it('blocks openWalletSession wallet identifiers in direct and iframe call logging', () => {
39
+ const payload = {
40
+ method: 'openWalletSession',
41
+ mode: 'resume-hidden',
42
+ deviceId: 'device-id',
43
+ passphraseState: 'wallet-identifier',
44
+ };
45
+
46
+ expect(getLogBlockLabel(payload)).toBe('openWalletSession');
35
47
  expect(
36
48
  getLogBlockLabel({
37
- method: 'deviceSessionOpen',
38
- select: {
39
- host_passphrase: {
40
- passphrase: 'do-not-log-this',
41
- },
42
- },
49
+ event: 'iframe-call',
50
+ type: 'iframe-call',
51
+ payload,
43
52
  })
44
- ).toBe('deviceSessionOpen');
53
+ ).toBe('openWalletSession');
45
54
  });
46
55
 
47
- it('keeps existing sensitive UI response blocking', () => {
48
- expect(getLogBlockLabel({ type: UI_RESPONSE.RECEIVE_PIN })).toBe(UI_RESPONSE.RECEIVE_PIN);
49
- });
56
+ it.each(['deviceUploadWallpaper', 'uploadPortfolio', 'fileWrite', 'fileRead'])(
57
+ 'blocks binary payload logging for %s',
58
+ method => {
59
+ expect(getLogBlockLabel({ method, data: new Uint8Array(1024) })).toBe(method);
60
+ expect(getLogBlockLabel({ payload: { method, data: new Uint8Array(1024) } })).toBe(method);
61
+ }
62
+ );
50
63
  });
@@ -0,0 +1,386 @@
1
+ import axios from 'axios';
2
+
3
+ import { httpRequest } from '../src/utils/networkUtils';
4
+
5
+ describe('networkUtils httpRequest retry bounds', () => {
6
+ afterEach(() => {
7
+ jest.restoreAllMocks();
8
+ jest.useRealTimers();
9
+ });
10
+
11
+ it('keeps timeout, retries, and cancellation opt-in for existing callers', async () => {
12
+ const requestSpy = jest.spyOn(axios, 'request').mockResolvedValue({
13
+ status: 200,
14
+ data: {
15
+ ok: true,
16
+ },
17
+ });
18
+
19
+ await expect(httpRequest('https://example.com/data.json', 'json')).resolves.toEqual({
20
+ ok: true,
21
+ });
22
+
23
+ expect(requestSpy).toHaveBeenCalledTimes(1);
24
+ expect(requestSpy.mock.calls[0][0]).not.toHaveProperty('timeout');
25
+ expect(requestSpy.mock.calls[0][0]).not.toHaveProperty('signal');
26
+ expect(requestSpy.mock.calls[0][0]).not.toHaveProperty('adapter');
27
+ expect(requestSpy.mock.calls[0][0]).not.toHaveProperty('onDownloadProgress');
28
+ });
29
+
30
+ it('passes the per-attempt timeout and stops after the configured retry count', async () => {
31
+ const timeoutError = Object.assign(new Error('timeout'), {
32
+ code: 'ECONNABORTED',
33
+ isAxiosError: true,
34
+ });
35
+ const requestSpy = jest.spyOn(axios, 'request').mockRejectedValue(timeoutError);
36
+
37
+ await expect(
38
+ httpRequest('https://example.com/firmware.bin', 'binary', {
39
+ timeoutMs: 1234,
40
+ maxRetries: 2,
41
+ retryDelayMs: 0,
42
+ })
43
+ ).rejects.toBe(timeoutError);
44
+
45
+ expect(requestSpy).toHaveBeenCalledTimes(3);
46
+ expect(requestSpy).toHaveBeenNthCalledWith(
47
+ 1,
48
+ expect.objectContaining({
49
+ timeout: 1234,
50
+ })
51
+ );
52
+ });
53
+
54
+ it('retries a connection deadline and stops at the configured attempt bound', async () => {
55
+ jest.useFakeTimers({ doNotFake: ['performance'] });
56
+ const requestSpy = jest.spyOn(axios, 'request').mockImplementation(
57
+ config =>
58
+ new Promise((_resolve, reject) => {
59
+ config.signal?.addEventListener(
60
+ 'abort',
61
+ () => {
62
+ reject(
63
+ Object.assign(new Error('canceled'), {
64
+ __CANCEL__: true,
65
+ code: 'ERR_CANCELED',
66
+ isAxiosError: true,
67
+ })
68
+ );
69
+ },
70
+ { once: true }
71
+ );
72
+ })
73
+ );
74
+ const requestPromise = httpRequest('https://example.com/firmware.bin', 'binary', {
75
+ connectTimeoutMs: 1_000,
76
+ maxRetries: 1,
77
+ retryDelayMs: 0,
78
+ });
79
+ const rejection = expect(requestPromise).rejects.toMatchObject({
80
+ name: 'HttpRequestPhaseTimeoutError',
81
+ code: 'ECONNECTTIMEDOUT',
82
+ message: expect.stringContaining('connect timeout'),
83
+ });
84
+
85
+ await Promise.resolve();
86
+ jest.advanceTimersByTime(1_000);
87
+ await Promise.resolve();
88
+ await Promise.resolve();
89
+ expect(requestSpy).toHaveBeenCalledTimes(2);
90
+
91
+ jest.advanceTimersByTime(1_000);
92
+
93
+ await rejection;
94
+ expect(requestSpy).toHaveBeenCalledTimes(2);
95
+ expect(requestSpy.mock.calls[0][0].adapter).toEqual(['xhr', 'http']);
96
+ });
97
+
98
+ it('restarts the read-idle deadline only when loaded bytes increase', async () => {
99
+ jest.useFakeTimers({ doNotFake: ['performance'] });
100
+ let requestConfig: Parameters<typeof axios.request>[0] | undefined;
101
+ const requestSpy = jest.spyOn(axios, 'request').mockImplementation(
102
+ config =>
103
+ new Promise((_resolve, reject) => {
104
+ requestConfig = config;
105
+ config.signal?.addEventListener(
106
+ 'abort',
107
+ () => {
108
+ reject(
109
+ Object.assign(new Error('canceled'), {
110
+ __CANCEL__: true,
111
+ code: 'ERR_CANCELED',
112
+ isAxiosError: true,
113
+ })
114
+ );
115
+ },
116
+ { once: true }
117
+ );
118
+ })
119
+ );
120
+ const requestPromise = httpRequest('https://example.com/firmware.bin', 'binary', {
121
+ connectTimeoutMs: 1_000,
122
+ readTimeoutMs: 2_000,
123
+ });
124
+ const rejection = expect(requestPromise).rejects.toMatchObject({
125
+ name: 'HttpRequestPhaseTimeoutError',
126
+ code: 'EREADTIMEDOUT',
127
+ message: expect.stringContaining('read timeout'),
128
+ });
129
+
130
+ await Promise.resolve();
131
+ jest.advanceTimersByTime(500);
132
+ requestConfig?.onDownloadProgress?.({
133
+ bytes: 0,
134
+ lengthComputable: false,
135
+ loaded: 0,
136
+ });
137
+ jest.advanceTimersByTime(400);
138
+ requestConfig?.onDownloadProgress?.({
139
+ bytes: 1,
140
+ lengthComputable: false,
141
+ loaded: 1,
142
+ });
143
+ jest.advanceTimersByTime(900);
144
+ requestConfig?.onDownloadProgress?.({
145
+ bytes: 1,
146
+ lengthComputable: false,
147
+ loaded: 2,
148
+ });
149
+ jest.advanceTimersByTime(1_900);
150
+ expect(requestConfig?.signal?.aborted).toBe(false);
151
+
152
+ requestConfig?.onDownloadProgress?.({
153
+ bytes: 0,
154
+ lengthComputable: false,
155
+ loaded: 2,
156
+ });
157
+ jest.advanceTimersByTime(99);
158
+ expect(requestConfig?.signal?.aborted).toBe(false);
159
+
160
+ jest.advanceTimersByTime(1);
161
+
162
+ await rejection;
163
+ expect(requestSpy).toHaveBeenCalledTimes(1);
164
+ expect(requestConfig?.signal?.aborted).toBe(true);
165
+ });
166
+
167
+ it('ignores progress callbacks after the attempt has settled', async () => {
168
+ let requestConfig: Parameters<typeof axios.request>[0] | undefined;
169
+ jest.spyOn(axios, 'request').mockImplementation(config => {
170
+ requestConfig = config;
171
+ return Promise.resolve({
172
+ status: 200,
173
+ data: new ArrayBuffer(4),
174
+ });
175
+ });
176
+
177
+ await httpRequest('https://example.com/firmware.bin', 'binary', {
178
+ connectTimeoutMs: 1_000,
179
+ readTimeoutMs: 2_000,
180
+ });
181
+
182
+ const timeoutSpy = jest.spyOn(global, 'setTimeout');
183
+ requestConfig?.onDownloadProgress?.({
184
+ bytes: 1,
185
+ lengthComputable: false,
186
+ loaded: 1,
187
+ });
188
+
189
+ expect(timeoutSpy).not.toHaveBeenCalled();
190
+ });
191
+
192
+ it.each(['ECONNRESET', 'ENOTFOUND', 'ERR_NETWORK', 'ETIMEDOUT'])(
193
+ 'retries transient network error %s',
194
+ async code => {
195
+ const networkError = Object.assign(new Error('temporarily unreachable'), {
196
+ code,
197
+ isAxiosError: true,
198
+ });
199
+ const firmwareBinary = new ArrayBuffer(4);
200
+ const requestSpy = jest
201
+ .spyOn(axios, 'request')
202
+ .mockRejectedValueOnce(networkError)
203
+ .mockResolvedValueOnce({
204
+ status: 200,
205
+ data: firmwareBinary,
206
+ });
207
+
208
+ await expect(
209
+ httpRequest('https://example.com/firmware.bin', 'binary', {
210
+ maxRetries: 1,
211
+ retryDelayMs: 0,
212
+ })
213
+ ).resolves.toBe(firmwareBinary);
214
+
215
+ expect(requestSpy).toHaveBeenCalledTimes(2);
216
+ }
217
+ );
218
+
219
+ it('does not retry a canceled request', async () => {
220
+ const canceledError = Object.assign(new Error('canceled'), {
221
+ __CANCEL__: true,
222
+ code: 'ERR_CANCELED',
223
+ isAxiosError: true,
224
+ });
225
+ const requestSpy = jest.spyOn(axios, 'request').mockRejectedValue(canceledError);
226
+
227
+ await expect(
228
+ httpRequest('https://example.com/firmware.bin', 'binary', {
229
+ maxRetries: 2,
230
+ retryDelayMs: 0,
231
+ })
232
+ ).rejects.toBe(canceledError);
233
+
234
+ expect(requestSpy).toHaveBeenCalledTimes(1);
235
+ });
236
+
237
+ it('enforces an overall deadline independently from per-attempt phase deadlines', async () => {
238
+ jest.useFakeTimers({ doNotFake: ['performance'] });
239
+ const requestSpy = jest.spyOn(axios, 'request').mockImplementation(
240
+ () =>
241
+ new Promise(() => {
242
+ // Keep the request pending until the overall deadline aborts it.
243
+ })
244
+ );
245
+ const requestPromise = httpRequest('https://example.com/firmware.bin', 'binary', {
246
+ connectTimeoutMs: 5_000,
247
+ readTimeoutMs: 5_000,
248
+ overallTimeoutMs: 2_500,
249
+ maxRetries: 2,
250
+ retryDelayMs: 0,
251
+ });
252
+ const rejection = expect(requestPromise).rejects.toMatchObject({
253
+ name: 'HttpRequestOverallTimeoutError',
254
+ message: expect.stringContaining('2500ms'),
255
+ });
256
+
257
+ jest.advanceTimersByTime(2_500);
258
+
259
+ await rejection;
260
+ expect(requestSpy).toHaveBeenCalledTimes(1);
261
+ expect(requestSpy.mock.calls[0][0].signal?.aborted).toBe(true);
262
+ });
263
+
264
+ it('includes retry backoff in the overall deadline', async () => {
265
+ jest.useFakeTimers({ doNotFake: ['performance'] });
266
+ const networkError = Object.assign(new Error('temporarily unreachable'), {
267
+ code: 'ECONNRESET',
268
+ isAxiosError: true,
269
+ });
270
+ const requestSpy = jest.spyOn(axios, 'request').mockRejectedValue(networkError);
271
+ const requestPromise = httpRequest('https://example.com/firmware.bin', 'binary', {
272
+ overallTimeoutMs: 2_500,
273
+ maxRetries: 2,
274
+ retryDelayMs: 5_000,
275
+ });
276
+ const rejection = expect(requestPromise).rejects.toMatchObject({
277
+ name: 'HttpRequestOverallTimeoutError',
278
+ });
279
+
280
+ await Promise.resolve();
281
+ await Promise.resolve();
282
+ await Promise.resolve();
283
+ await Promise.resolve();
284
+ expect(requestSpy).toHaveBeenCalledTimes(1);
285
+ expect(jest.getTimerCount()).toBe(2);
286
+ jest.advanceTimersByTime(2_500);
287
+
288
+ await rejection;
289
+ expect(requestSpy).toHaveBeenCalledTimes(1);
290
+ expect(jest.getTimerCount()).toBe(0);
291
+ });
292
+
293
+ it('does not start another attempt after an absolute deadline clock jump', async () => {
294
+ jest
295
+ .spyOn(Date, 'now')
296
+ .mockReturnValueOnce(1_000)
297
+ .mockReturnValueOnce(1_000)
298
+ .mockReturnValue(4_000);
299
+ const networkError = Object.assign(new Error('temporarily unreachable'), {
300
+ code: 'ECONNRESET',
301
+ isAxiosError: true,
302
+ });
303
+ const requestSpy = jest.spyOn(axios, 'request').mockRejectedValue(networkError);
304
+
305
+ await expect(
306
+ httpRequest('https://example.com/firmware.bin', 'binary', {
307
+ overallTimeoutMs: 2_500,
308
+ maxRetries: 1,
309
+ retryDelayMs: 0,
310
+ })
311
+ ).rejects.toMatchObject({
312
+ name: 'HttpRequestOverallTimeoutError',
313
+ message: expect.stringContaining('2500ms'),
314
+ });
315
+
316
+ expect(requestSpy).toHaveBeenCalledTimes(1);
317
+ });
318
+
319
+ it.each([500, 502, 503, 504])(
320
+ 'retries transient HTTP %s and returns the next response',
321
+ async status => {
322
+ const serverError = Object.assign(new Error('unavailable'), {
323
+ isAxiosError: true,
324
+ response: {
325
+ status,
326
+ },
327
+ });
328
+ const firmwareBinary = new ArrayBuffer(4);
329
+ const requestSpy = jest
330
+ .spyOn(axios, 'request')
331
+ .mockRejectedValueOnce(serverError)
332
+ .mockResolvedValueOnce({
333
+ status: 200,
334
+ data: firmwareBinary,
335
+ });
336
+
337
+ await expect(
338
+ httpRequest('https://example.com/firmware.bin', 'binary', {
339
+ maxRetries: 1,
340
+ retryDelayMs: 0,
341
+ })
342
+ ).resolves.toBe(firmwareBinary);
343
+
344
+ expect(requestSpy).toHaveBeenCalledTimes(2);
345
+ }
346
+ );
347
+
348
+ it.each([404, 501, 505])('does not retry HTTP %s', async status => {
349
+ const responseError = Object.assign(new Error('request failed'), {
350
+ isAxiosError: true,
351
+ response: {
352
+ status,
353
+ },
354
+ });
355
+ const requestSpy = jest.spyOn(axios, 'request').mockRejectedValue(responseError);
356
+
357
+ await expect(
358
+ httpRequest('https://example.com/firmware.bin', 'binary', {
359
+ maxRetries: 2,
360
+ retryDelayMs: 0,
361
+ })
362
+ ).rejects.toBe(responseError);
363
+
364
+ expect(requestSpy).toHaveBeenCalledTimes(1);
365
+ });
366
+
367
+ it.each(['CERT_HAS_EXPIRED', 'DEPTH_ZERO_SELF_SIGNED_CERT', 'ERR_TLS_CERT_ALTNAME_INVALID'])(
368
+ 'does not retry TLS/certificate error %s',
369
+ async code => {
370
+ const certificateError = Object.assign(new Error('certificate rejected'), {
371
+ code,
372
+ isAxiosError: true,
373
+ });
374
+ const requestSpy = jest.spyOn(axios, 'request').mockRejectedValue(certificateError);
375
+
376
+ await expect(
377
+ httpRequest('https://example.com/firmware.bin', 'binary', {
378
+ maxRetries: 2,
379
+ retryDelayMs: 0,
380
+ })
381
+ ).rejects.toBe(certificateError);
382
+
383
+ expect(requestSpy).toHaveBeenCalledTimes(1);
384
+ }
385
+ );
386
+ });
@@ -0,0 +1,43 @@
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ import { initCore } from '../src/core';
4
+ import { IFRAME } from '../src/events';
5
+
6
+ jest.mock('../src/data/config', () => ({
7
+ getSDKVersion: jest.fn(() => '1.0.0-test'),
8
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0-test/',
9
+ }));
10
+
11
+ jest.mock('../src/data-manager/TransportManager', () => ({
12
+ __esModule: true,
13
+ default: {
14
+ configure: jest.fn().mockResolvedValue(undefined),
15
+ getTransport: jest.fn(() => undefined),
16
+ },
17
+ }));
18
+
19
+ describe('openWalletSession error response', () => {
20
+ test('returns a structured invalid-parameter response for an incomplete resume request', async () => {
21
+ const core = initCore();
22
+
23
+ const response = await core.handleMessage({
24
+ id: 1,
25
+ event: IFRAME.CALL,
26
+ type: IFRAME.CALL,
27
+ payload: {
28
+ method: 'openWalletSession',
29
+ connectId: 'connect-id',
30
+ mode: 'resume-hidden',
31
+ },
32
+ } as never);
33
+
34
+ expect(response).toMatchObject({
35
+ success: false,
36
+ payload: {
37
+ error: 'Missing required parameter: deviceId',
38
+ code: HardwareErrorCode.CallMethodInvalidParameter,
39
+ },
40
+ });
41
+ await core.dispose();
42
+ });
43
+ });