@onekeyfe/hd-core 1.2.0-alpha.51 → 1.2.0-alpha.53

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 (247) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +78 -0
  2. package/__tests__/DeviceCommands.test.ts +45 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
  5. package/__tests__/connectSettings.test.ts +5 -47
  6. package/__tests__/device-lifecycle-events.test.ts +105 -3
  7. package/__tests__/deviceUploadNft.test.ts +293 -0
  8. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  9. package/__tests__/get-device-state.test.ts +132 -12
  10. package/__tests__/homescreen.test.ts +41 -1
  11. package/__tests__/logBlockEvent.test.ts +1 -1
  12. package/__tests__/method-protocol-support.test.ts +19 -0
  13. package/__tests__/open-wallet-session.test.ts +446 -33
  14. package/__tests__/pro2Nft.test.ts +108 -0
  15. package/__tests__/protocol-binding.test.ts +89 -0
  16. package/__tests__/protocol-v2-resources.test.ts +284 -0
  17. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  18. package/__tests__/protocol-v2-unlock-policy.test.ts +201 -35
  19. package/__tests__/protocol-v2.test.ts +1349 -1005
  20. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  21. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  22. package/__tests__/search-devices.test.ts +12 -3
  23. package/__tests__/uiProgressThrottle.test.ts +74 -0
  24. package/__tests__/uiPromiseRegistry.test.ts +86 -0
  25. package/dist/api/BaseMethod.d.ts +3 -1
  26. package/dist/api/BaseMethod.d.ts.map +1 -1
  27. package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
  28. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  29. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  30. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  31. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  32. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  33. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  34. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  35. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  36. package/dist/api/FirmwareUpdateV4.d.ts +11 -25
  37. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  38. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  39. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  40. package/dist/api/SearchDevices.d.ts.map +1 -1
  41. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  42. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  43. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  44. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  45. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  46. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -9
  47. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  48. package/dist/api/firmware/getBinary.d.ts +0 -7
  49. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  50. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  51. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  52. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  53. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  54. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  55. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  56. package/dist/api/index.d.ts +2 -0
  57. package/dist/api/index.d.ts.map +1 -1
  58. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  59. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  60. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  61. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  62. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  63. package/dist/core/deviceEventRegistration.d.ts +2 -0
  64. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  65. package/dist/core/index.d.ts.map +1 -1
  66. package/dist/core/uiPromiseRegistry.d.ts +4 -0
  67. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  68. package/dist/data-manager/DataManager.d.ts +4 -2
  69. package/dist/data-manager/DataManager.d.ts.map +1 -1
  70. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  71. package/dist/device/Device.d.ts +29 -6
  72. package/dist/device/Device.d.ts.map +1 -1
  73. package/dist/device/DeviceCommands.d.ts +4 -4
  74. package/dist/device/DeviceCommands.d.ts.map +1 -1
  75. package/dist/device/DevicePool.d.ts.map +1 -1
  76. package/dist/events/device.d.ts +4 -0
  77. package/dist/events/device.d.ts.map +1 -1
  78. package/dist/events/logBlockEvent.d.ts.map +1 -1
  79. package/dist/events/ui-promise.d.ts +4 -2
  80. package/dist/events/ui-promise.d.ts.map +1 -1
  81. package/dist/events/ui-request.d.ts +31 -2
  82. package/dist/events/ui-request.d.ts.map +1 -1
  83. package/dist/events/ui-response.d.ts +10 -2
  84. package/dist/events/ui-response.d.ts.map +1 -1
  85. package/dist/index.d.ts +214 -231
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +2559 -3473
  88. package/dist/inject.d.ts +6 -1
  89. package/dist/inject.d.ts.map +1 -1
  90. package/dist/lowLevelInject.d.ts.map +1 -1
  91. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  92. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  93. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  94. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  95. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  96. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  97. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  98. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  99. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  100. package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
  101. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  102. package/dist/topLevelInject.d.ts.map +1 -1
  103. package/dist/types/api/checkAllFirmwareRelease.d.ts +2 -4
  104. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  105. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  106. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  107. package/dist/types/api/deviceUnlock.d.ts +5 -1
  108. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  109. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  110. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  111. package/dist/types/api/export.d.ts +2 -4
  112. package/dist/types/api/export.d.ts.map +1 -1
  113. package/dist/types/api/firmwareUpdate.d.ts +2 -70
  114. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  115. package/dist/types/api/index.d.ts +7 -10
  116. package/dist/types/api/index.d.ts.map +1 -1
  117. package/dist/types/api/protocolV2.d.ts +3 -0
  118. package/dist/types/api/protocolV2.d.ts.map +1 -1
  119. package/dist/types/device.d.ts.map +1 -1
  120. package/dist/types/params.d.ts +1 -0
  121. package/dist/types/params.d.ts.map +1 -1
  122. package/dist/types/settings.d.ts +22 -13
  123. package/dist/types/settings.d.ts.map +1 -1
  124. package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
  125. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  126. package/dist/utils/homescreen.d.ts +4 -0
  127. package/dist/utils/homescreen.d.ts.map +1 -1
  128. package/dist/utils/index.d.ts +1 -1
  129. package/dist/utils/index.d.ts.map +1 -1
  130. package/dist/utils/patch.d.ts +1 -1
  131. package/dist/utils/patch.d.ts.map +1 -1
  132. package/dist/utils/pro2Nft.d.ts +31 -0
  133. package/dist/utils/pro2Nft.d.ts.map +1 -0
  134. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  135. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  136. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  137. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  138. package/package.json +4 -4
  139. package/src/api/BaseMethod.ts +8 -10
  140. package/src/api/CheckAllFirmwareRelease.ts +54 -57
  141. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  142. package/src/api/FirmwareUpdate.ts +7 -15
  143. package/src/api/FirmwareUpdateV2.ts +126 -313
  144. package/src/api/FirmwareUpdateV3.ts +63 -265
  145. package/src/api/FirmwareUpdateV4.ts +566 -965
  146. package/src/api/GetPassphraseState.ts +7 -1
  147. package/src/api/OpenWalletSession.ts +54 -17
  148. package/src/api/SearchDevices.ts +3 -1
  149. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
  150. package/src/api/device/DeviceUnlock.ts +8 -3
  151. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  152. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +16 -83
  153. package/src/api/firmware/updateBootloader.ts +4 -19
  154. package/src/api/firmware/uploadFirmware.ts +39 -144
  155. package/src/api/helpers/protocolV2FileWrite.ts +11 -4
  156. package/src/api/index.ts +2 -0
  157. package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
  158. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  159. package/src/api/protocol-v2/helpers.ts +1 -0
  160. package/src/core/deviceEventRegistration.ts +4 -0
  161. package/src/core/index.ts +167 -83
  162. package/src/core/uiPromiseRegistry.ts +41 -0
  163. package/src/data/messages/messages-protocol-v2.json +25 -0
  164. package/src/data-manager/DataManager.ts +67 -38
  165. package/src/data-manager/connectSettings.ts +0 -11
  166. package/src/device/Device.ts +258 -42
  167. package/src/device/DeviceCommands.ts +7 -1
  168. package/src/device/DevicePool.ts +7 -2
  169. package/src/events/device.ts +4 -0
  170. package/src/events/logBlockEvent.ts +1 -0
  171. package/src/events/ui-promise.ts +4 -2
  172. package/src/events/ui-request.ts +36 -2
  173. package/src/events/ui-response.ts +12 -2
  174. package/src/index.ts +0 -5
  175. package/src/inject.ts +60 -24
  176. package/src/lowLevelInject.ts +7 -8
  177. package/src/protocols/protocol-v2/features.ts +10 -3
  178. package/src/protocols/protocol-v2/resources.ts +359 -0
  179. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  180. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  181. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +118 -0
  182. package/src/protocols/protocol-v2/walletSession.ts +180 -30
  183. package/src/topLevelInject.ts +7 -8
  184. package/src/types/api/checkAllFirmwareRelease.ts +2 -4
  185. package/src/types/api/detectDeviceConnectProtocol.ts +7 -0
  186. package/src/types/api/deviceUnlock.ts +12 -1
  187. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  188. package/src/types/api/export.ts +1 -18
  189. package/src/types/api/firmwareUpdate.ts +3 -76
  190. package/src/types/api/index.ts +13 -14
  191. package/src/types/api/protocolV2.ts +13 -0
  192. package/src/types/device.ts +3 -0
  193. package/src/types/params.ts +7 -1
  194. package/src/types/settings.ts +42 -13
  195. package/src/utils/deviceFeaturesUtils.ts +8 -0
  196. package/src/utils/homescreen.ts +32 -6
  197. package/src/utils/index.ts +6 -1
  198. package/src/utils/pro2Nft.ts +142 -0
  199. package/src/utils/pro2Wallpaper.ts +35 -8
  200. package/src/utils/uiProgressThrottle.ts +63 -0
  201. package/__tests__/device-initialize-timeout.test.ts +0 -56
  202. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  203. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  204. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  205. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  206. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
  207. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  208. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  209. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  210. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  211. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  212. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  213. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  214. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  215. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  216. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  217. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  218. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  219. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  220. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  221. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  222. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  223. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  224. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  225. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  226. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  227. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  228. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  229. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  230. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  231. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  232. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  233. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  234. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  235. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  236. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  237. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  238. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  239. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  240. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
  241. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  242. package/src/api/firmware/progressThrottle.ts +0 -44
  243. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  244. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  245. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  246. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  247. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -0,0 +1,108 @@
1
+ import { blake2s } from '@noble/hashes/blake2s';
2
+ import { bytesToHex } from '@noble/hashes/utils';
3
+
4
+ import {
5
+ PRO2_NFT_IMAGE_HEIGHT,
6
+ PRO2_NFT_IMAGE_WIDTH,
7
+ PRO2_NFT_THUMBNAIL_HEIGHT,
8
+ PRO2_NFT_THUMBNAIL_WIDTH,
9
+ buildPro2NftBundle,
10
+ } from '../src/utils/pro2Nft';
11
+
12
+ const rgba = (width: number, height: number, alpha = 0xff) => {
13
+ const data = new Uint8Array(width * height * 4);
14
+ for (let index = 3; index < data.length; index += 4) data[index] = alpha;
15
+ return data;
16
+ };
17
+
18
+ describe('buildPro2NftBundle', () => {
19
+ test('builds the exact Pro2 RGB565 triplet and basename', () => {
20
+ const bundle = buildPro2NftBundle({
21
+ image: {
22
+ width: PRO2_NFT_IMAGE_WIDTH,
23
+ height: PRO2_NFT_IMAGE_HEIGHT,
24
+ rgba: rgba(PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT),
25
+ },
26
+ thumbnail: {
27
+ width: PRO2_NFT_THUMBNAIL_WIDTH,
28
+ height: PRO2_NFT_THUMBNAIL_HEIGHT,
29
+ rgba: rgba(PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT),
30
+ },
31
+ title: 'CryptoPunk #3100',
32
+ subtitle: 'CryptoPunks',
33
+ timestampMs: 1_760_000_000_000,
34
+ });
35
+
36
+ expect(bundle.image.byteLength).toBe(583_212);
37
+ expect(bundle.thumbnail.byteLength).toBe(138_876);
38
+ expect(Array.from(bundle.image.slice(0, 12))).toEqual([
39
+ 0x19, 0x12, 0, 0, 28, 2, 28, 2, 56, 4, 0, 0,
40
+ ]);
41
+ expect(Array.from(bundle.thumbnail.slice(0, 12))).toEqual([
42
+ 0x19, 0x12, 0, 0, 7, 1, 7, 1, 16, 2, 0, 0,
43
+ ]);
44
+ expect(new TextDecoder().decode(bundle.metadata)).toBe(
45
+ '{"title":"CryptoPunk #3100","subtitle":"CryptoPunks"}'
46
+ );
47
+ expect(bytesToHex(blake2s(bundle.image))).toBe(
48
+ 'af4b5f8fe216cfd0ef4a9162a42b5c66a73466f2491066f66f756c4e28cc869e'
49
+ );
50
+ expect(bytesToHex(blake2s(bundle.thumbnail))).toBe(
51
+ '44591afae1163cc14651d77644501c6a110f96506889b825cab0351ff4de857d'
52
+ );
53
+ expect(bytesToHex(blake2s(bundle.metadata))).toBe(
54
+ 'e30d4dc2cddd441c9f1438c783e05a623ccf76f2f6219d4281769dda1b3635b5'
55
+ );
56
+ expect(bundle.basename).toBe('nft-af4b5f8f-1760000000000');
57
+ });
58
+
59
+ test('composites transparent pixels onto black without an alpha plane', () => {
60
+ const image = rgba(PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT);
61
+ image.set([255, 255, 255, 0], 0);
62
+ const bundle = buildPro2NftBundle({
63
+ image: { width: PRO2_NFT_IMAGE_WIDTH, height: PRO2_NFT_IMAGE_HEIGHT, rgba: image },
64
+ thumbnail: {
65
+ width: PRO2_NFT_THUMBNAIL_WIDTH,
66
+ height: PRO2_NFT_THUMBNAIL_HEIGHT,
67
+ rgba: rgba(PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT, 0),
68
+ },
69
+ title: 'NFT',
70
+ subtitle: '',
71
+ timestampMs: 1,
72
+ });
73
+
74
+ expect(bundle.image[1]).toBe(0x12);
75
+ expect(Array.from(bundle.image.slice(12, 14))).toEqual([0, 0]);
76
+ expect(bundle.thumbnail.byteLength).toBe(138_876);
77
+ });
78
+
79
+ test('rejects invalid dimensions and metadata byte lengths', () => {
80
+ const validThumbnail = {
81
+ width: PRO2_NFT_THUMBNAIL_WIDTH,
82
+ height: PRO2_NFT_THUMBNAIL_HEIGHT,
83
+ rgba: rgba(PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT),
84
+ };
85
+ expect(() =>
86
+ buildPro2NftBundle({
87
+ image: { width: 1, height: 1, rgba: new Uint8Array(4) },
88
+ thumbnail: validThumbnail,
89
+ title: 'NFT',
90
+ subtitle: '',
91
+ timestampMs: 1,
92
+ })
93
+ ).toThrow('540x540');
94
+ expect(() =>
95
+ buildPro2NftBundle({
96
+ image: {
97
+ width: PRO2_NFT_IMAGE_WIDTH,
98
+ height: PRO2_NFT_IMAGE_HEIGHT,
99
+ rgba: rgba(PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT),
100
+ },
101
+ thumbnail: validThumbnail,
102
+ title: 'a'.repeat(64),
103
+ subtitle: '',
104
+ timestampMs: 1,
105
+ })
106
+ ).toThrow('1 to 63');
107
+ });
108
+ });
@@ -0,0 +1,89 @@
1
+ import { createCoreApi, createProtocolAwareCall } from '../src/inject';
2
+
3
+ import type { CoreApi } from '../src/types/api';
4
+
5
+ describe('SDK protocol binding', () => {
6
+ test('exposes a minimal API that returns the actively detected protocol', async () => {
7
+ const response = { success: true as const, payload: 'V2' as const };
8
+ const rawCall = jest.fn().mockResolvedValue(response);
9
+ const api = createCoreApi(rawCall as CoreApi['call']);
10
+
11
+ await expect(api.detectDeviceConnectProtocol('USB_DEVICE_A')).resolves.toBe(response);
12
+ expect(rawCall).toHaveBeenCalledWith({
13
+ connectId: 'USB_DEVICE_A',
14
+ forceProtocolDetection: true,
15
+ method: 'detectDeviceConnectProtocol',
16
+ });
17
+ });
18
+
19
+ test('injects a strict protocol into every later call and isolates devices', async () => {
20
+ const rawCall = jest.fn(params => Promise.resolve({ success: true, payload: params }));
21
+ const protocolAwareCall = createProtocolAwareCall(rawCall as CoreApi['call']);
22
+ const api = createCoreApi(protocolAwareCall.call);
23
+
24
+ protocolAwareCall.setDeviceConnectProtocol('USB_DEVICE_A', 'V2');
25
+ protocolAwareCall.setDeviceConnectProtocol('ble-device-b', 'V1');
26
+
27
+ await api.deviceWipe('usb_device_a');
28
+ await api.deviceSupportFeatures('BLE-DEVICE-B');
29
+ await api.getFeatures('USB_DEVICE_A', { connectProtocol: 'V1' });
30
+ await api.getDeviceState('USB_DEVICE_A', { forceProtocolDetection: true });
31
+ await protocolAwareCall.call({
32
+ connectId: 'USB_DEVICE_A',
33
+ method: 'deviceUpdateReboot',
34
+ });
35
+
36
+ expect(rawCall).toHaveBeenNthCalledWith(1, {
37
+ connectId: 'usb_device_a',
38
+ method: 'deviceWipe',
39
+ connectProtocol: 'V2',
40
+ });
41
+ expect(rawCall).toHaveBeenNthCalledWith(2, {
42
+ connectId: 'BLE-DEVICE-B',
43
+ method: 'deviceSupportFeatures',
44
+ connectProtocol: 'V1',
45
+ });
46
+ expect(rawCall).toHaveBeenNthCalledWith(3, {
47
+ connectId: 'USB_DEVICE_A',
48
+ method: 'getFeatures',
49
+ connectProtocol: 'V1',
50
+ });
51
+ expect(rawCall).toHaveBeenNthCalledWith(4, {
52
+ connectId: 'USB_DEVICE_A',
53
+ method: 'getDeviceState',
54
+ forceProtocolDetection: true,
55
+ });
56
+ expect(rawCall).toHaveBeenNthCalledWith(5, {
57
+ connectId: 'USB_DEVICE_A',
58
+ method: 'deviceUpdateReboot',
59
+ connectProtocol: 'V2',
60
+ });
61
+ });
62
+
63
+ test('keeps bindings local to one SDK instance and supports clearing them', async () => {
64
+ const firstRawCall = jest.fn(() => Promise.resolve({ success: true, payload: {} }));
65
+ const secondRawCall = jest.fn(() => Promise.resolve({ success: true, payload: {} }));
66
+ const first = createProtocolAwareCall(firstRawCall as CoreApi['call']);
67
+ const second = createProtocolAwareCall(secondRawCall as CoreApi['call']);
68
+
69
+ first.setDeviceConnectProtocol('same-device', 'V2');
70
+ await first.call({ connectId: 'same-device', method: 'deviceWipe' });
71
+ await second.call({ connectId: 'same-device', method: 'deviceWipe' });
72
+ first.setDeviceConnectProtocol('same-device', undefined);
73
+ await first.call({ connectId: 'same-device', method: 'deviceWipe' });
74
+
75
+ expect(firstRawCall).toHaveBeenNthCalledWith(1, {
76
+ connectId: 'same-device',
77
+ method: 'deviceWipe',
78
+ connectProtocol: 'V2',
79
+ });
80
+ expect(secondRawCall).toHaveBeenCalledWith({
81
+ connectId: 'same-device',
82
+ method: 'deviceWipe',
83
+ });
84
+ expect(firstRawCall).toHaveBeenNthCalledWith(2, {
85
+ connectId: 'same-device',
86
+ method: 'deviceWipe',
87
+ });
88
+ });
89
+ });
@@ -0,0 +1,284 @@
1
+ import axios from 'axios';
2
+ import { sha256 } from '@noble/hashes/sha256';
3
+
4
+ import { DataManager } from '../src/data-manager';
5
+ import {
6
+ PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
7
+ PROTOCOL_V2_RESOURCE_TYPES,
8
+ buildProtocolV2ResourceUpdatePlan,
9
+ isProtocolV2ResourceFileValid,
10
+ parseProtocolV2Resources,
11
+ readProtocolV2ResourceInventory,
12
+ } from '../src/protocols/protocol-v2/resources';
13
+
14
+ import type {
15
+ ConnectSettings,
16
+ IProtocolV2BootResources,
17
+ IProtocolV2Resource,
18
+ RemoteConfigResponse,
19
+ } from '../src/types';
20
+
21
+ jest.mock('axios');
22
+ jest.mock('../src/data/config', () => ({
23
+ getSDKVersion: jest.fn(() => '1.0.0-test'),
24
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0-test/',
25
+ }));
26
+
27
+ const bytesToHex = (bytes: Uint8Array) =>
28
+ Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
29
+
30
+ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
31
+
32
+ const createResourceHeader = (headerHash: string) => {
33
+ const header = new Uint8Array(PROTOCOL_V2_OKPP_HEADER_SIZE);
34
+ const view = new DataView(header.buffer);
35
+ 'OKPP'.split('').forEach((char, index) => {
36
+ header[index] = char.charCodeAt(0);
37
+ });
38
+ 'RESC'.split('').forEach((char, index) => {
39
+ header[0x08 + index] = char.charCodeAt(0);
40
+ });
41
+ view.setUint32(0x0c, header.byteLength, true);
42
+ for (let index = 0; index < headerHash.length / 2; index++) {
43
+ header[0x240 + index] = Number.parseInt(headerHash.slice(index * 2, index * 2 + 2), 16);
44
+ }
45
+ return header;
46
+ };
47
+
48
+ const resources: IProtocolV2Resource[] = PROTOCOL_V2_RESOURCE_TYPES.map((type, index) => ({
49
+ type,
50
+ url: `https://example.com/${type}.okpkg`,
51
+ size: index + 100,
52
+ fileHash: index.toString(16).padStart(64, '0'),
53
+ headerHash: index.toString(16).padStart(128, '0'),
54
+ }));
55
+
56
+ const bootResources: IProtocolV2BootResources = {
57
+ required: false,
58
+ target: 'CRATE',
59
+ url: 'https://example.com/boot-resources.crate.okpkg',
60
+ size: 1234,
61
+ fileHash: 'ab'.repeat(32),
62
+ payloadHash: 'cd'.repeat(64),
63
+ headerHash: 'ef'.repeat(64),
64
+ };
65
+
66
+ const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
67
+ ({
68
+ env: 'node',
69
+ fetchConfig: true,
70
+ preRelease: true,
71
+ configFetcher,
72
+ } as ConnectSettings);
73
+
74
+ const createRemoteConfig = (): RemoteConfigResponse =>
75
+ ({
76
+ classic: { firmware: [], ble: [] },
77
+ classic1s: { firmware: [], ble: [] },
78
+ classicpure: { firmware: [], ble: [] },
79
+ mini: { firmware: [], ble: [] },
80
+ touch: { firmware: [], ble: [] },
81
+ pro: { firmware: [], ble: [] },
82
+ pro2: { firmware: [], ble: [], resources: { stable: resources, boot: bootResources } },
83
+ bridge: {},
84
+ } as unknown as RemoteConfigResponse);
85
+
86
+ describe('Pro2 resource configuration', () => {
87
+ beforeEach(() => {
88
+ jest.clearAllMocks();
89
+ DataManager.lastCheckTimestamp = 0;
90
+ });
91
+
92
+ test('accepts exactly six resources and normalizes their deterministic order', () => {
93
+ const parsed = parseProtocolV2Resources({
94
+ stable: [...resources].reverse(),
95
+ boot: bootResources,
96
+ });
97
+
98
+ expect(parsed?.stable.map(item => item.type)).toEqual(PROTOCOL_V2_RESOURCE_TYPES);
99
+ expect(parsed?.boot).toEqual(bootResources);
100
+ expect(PROTOCOL_V2_RESOURCE_DEVICE_PATHS.translations).toBe(
101
+ 'vol0:/bundles/translations/translations.okpkg'
102
+ );
103
+ });
104
+
105
+ test('rejects incomplete, duplicate, or malformed stable sets', () => {
106
+ expect(() => parseProtocolV2Resources({ stable: resources.slice(1) })).toThrow(
107
+ 'six unique resource types'
108
+ );
109
+ expect(() =>
110
+ parseProtocolV2Resources({ stable: [...resources.slice(0, 5), resources[0]] })
111
+ ).toThrow('six unique resource types');
112
+ expect(() =>
113
+ parseProtocolV2Resources({
114
+ stable: resources.map((resource, index) =>
115
+ index === 0 ? { ...resource, headerHash: 'bad' } : resource
116
+ ),
117
+ })
118
+ ).toThrow('headerHash');
119
+ });
120
+
121
+ test('requires boot resources to remain optional and use a CRATE package', () => {
122
+ expect(() =>
123
+ parseProtocolV2Resources({
124
+ stable: resources,
125
+ boot: { ...bootResources, required: true },
126
+ })
127
+ ).toThrow('required flag');
128
+ expect(() =>
129
+ parseProtocolV2Resources({
130
+ stable: resources,
131
+ boot: { ...bootResources, target: 'RESC' },
132
+ })
133
+ ).toThrow('expected CRATE');
134
+ });
135
+
136
+ test('downloads nothing when all resource identities match', () => {
137
+ const inventory = resources.map(({ type, size, headerHash }) => ({ type, size, headerHash }));
138
+
139
+ expect(
140
+ buildProtocolV2ResourceUpdatePlan({ resources, inventory, mode: 'application' })
141
+ ).toEqual({ status: 'valid', resources: [] });
142
+ });
143
+
144
+ test('builds inventory from existing filesystem calls without ResourceInventoryGet', async () => {
145
+ const installedResources = resources.map((resource, index) => ({
146
+ ...resource,
147
+ size: PROTOCOL_V2_OKPP_HEADER_SIZE + index + 1,
148
+ }));
149
+ const resourceByPath = new Map(
150
+ installedResources.map(resource => [
151
+ PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
152
+ resource,
153
+ ])
154
+ );
155
+ const typedCall = jest.fn(
156
+ (requestType: string, _responseType: string, payload: Record<string, any>) => {
157
+ if (requestType === 'ResourceInventoryGet') {
158
+ throw new Error('ResourceInventoryGet is unavailable on released firmware');
159
+ }
160
+ if (requestType === 'FilesystemPathInfoQuery') {
161
+ const resource = resourceByPath.get(payload.path);
162
+ return {
163
+ message: {
164
+ exist: Boolean(resource),
165
+ directory: false,
166
+ size: resource?.size ?? 0,
167
+ },
168
+ };
169
+ }
170
+ if (requestType === 'FilesystemFileRead') {
171
+ const resource = resourceByPath.get(payload.file.path);
172
+ if (!resource) throw new Error('missing resource');
173
+ const header = createResourceHeader(resource.headerHash);
174
+ const offset = Number(payload.file.offset);
175
+ const chunkLength = Number(payload.chunk_len);
176
+ return {
177
+ message: {
178
+ data: header.slice(offset, offset + chunkLength),
179
+ },
180
+ };
181
+ }
182
+ throw new Error(`Unexpected request: ${requestType}`);
183
+ }
184
+ );
185
+
186
+ await expect(
187
+ readProtocolV2ResourceInventory({
188
+ commands: { typedCall },
189
+ resources: installedResources,
190
+ chunkSize: 4000,
191
+ })
192
+ ).resolves.toEqual(
193
+ installedResources.map(({ type, size, headerHash }) => ({ type, size, headerHash }))
194
+ );
195
+ expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
196
+ expect(typedCall.mock.calls.filter(call => call[0] === 'FilesystemPathInfoQuery')).toHaveLength(
197
+ 6
198
+ );
199
+ expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
200
+ });
201
+
202
+ test('selects only the changed or missing resource in application mode', () => {
203
+ const inventory = resources
204
+ .filter(resource => resource.type !== 'noto')
205
+ .map(({ type, size, headerHash }) => ({
206
+ type,
207
+ size: type === 'images' ? size + 1 : size,
208
+ headerHash,
209
+ }));
210
+
211
+ const result = buildProtocolV2ResourceUpdatePlan({
212
+ resources,
213
+ inventory,
214
+ mode: 'application',
215
+ });
216
+
217
+ expect(result.status).toBe('outdated');
218
+ expect(result.resources.map(resource => resource.type)).toEqual(['images', 'noto']);
219
+ });
220
+
221
+ test('reports unknown without an application inventory and selects all in recovery mode', () => {
222
+ expect(buildProtocolV2ResourceUpdatePlan({ resources, mode: 'application' })).toEqual({
223
+ status: 'unknown',
224
+ resources: [],
225
+ });
226
+ expect(
227
+ buildProtocolV2ResourceUpdatePlan({ resources, mode: 'bootloader-recovery' }).resources
228
+ ).toHaveLength(6);
229
+ });
230
+
231
+ test('uses a filesystem inventory for incremental recovery mode updates', () => {
232
+ const inventory = resources.slice(1).map(({ type, size, headerHash }) => ({
233
+ type,
234
+ size,
235
+ headerHash,
236
+ }));
237
+
238
+ expect(
239
+ buildProtocolV2ResourceUpdatePlan({
240
+ resources,
241
+ inventory,
242
+ mode: 'bootloader-recovery',
243
+ }).resources.map(resource => resource.type)
244
+ ).toEqual(['images']);
245
+ });
246
+
247
+ test('verifies both full file size and SHA-256 before transfer', () => {
248
+ const bytes = new Uint8Array([1, 2, 3]);
249
+ const binary = bytes.buffer;
250
+ const identity = { size: bytes.byteLength, fileHash: bytesToHex(sha256(bytes)) };
251
+
252
+ expect(isProtocolV2ResourceFileValid(binary, identity)).toBe(true);
253
+ expect(isProtocolV2ResourceFileValid(binary, { ...identity, size: 4 })).toBe(false);
254
+ expect(isProtocolV2ResourceFileValid(binary, { ...identity, fileHash: '0'.repeat(64) })).toBe(
255
+ false
256
+ );
257
+ });
258
+
259
+ test('applies a validated pre-release config and exposes the stable resource set', async () => {
260
+ const configFetcher = jest.fn().mockResolvedValue(createRemoteConfig());
261
+
262
+ await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
263
+
264
+ expect(configFetcher).toHaveBeenCalledWith(
265
+ expect.stringMatching(/^https:\/\/data\.onekey\.so\/pre-config\.json\?noCache=/)
266
+ );
267
+ expect(DataManager.getProtocolV2Resources()).toEqual(resources);
268
+ expect(DataManager.getProtocolV2BootResources()).toEqual(bootResources);
269
+ });
270
+
271
+ test('does not advance the cache timestamp when refresh fails', async () => {
272
+ jest.spyOn(axios, 'get').mockRejectedValue(new Error('offline'));
273
+ const settings = createSettings(jest.fn().mockResolvedValue(null));
274
+ DataManager.settings = settings;
275
+
276
+ await DataManager.checkAndReloadData();
277
+
278
+ expect(DataManager.lastCheckTimestamp).toBe(0);
279
+ await expect(DataManager.forceReloadData()).rejects.toThrow(
280
+ 'Unable to refresh the latest remote config'
281
+ );
282
+ expect(DataManager.lastCheckTimestamp).toBe(0);
283
+ });
284
+ });
@@ -0,0 +1,91 @@
1
+ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
+
3
+ import { DEVICE } from '../src/events';
4
+ import { Device } from '../src/device/Device';
5
+
6
+ jest.mock('../src/data/config', () => ({
7
+ getSDKVersion: jest.fn(() => '1.0.0-test'),
8
+ DEFAULT_DOMAIN: 'https://example.com/',
9
+ }));
10
+
11
+ describe('Protocol V2 UI interaction lifecycle', () => {
12
+ test('synthesizes finish metadata for a progress-only operation', () => {
13
+ const device = new Device({ id: 'connect-progress' } as any);
14
+ device.isProtocolV2 = jest.fn(() => true);
15
+ device.beginProtocolV2UiInteraction();
16
+
17
+ const metadata = device.finishProtocolV2UiInteraction('succeeded', {
18
+ ensureMetadata: true,
19
+ });
20
+
21
+ expect(metadata).toMatchObject({
22
+ phase: 'processing',
23
+ transition: 'finish',
24
+ outcome: 'succeeded',
25
+ protocol: 'V2',
26
+ });
27
+ });
28
+
29
+ test('keeps finish metadata after the Protocol V2 transport is released', () => {
30
+ const device = new Device({ id: 'connect-released' } as any);
31
+ const isProtocolV2 = jest.fn().mockReturnValueOnce(true).mockReturnValue(false);
32
+ device.isProtocolV2 = isProtocolV2;
33
+ device.beginProtocolV2UiInteraction();
34
+
35
+ const metadata = device.finishProtocolV2UiInteraction('succeeded', {
36
+ ensureMetadata: true,
37
+ });
38
+
39
+ expect(metadata).toMatchObject({
40
+ phase: 'processing',
41
+ transition: 'finish',
42
+ outcome: 'succeeded',
43
+ protocol: 'V2',
44
+ });
45
+ });
46
+
47
+ test('emits a matching PIN phase completion after DeviceSessionAskPin succeeds', async () => {
48
+ const device = new Device({ id: 'connect-1' } as any);
49
+ device.isProtocolV2 = jest.fn(() => true);
50
+ device.updateProtocolV2Status = jest.fn(() => undefined as any);
51
+ device.commands = {
52
+ typedCall: jest.fn().mockResolvedValue({
53
+ message: { unlocked: true },
54
+ }),
55
+ } as any;
56
+ device.beginProtocolV2UiInteraction();
57
+
58
+ const starts: unknown[] = [];
59
+ const completions: unknown[] = [];
60
+ device.on(DEVICE.PIN_ON_DEVICE, (...args) => starts.push(args));
61
+ device.on(DEVICE.PIN_ON_DEVICE_COMPLETE, (...args) => completions.push(args));
62
+
63
+ await device.unlockDevice(DeviceSessionPinType.Main, {
64
+ source: 'unlock-coordinator',
65
+ reason: 'device-locked',
66
+ deviceOnly: true,
67
+ });
68
+
69
+ const startMetadata = (starts[0] as unknown[])[2] as Record<string, unknown>;
70
+ const completionMetadata = (completions[0] as unknown[])[1] as Record<string, unknown>;
71
+ expect(startMetadata.interaction).toMatchObject({
72
+ phase: 'pin',
73
+ transition: 'start',
74
+ protocol: 'V2',
75
+ });
76
+ expect(completionMetadata).toMatchObject({
77
+ interactionId: (startMetadata.interaction as { interactionId: string }).interactionId,
78
+ phaseId: (startMetadata.interaction as { phaseId: string }).phaseId,
79
+ phase: 'pin',
80
+ transition: 'complete',
81
+ outcome: 'succeeded',
82
+ protocol: 'V2',
83
+ });
84
+ expect(completionMetadata.sequence).toBeGreaterThan(
85
+ (startMetadata.interaction as { sequence: number }).sequence
86
+ );
87
+ expect(completionMetadata.phaseId).toBe(
88
+ (startMetadata.interaction as { phaseId: string }).phaseId
89
+ );
90
+ });
91
+ });