@onekeyfe/hd-core 1.2.0-alpha.56 → 1.2.0-alpha.62

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 (262) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +0 -231
  2. package/__tests__/DeviceCommands.test.ts +0 -45
  3. package/__tests__/DeviceEventRegistration.test.ts +0 -6
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +3 -198
  5. package/__tests__/connectSettings.test.ts +47 -5
  6. package/__tests__/device-initialize-timeout.test.ts +56 -0
  7. package/__tests__/device-lifecycle-events.test.ts +3 -105
  8. package/__tests__/device-state-contract.test.ts +0 -1
  9. package/__tests__/device-state-mapper.test.ts +1 -1
  10. package/__tests__/deviceSettings.test.ts +1 -1
  11. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  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-preparation-error.test.ts +27 -0
  15. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
  16. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  17. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +294 -0
  18. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  19. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
  20. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  21. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
  22. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  23. package/__tests__/get-device-state.test.ts +12 -132
  24. package/__tests__/homescreen.test.ts +1 -41
  25. package/__tests__/kaspaSignTransaction.test.ts +26 -3
  26. package/__tests__/logBlockEvent.test.ts +1 -1
  27. package/__tests__/method-protocol-support.test.ts +0 -19
  28. package/__tests__/open-wallet-session.test.ts +37 -568
  29. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -201
  30. package/__tests__/protocol-v2.test.ts +1046 -1388
  31. package/__tests__/protocolV2FileWrite.test.ts +0 -39
  32. package/__tests__/protocolV2UiInteraction.test.ts +0 -63
  33. package/__tests__/public-pro2-api-boundary.test.ts +0 -1
  34. package/__tests__/search-devices.test.ts +3 -12
  35. package/dist/api/BaseMethod.d.ts +1 -3
  36. package/dist/api/BaseMethod.d.ts.map +1 -1
  37. package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
  38. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  39. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  40. package/dist/api/FirmwareUpdateV2.d.ts +11 -0
  41. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  42. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  43. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  44. package/dist/api/FirmwareUpdateV4.d.ts +25 -11
  45. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  46. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  47. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  48. package/dist/api/SearchDevices.d.ts.map +1 -1
  49. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  50. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  51. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  52. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  53. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  54. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  55. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  56. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  57. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  58. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  59. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  60. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +9 -0
  61. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  62. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  63. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  64. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  65. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  66. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
  67. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  68. package/dist/api/firmware/getBinary.d.ts +7 -0
  69. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  70. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  71. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  72. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  73. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  74. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  75. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  76. package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -2
  77. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  78. package/dist/api/index.d.ts +0 -2
  79. package/dist/api/index.d.ts.map +1 -1
  80. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  81. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  82. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  83. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  84. package/dist/core/deviceEventRegistration.d.ts +0 -2
  85. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  86. package/dist/core/index.d.ts.map +1 -1
  87. package/dist/data-manager/DataManager.d.ts +2 -4
  88. package/dist/data-manager/DataManager.d.ts.map +1 -1
  89. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  90. package/dist/device/Device.d.ts +6 -29
  91. package/dist/device/Device.d.ts.map +1 -1
  92. package/dist/device/DeviceCommands.d.ts +4 -4
  93. package/dist/device/DeviceCommands.d.ts.map +1 -1
  94. package/dist/device/DevicePool.d.ts.map +1 -1
  95. package/dist/events/device.d.ts +0 -4
  96. package/dist/events/device.d.ts.map +1 -1
  97. package/dist/events/logBlockEvent.d.ts.map +1 -1
  98. package/dist/events/ui-promise.d.ts +2 -4
  99. package/dist/events/ui-promise.d.ts.map +1 -1
  100. package/dist/events/ui-request.d.ts +2 -31
  101. package/dist/events/ui-request.d.ts.map +1 -1
  102. package/dist/events/ui-response.d.ts +2 -10
  103. package/dist/events/ui-response.d.ts.map +1 -1
  104. package/dist/index.d.ts +231 -214
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +3530 -2634
  107. package/dist/inject.d.ts +1 -6
  108. package/dist/inject.d.ts.map +1 -1
  109. package/dist/lowLevelInject.d.ts.map +1 -1
  110. package/dist/protocols/protocol-v2/features.d.ts +4 -0
  111. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  112. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  113. package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
  114. package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
  115. package/dist/protocols/protocol-v2/uiInteraction.d.ts +4 -7
  116. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  117. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
  118. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  119. package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
  120. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
  121. package/dist/protocols/protocol-v2/walletSession.d.ts +1 -6
  122. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  123. package/dist/topLevelInject.d.ts.map +1 -1
  124. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -2
  125. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  126. package/dist/types/api/deviceUnlock.d.ts +1 -5
  127. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  128. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  129. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  130. package/dist/types/api/export.d.ts +4 -2
  131. package/dist/types/api/export.d.ts.map +1 -1
  132. package/dist/types/api/firmwareUpdate.d.ts +70 -2
  133. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  134. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  135. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  136. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  137. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  138. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  139. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  140. package/dist/types/api/index.d.ts +10 -7
  141. package/dist/types/api/index.d.ts.map +1 -1
  142. package/dist/types/api/protocolV2.d.ts +0 -3
  143. package/dist/types/api/protocolV2.d.ts.map +1 -1
  144. package/dist/types/device.d.ts.map +1 -1
  145. package/dist/types/params.d.ts +0 -1
  146. package/dist/types/params.d.ts.map +1 -1
  147. package/dist/types/settings.d.ts +13 -22
  148. package/dist/types/settings.d.ts.map +1 -1
  149. package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
  150. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  151. package/dist/utils/deviceFeaturesUtils.d.ts +0 -4
  152. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  153. package/dist/utils/homescreen.d.ts +0 -4
  154. package/dist/utils/homescreen.d.ts.map +1 -1
  155. package/dist/utils/index.d.ts +1 -1
  156. package/dist/utils/index.d.ts.map +1 -1
  157. package/dist/utils/patch.d.ts +1 -1
  158. package/dist/utils/patch.d.ts.map +1 -1
  159. package/dist/utils/pro2Wallpaper.d.ts +0 -10
  160. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  161. package/package.json +4 -4
  162. package/src/api/BaseMethod.ts +10 -8
  163. package/src/api/CheckAllFirmwareRelease.ts +57 -54
  164. package/src/api/FirmwareUpdate.ts +15 -7
  165. package/src/api/FirmwareUpdateV2.ts +316 -126
  166. package/src/api/FirmwareUpdateV3.ts +265 -63
  167. package/src/api/FirmwareUpdateV4.ts +975 -584
  168. package/src/api/GetPassphraseState.ts +1 -7
  169. package/src/api/OpenWalletSession.ts +17 -70
  170. package/src/api/SearchDevices.ts +1 -3
  171. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  172. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -34
  173. package/src/api/device/DeviceUnlock.ts +3 -8
  174. package/src/api/device/DeviceUpdateBootloader.ts +122 -6
  175. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  176. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  177. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  178. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +83 -16
  179. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  180. package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
  181. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +415 -0
  182. package/src/api/firmware/progressThrottle.ts +44 -0
  183. package/src/api/firmware/updateBootloader.ts +19 -4
  184. package/src/api/firmware/uploadFirmware.ts +144 -39
  185. package/src/api/helpers/protocolV2FileWrite.ts +7 -27
  186. package/src/api/index.ts +0 -2
  187. package/src/api/kaspa/KaspaSignTransaction.ts +12 -1
  188. package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
  189. package/src/api/protocol-v2/helpers.ts +0 -1
  190. package/src/core/deviceEventRegistration.ts +0 -4
  191. package/src/core/index.ts +83 -167
  192. package/src/data/messages/messages-protocol-v2.json +0 -25
  193. package/src/data-manager/DataManager.ts +39 -71
  194. package/src/data-manager/connectSettings.ts +11 -0
  195. package/src/device/Device.ts +43 -259
  196. package/src/device/DeviceCommands.ts +1 -7
  197. package/src/device/DevicePool.ts +2 -7
  198. package/src/events/device.ts +0 -4
  199. package/src/events/logBlockEvent.ts +0 -1
  200. package/src/events/ui-promise.ts +2 -4
  201. package/src/events/ui-request.ts +2 -36
  202. package/src/events/ui-response.ts +2 -12
  203. package/src/index.ts +5 -0
  204. package/src/inject.ts +24 -60
  205. package/src/lowLevelInject.ts +8 -7
  206. package/src/protocols/protocol-v2/features.ts +7 -10
  207. package/src/protocols/protocol-v2/firmware.ts +0 -2
  208. package/src/protocols/protocol-v2/lockedError.ts +10 -0
  209. package/src/protocols/protocol-v2/uiInteraction.ts +6 -46
  210. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
  211. package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
  212. package/src/protocols/protocol-v2/walletSession.ts +36 -214
  213. package/src/topLevelInject.ts +8 -7
  214. package/src/types/api/checkAllFirmwareRelease.ts +4 -2
  215. package/src/types/api/deviceUnlock.ts +1 -12
  216. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  217. package/src/types/api/export.ts +18 -1
  218. package/src/types/api/firmwareUpdate.ts +76 -3
  219. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  220. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  221. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  222. package/src/types/api/index.ts +14 -13
  223. package/src/types/api/protocolV2.ts +0 -13
  224. package/src/types/device.ts +0 -3
  225. package/src/types/params.ts +1 -7
  226. package/src/types/settings.ts +13 -42
  227. package/src/utils/deviceFeaturesCompat.ts +6 -0
  228. package/src/utils/deviceFeaturesUtils.ts +0 -8
  229. package/src/utils/homescreen.ts +6 -32
  230. package/src/utils/index.ts +1 -6
  231. package/src/utils/pro2Wallpaper.ts +8 -35
  232. package/__tests__/deviceUploadNft.test.ts +0 -293
  233. package/__tests__/pro2Nft.test.ts +0 -108
  234. package/__tests__/protocol-binding.test.ts +0 -89
  235. package/__tests__/protocol-v2-resources.test.ts +0 -286
  236. package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -91
  237. package/__tests__/uiProgressThrottle.test.ts +0 -74
  238. package/__tests__/uiPromiseRegistry.test.ts +0 -86
  239. package/dist/api/DetectDeviceConnectProtocol.d.ts +0 -7
  240. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +0 -1
  241. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -31
  242. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
  243. package/dist/core/uiPromiseRegistry.d.ts +0 -4
  244. package/dist/core/uiPromiseRegistry.d.ts.map +0 -1
  245. package/dist/protocols/protocol-v2/resources.d.ts +0 -30
  246. package/dist/protocols/protocol-v2/resources.d.ts.map +0 -1
  247. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
  248. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
  249. package/dist/types/api/detectDeviceConnectProtocol.d.ts +0 -4
  250. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +0 -1
  251. package/dist/utils/pro2Nft.d.ts +0 -31
  252. package/dist/utils/pro2Nft.d.ts.map +0 -1
  253. package/dist/utils/uiProgressThrottle.d.ts +0 -3
  254. package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
  255. package/src/api/DetectDeviceConnectProtocol.ts +0 -18
  256. package/src/api/protocol-v2/DeviceUploadNft.ts +0 -189
  257. package/src/core/uiPromiseRegistry.ts +0 -41
  258. package/src/protocols/protocol-v2/resources.ts +0 -359
  259. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -118
  260. package/src/types/api/detectDeviceConnectProtocol.ts +0 -6
  261. package/src/utils/pro2Nft.ts +0 -142
  262. package/src/utils/uiProgressThrottle.ts +0 -63
@@ -1,293 +0,0 @@
1
- import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
-
3
- import DeviceUploadNft from '../src/api/protocol-v2/DeviceUploadNft';
4
-
5
- jest.mock('../src/data/config', () => ({
6
- getSDKVersion: jest.fn(() => '1.0.0'),
7
- DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
8
- }));
9
-
10
- const createRgba = (width: number, height: number) => {
11
- const data = new Uint8Array(width * height * 4);
12
- for (let index = 3; index < data.length; index += 4) data[index] = 0xff;
13
- return data;
14
- };
15
-
16
- const createMethod = ({
17
- typedCall,
18
- supportedMessages = [60805, 60808, 61500],
19
- useFullBundle = false,
20
- }: {
21
- typedCall: jest.Mock;
22
- supportedMessages?: number[];
23
- useFullBundle?: boolean;
24
- }) => {
25
- const method = new DeviceUploadNft({
26
- id: 1,
27
- payload: {
28
- method: 'deviceUploadNft',
29
- image: { width: 540, height: 540, rgba: createRgba(540, 540) },
30
- thumbnail: { width: 263, height: 263, rgba: createRgba(263, 263) },
31
- title: 'CryptoPunk #3100',
32
- subtitle: 'CryptoPunks',
33
- timestampMs: 1_760_000_000_000,
34
- chunkSize: 2048,
35
- paceMs: 0,
36
- },
37
- });
38
- (method as any).device = {
39
- commands: { typedCall },
40
- ensureProtocolV2RuntimeContext: jest.fn(() =>
41
- Promise.resolve({
42
- version: 2,
43
- build_fingerprint: 'application__1.0.0__test__DEV__DEBUG',
44
- supported_messages: supportedMessages,
45
- })
46
- ),
47
- getCurrentFirmwareType: jest.fn(),
48
- };
49
- method.postMessage = jest.fn();
50
-
51
- if (useFullBundle) {
52
- method.init();
53
- } else {
54
- (method as any).params = { chunkSize: 2048, paceMs: 0, timeoutMs: 15_000 };
55
- (method as any).bundle = {
56
- basename: 'nft-deadbeef-1760000000000',
57
- image: new Uint8Array([1]),
58
- thumbnail: new Uint8Array([2]),
59
- metadata: new TextEncoder().encode('{"title":"NFT","subtitle":""}'),
60
- };
61
- }
62
-
63
- return method;
64
- };
65
-
66
- const fileWriteSuccess = (params: { file: { offset: number; data: Uint8Array } }) => ({
67
- message: {
68
- processed_byte: params.file.offset + params.file.data.byteLength,
69
- },
70
- });
71
-
72
- const nftFileList = (count: number, basenameAt?: number) =>
73
- Array.from({ length: count }, (_, index) => {
74
- const basename =
75
- index === basenameAt
76
- ? 'nft-deadbeef-1760000000000'
77
- : `nft-${index.toString(16).padStart(8, '0')}-${1760000000001 + index}`;
78
- return [`${basename}.bin`, `${basename}_m.bin`, `${basename}.json`];
79
- })
80
- .flat()
81
- .join('\n');
82
-
83
- describe('DeviceUploadNft', () => {
84
- test('defaults to maximum-size chunks without artificial pacing', () => {
85
- const method = new DeviceUploadNft({
86
- id: 1,
87
- payload: {
88
- method: 'deviceUploadNft',
89
- image: { width: 540, height: 540, rgba: createRgba(540, 540) },
90
- thumbnail: { width: 263, height: 263, rgba: createRgba(263, 263) },
91
- title: 'CryptoPunk #3100',
92
- subtitle: 'CryptoPunks',
93
- timestampMs: 1_760_000_000_000,
94
- },
95
- });
96
-
97
- method.init();
98
-
99
- expect((method as any).params).toMatchObject({
100
- chunkSize: 2048,
101
- paceMs: 0,
102
- });
103
- });
104
-
105
- test('uploads the triplet in order without creating the firmware-owned directory', async () => {
106
- const typedCall = jest.fn((request: string, _response: string, params: any) => {
107
- if (request === 'FilesystemDirList') {
108
- return { message: { path: 'vol1:/nft', child_files: '' } };
109
- }
110
- if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
111
- if (request === 'NftUpdate') return { message: { message: 'NFT updated' } };
112
- throw new Error(`Unexpected request: ${request}`);
113
- });
114
- const method = createMethod({ typedCall, useFullBundle: true });
115
-
116
- const result = await method.run();
117
-
118
- const requests = typedCall.mock.calls.map(call => call[0]);
119
- expect(requests[0]).toBe('FilesystemDirList');
120
- expect(typedCall).toHaveBeenNthCalledWith(
121
- 1,
122
- 'FilesystemDirList',
123
- 'FilesystemDir',
124
- { path: 'vol1:/nft', depth: 1 },
125
- { timeoutMs: 15_000 }
126
- );
127
- expect(requests).not.toContain('FilesystemDirMake');
128
- expect(requests.at(-1)).toBe('NftUpdate');
129
- const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
130
- const paths = fileWrites.map(call => call[2].file.path as string);
131
- const imagePath = paths[0];
132
- const thumbnailStart = paths.findIndex(path => path.endsWith('_m.bin'));
133
- const metadataStart = paths.findIndex(path => path.endsWith('.json'));
134
- expect(imagePath).toMatch(/^vol1:\/nft\/nft-[a-f0-9]{8}-1760000000000\.bin$/);
135
- expect(thumbnailStart).toBeGreaterThan(0);
136
- expect(metadataStart).toBeGreaterThan(thumbnailStart);
137
- expect(new Set(paths.slice(0, thumbnailStart))).toEqual(new Set([imagePath]));
138
- expect(new Set(paths.slice(thumbnailStart, metadataStart))).toEqual(
139
- new Set([result.thumbnailPath])
140
- );
141
- expect(typedCall).toHaveBeenLastCalledWith(
142
- 'NftUpdate',
143
- 'Success',
144
- { file_name_no_ext: result.basename },
145
- { timeoutMs: 15_000 }
146
- );
147
- expect(result).toMatchObject({
148
- nftUpdated: true,
149
- message: 'NFT updated',
150
- totalSize: 583_212 + 138_876 + 53,
151
- });
152
- expect(method.postMessage).toHaveBeenLastCalledWith({
153
- event: 'UI_EVENT',
154
- type: 'ui-device_progress',
155
- payload: expect.objectContaining({
156
- progress: 100,
157
- transferredBytes: result.totalSize,
158
- totalBytes: result.totalSize,
159
- }),
160
- });
161
- });
162
-
163
- test('retries one timed-out NftUpdate with the same basename', async () => {
164
- const timeout = Object.assign(new Error('Protocol V2 response timeout'), {
165
- code: 'response-timeout',
166
- });
167
- let updateCalls = 0;
168
- const typedCall = jest.fn((request: string, _response: string, params: any) => {
169
- if (request === 'FilesystemDirList') {
170
- return { message: { path: 'vol1:/nft', child_files: '' } };
171
- }
172
- if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
173
- if (request === 'NftUpdate') {
174
- updateCalls += 1;
175
- if (updateCalls === 1) throw timeout;
176
- return { message: { message: 'NFT updated' } };
177
- }
178
- throw new Error(`Unexpected request: ${request}`);
179
- });
180
- const method = createMethod({ typedCall });
181
-
182
- await expect(method.run()).resolves.toMatchObject({ nftUpdated: true });
183
-
184
- const updateRequests = typedCall.mock.calls.filter(call => call[0] === 'NftUpdate');
185
- expect(updateRequests).toHaveLength(2);
186
- expect(updateRequests[1]).toEqual(updateRequests[0]);
187
- });
188
-
189
- test('does not retry a non-timeout NftUpdate failure', async () => {
190
- const typedCall = jest.fn((request: string, _response: string, params: any) => {
191
- if (request === 'FilesystemDirList') {
192
- return { message: { path: 'vol1:/nft', child_files: '' } };
193
- }
194
- if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
195
- if (request === 'NftUpdate') throw new Error('Invalid NFT metadata');
196
- throw new Error(`Unexpected request: ${request}`);
197
- });
198
- const method = createMethod({ typedCall });
199
-
200
- await expect(method.run()).rejects.toThrow('Invalid NFT metadata');
201
- expect(typedCall.mock.calls.filter(call => call[0] === 'NftUpdate')).toHaveLength(1);
202
- });
203
-
204
- test('propagates a second NftUpdate timeout after one retry', async () => {
205
- const timeout = Object.assign(new Error('Protocol V2 response timeout'), {
206
- code: 'response-timeout',
207
- });
208
- const typedCall = jest.fn((request: string, _response: string, params: any) => {
209
- if (request === 'FilesystemDirList') {
210
- return { message: { path: 'vol1:/nft', child_files: '' } };
211
- }
212
- if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
213
- if (request === 'NftUpdate') throw timeout;
214
- throw new Error(`Unexpected request: ${request}`);
215
- });
216
- const method = createMethod({ typedCall });
217
-
218
- await expect(method.run()).rejects.toBe(timeout);
219
- expect(typedCall.mock.calls.filter(call => call[0] === 'NftUpdate')).toHaveLength(2);
220
- });
221
-
222
- test('does not publish when a file write fails', async () => {
223
- const typedCall = jest.fn((request: string) => {
224
- if (request === 'FilesystemDirList') {
225
- return { message: { path: 'vol1:/nft', child_files: '' } };
226
- }
227
- if (request === 'FilesystemFileWrite') throw new Error('Filesystem full');
228
- throw new Error(`Unexpected request: ${request}`);
229
- });
230
- const method = createMethod({ typedCall });
231
-
232
- await expect(method.run()).rejects.toThrow('Filesystem full');
233
- expect(typedCall.mock.calls.some(call => call[0] === 'NftUpdate')).toBe(false);
234
- });
235
-
236
- test('rejects unsupported firmware before writing files', async () => {
237
- const typedCall = jest.fn();
238
- const method = createMethod({ typedCall, supportedMessages: [60805] });
239
-
240
- await expect(method.run()).rejects.toBeDefined();
241
- expect(typedCall).not.toHaveBeenCalled();
242
- });
243
-
244
- test('rejects a new NFT before writing when ten complete NFT bundles exist', async () => {
245
- const typedCall = jest.fn((request: string) => {
246
- if (request === 'FilesystemDirList') {
247
- return { message: { path: 'vol1:/nft', child_files: nftFileList(10) } };
248
- }
249
- throw new Error(`Unexpected request: ${request}`);
250
- });
251
- const method = createMethod({ typedCall });
252
-
253
- await expect(method.run()).rejects.toMatchObject({
254
- errorCode: HardwareErrorCode.NftStorageLimitReached,
255
- params: { count: 10, limit: 10 },
256
- });
257
- expect(typedCall.mock.calls.map(call => call[0])).toEqual(['FilesystemDirList']);
258
- });
259
-
260
- test('allows an idempotent retry for a basename already counted at the limit', async () => {
261
- const typedCall = jest.fn((request: string, _response: string, params: any) => {
262
- if (request === 'FilesystemDirList') {
263
- return { message: { path: 'vol1:/nft', child_files: nftFileList(10, 0) } };
264
- }
265
- if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
266
- if (request === 'NftUpdate') return { message: {} };
267
- throw new Error(`Unexpected request: ${request}`);
268
- });
269
- const method = createMethod({ typedCall });
270
-
271
- await expect(method.run()).resolves.toMatchObject({ nftUpdated: true });
272
- expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileWrite')).toBe(true);
273
- });
274
-
275
- test('does not count unrelated or incomplete files as stored NFTs', async () => {
276
- const typedCall = jest.fn((request: string, _response: string, params: any) => {
277
- if (request === 'FilesystemDirList') {
278
- return {
279
- message: {
280
- path: 'vol1:/nft',
281
- child_files: `${nftFileList(9)}\nnft-ffffffff-1760000009999.json\nnotes.txt`,
282
- },
283
- };
284
- }
285
- if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
286
- if (request === 'NftUpdate') return { message: {} };
287
- throw new Error(`Unexpected request: ${request}`);
288
- });
289
- const method = createMethod({ typedCall });
290
-
291
- await expect(method.run()).resolves.toMatchObject({ nftUpdated: true });
292
- });
293
- });
@@ -1,108 +0,0 @@
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
- });
@@ -1,89 +0,0 @@
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
- });