@onekeyfe/hd-core 1.2.0-alpha.66 → 1.2.0-alpha.68

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 (111) hide show
  1. package/__tests__/connectSettings.test.ts +47 -5
  2. package/__tests__/device-initialize-timeout.test.ts +56 -0
  3. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  4. package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
  5. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  6. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  7. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  8. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
  9. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  10. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
  11. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  12. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
  13. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  14. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +63 -0
  15. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  16. package/__tests__/protocol-v2.test.ts +371 -94
  17. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  18. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  19. package/dist/api/FirmwareUpdateV2.d.ts +10 -0
  20. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  21. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  22. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  23. package/dist/api/FirmwareUpdateV4.d.ts +25 -4
  24. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  25. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  26. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  27. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  28. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  29. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  30. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  31. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  32. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  33. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
  34. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  35. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  36. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  37. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  38. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  39. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
  40. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  41. package/dist/api/firmware/getBinary.d.ts +7 -0
  42. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  43. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  44. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  45. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  46. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  47. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  48. package/dist/data-manager/DataManager.d.ts +1 -0
  49. package/dist/data-manager/DataManager.d.ts.map +1 -1
  50. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  51. package/dist/device/Device.d.ts +1 -0
  52. package/dist/device/Device.d.ts.map +1 -1
  53. package/dist/index.d.ts +179 -1
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3020 -955
  56. package/dist/inject.d.ts.map +1 -1
  57. package/dist/lowLevelInject.d.ts.map +1 -1
  58. package/dist/topLevelInject.d.ts.map +1 -1
  59. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
  60. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  61. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  62. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  63. package/dist/types/api/export.d.ts +4 -1
  64. package/dist/types/api/export.d.ts.map +1 -1
  65. package/dist/types/api/firmwareUpdate.d.ts +68 -0
  66. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  67. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  68. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  69. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  70. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  71. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  72. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  73. package/dist/types/api/index.d.ts +9 -1
  74. package/dist/types/api/index.d.ts.map +1 -1
  75. package/dist/types/settings.d.ts +13 -0
  76. package/dist/types/settings.d.ts.map +1 -1
  77. package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
  78. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  79. package/package.json +4 -4
  80. package/src/api/CheckAllFirmwareRelease.ts +57 -6
  81. package/src/api/FirmwareUpdate.ts +13 -1
  82. package/src/api/FirmwareUpdateV2.ts +296 -121
  83. package/src/api/FirmwareUpdateV3.ts +263 -57
  84. package/src/api/FirmwareUpdateV4.ts +863 -243
  85. package/src/api/device/DeviceUpdateBootloader.ts +125 -6
  86. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  87. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  88. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  89. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
  90. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  91. package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
  92. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
  93. package/src/api/firmware/updateBootloader.ts +19 -4
  94. package/src/api/firmware/uploadFirmware.ts +140 -22
  95. package/src/data-manager/DataManager.ts +59 -54
  96. package/src/data-manager/connectSettings.ts +11 -0
  97. package/src/device/Device.ts +6 -1
  98. package/src/index.ts +5 -0
  99. package/src/inject.ts +22 -2
  100. package/src/lowLevelInject.ts +5 -1
  101. package/src/topLevelInject.ts +5 -1
  102. package/src/types/api/checkAllFirmwareRelease.ts +4 -0
  103. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  104. package/src/types/api/export.ts +18 -0
  105. package/src/types/api/firmwareUpdate.ts +75 -0
  106. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  107. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  108. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  109. package/src/types/api/index.ts +14 -0
  110. package/src/types/settings.ts +13 -0
  111. package/src/utils/deviceFeaturesCompat.ts +6 -0
@@ -0,0 +1,593 @@
1
+ import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
+
3
+ import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
4
+
5
+ import type { Features } from '../../src/types';
6
+
7
+ const createFeatures = ({
8
+ deviceType,
9
+ firmwareVersion = '1.0.0',
10
+ bootloaderVersion = '1.0.0',
11
+ }: {
12
+ deviceType: EDeviceType;
13
+ firmwareVersion?: string;
14
+ bootloaderVersion?: string;
15
+ }) =>
16
+ ({
17
+ deviceType,
18
+ serialNo: `${deviceType}-device-id`,
19
+ firmwareVersion,
20
+ bootloaderVersion,
21
+ } as Features);
22
+
23
+ const noUpdate = {
24
+ status: 'valid',
25
+ release: undefined,
26
+ };
27
+
28
+ const expectFirmwarePlanInvalid = (build: () => unknown, expectedMessage?: string) => {
29
+ let thrown: unknown;
30
+ try {
31
+ build();
32
+ } catch (error) {
33
+ thrown = error;
34
+ }
35
+ expect(thrown).toMatchObject({
36
+ params: {
37
+ firmwareUpdateCode: 'FirmwarePlanInvalid',
38
+ },
39
+ });
40
+ if (expectedMessage) {
41
+ expect(thrown).toBeInstanceOf(Error);
42
+ expect((thrown as Error).message).toContain(expectedMessage);
43
+ }
44
+ };
45
+
46
+ type BuildPlanInput = Parameters<typeof buildFirmwareUpdatePlan>[0];
47
+
48
+ const createLegacyForceInput = (
49
+ forceUpdateTargets: unknown,
50
+ release: Record<string, unknown> = {
51
+ url: 'https://firmware.onekey.so/pro/firmware.bin',
52
+ version: [4, 21, 0],
53
+ }
54
+ ): BuildPlanInput => ({
55
+ features: createFeatures({
56
+ deviceType: EDeviceType.Pro,
57
+ firmwareVersion: '4.21.0',
58
+ bootloaderVersion: '2.8.4',
59
+ }),
60
+ firmwareType: EFirmwareType.Universal,
61
+ platform: 'desktop',
62
+ firmware: { status: 'valid', release },
63
+ ble: noUpdate,
64
+ bootloader: noUpdate,
65
+ forceUpdateTargets: forceUpdateTargets as BuildPlanInput['forceUpdateTargets'],
66
+ });
67
+
68
+ const createProtocolV2ForceInput = (
69
+ forceUpdateTargets: unknown,
70
+ release: Record<string, unknown>
71
+ ): BuildPlanInput => ({
72
+ features: createFeatures({
73
+ deviceType: EDeviceType.Pro2,
74
+ firmwareVersion: '1.0.0',
75
+ bootloaderVersion: '1.0.0',
76
+ }),
77
+ firmwareType: EFirmwareType.Universal,
78
+ platform: 'desktop',
79
+ firmware: { status: 'valid', release },
80
+ ble: noUpdate,
81
+ bootloader: noUpdate,
82
+ forceUpdateTargets: forceUpdateTargets as BuildPlanInput['forceUpdateTargets'],
83
+ });
84
+
85
+ describe('buildFirmwareUpdatePlan', () => {
86
+ test('selects all legacy artifacts before execution and chooses the desktop full resource', () => {
87
+ const plan = buildFirmwareUpdatePlan({
88
+ features: createFeatures({
89
+ deviceType: EDeviceType.Classic1s,
90
+ firmwareVersion: '1.0.0',
91
+ }),
92
+ firmwareType: EFirmwareType.Universal,
93
+ platform: 'desktop',
94
+ firmware: {
95
+ status: 'outdated',
96
+ release: {
97
+ url: 'https://firmware.onekey.so/classic/firmware.bin',
98
+ expectedSize: 1024,
99
+ fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
100
+ version: [3, 0, 0],
101
+ resource: 'https://firmware.onekey.so/classic/resource.zip',
102
+ fullResource: 'https://firmware.onekey.so/classic/full-resource.zip',
103
+ fullResourceExpectedSize: 4096,
104
+ fullResourceFingerprint:
105
+ '2222222222222222222222222222222222222222222222222222222222222222',
106
+ fullResourceRange: ['2.0.0', '3.0.0'],
107
+ },
108
+ },
109
+ ble: {
110
+ status: 'outdated',
111
+ release: {
112
+ webUpdate: 'https://firmware.onekey.so/classic/ble.bin',
113
+ expectedSize: 512,
114
+ fingerprintWeb: '3333333333333333333333333333333333333333333333333333333333333333',
115
+ },
116
+ },
117
+ bootloader: {
118
+ status: 'outdated',
119
+ shouldUpdate: true,
120
+ release: {
121
+ bootloaderResource: 'https://firmware.onekey.so/classic/bootloader.bin',
122
+ bootloaderExpectedSize: 768,
123
+ bootloaderFingerprint: '4444444444444444444444444444444444444444444444444444444444444444',
124
+ bootloaderVersion: [2, 0, 0],
125
+ },
126
+ },
127
+ });
128
+
129
+ expect(plan.executor).toBe('v2');
130
+ expect(plan.artifacts).toEqual([
131
+ expect.objectContaining({
132
+ artifactId: 'bootloader',
133
+ role: 'bootloader',
134
+ expectedSize: 768,
135
+ targetVersion: '2.0.0',
136
+ }),
137
+ expect.objectContaining({
138
+ artifactId: 'firmware',
139
+ role: 'firmware',
140
+ expectedSize: 1024,
141
+ targetVersion: '3.0.0',
142
+ }),
143
+ expect.objectContaining({
144
+ artifactId: 'resource',
145
+ role: 'resource',
146
+ url: 'https://firmware.onekey.so/classic/full-resource.zip',
147
+ expectedSize: 4096,
148
+ }),
149
+ expect.objectContaining({
150
+ artifactId: 'ble',
151
+ role: 'ble',
152
+ expectedSize: 512,
153
+ }),
154
+ ]);
155
+ expect(plan.targetsToUpdate).toEqual(['bootloader', 'firmware', 'resource', 'ble']);
156
+ });
157
+
158
+ test('selects V3 for Pro with bootloader 2.8 or newer', () => {
159
+ const plan = buildFirmwareUpdatePlan({
160
+ features: createFeatures({
161
+ deviceType: EDeviceType.Pro,
162
+ bootloaderVersion: '2.8.0',
163
+ }),
164
+ firmwareType: EFirmwareType.BitcoinOnly,
165
+ platform: 'native',
166
+ firmware: {
167
+ status: 'outdated',
168
+ release: {
169
+ url: 'https://firmware.onekey.so/pro/firmware.bin',
170
+ version: [4, 0, 0],
171
+ },
172
+ },
173
+ ble: noUpdate,
174
+ bootloader: noUpdate,
175
+ });
176
+
177
+ expect(plan.executor).toBe('v3');
178
+ expect(plan.firmwareType).toBe(EFirmwareType.BitcoinOnly);
179
+ expect(plan.platform).toBe('native');
180
+ });
181
+
182
+ test('includes same-version legacy artifacts selected by the host', () => {
183
+ const plan = buildFirmwareUpdatePlan({
184
+ features: createFeatures({
185
+ deviceType: EDeviceType.Pro,
186
+ firmwareVersion: '4.21.0',
187
+ bootloaderVersion: '2.8.4',
188
+ }),
189
+ firmwareType: EFirmwareType.Universal,
190
+ platform: 'desktop',
191
+ firmware: {
192
+ status: 'valid',
193
+ release: {
194
+ url: 'https://firmware.onekey.so/pro/firmware.bin',
195
+ version: [4, 21, 0],
196
+ resource: 'https://firmware.onekey.so/pro/resource.zip',
197
+ },
198
+ },
199
+ ble: {
200
+ status: 'valid',
201
+ release: {
202
+ webUpdate: 'https://firmware.onekey.so/pro/ble.bin',
203
+ version: [2, 3, 7],
204
+ },
205
+ },
206
+ bootloader: {
207
+ status: 'valid',
208
+ release: {
209
+ bootloaderResource: 'https://firmware.onekey.so/pro/bootloader.bin',
210
+ bootloaderVersion: [2, 8, 4],
211
+ },
212
+ },
213
+ forceUpdateTargets: ['firmware', 'resource', 'ble', 'bootloader'],
214
+ });
215
+
216
+ expect(plan.executor).toBe('v3');
217
+ expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual([
218
+ 'bootloader',
219
+ 'firmware',
220
+ 'resource',
221
+ 'ble',
222
+ ]);
223
+ expect(plan.targetsToUpdate).toEqual(['bootloader', 'firmware', 'resource', 'ble']);
224
+ });
225
+
226
+ test('does not include a same-version resource unless the host selects it', () => {
227
+ const plan = buildFirmwareUpdatePlan({
228
+ features: createFeatures({
229
+ deviceType: EDeviceType.Pro,
230
+ firmwareVersion: '4.21.0',
231
+ bootloaderVersion: '2.8.4',
232
+ }),
233
+ firmwareType: EFirmwareType.Universal,
234
+ platform: 'desktop',
235
+ firmware: {
236
+ status: 'valid',
237
+ release: {
238
+ url: 'https://firmware.onekey.so/pro/firmware.bin',
239
+ version: [4, 21, 0],
240
+ resource: 'https://firmware.onekey.so/pro/resource.zip',
241
+ },
242
+ },
243
+ ble: noUpdate,
244
+ bootloader: noUpdate,
245
+ forceUpdateTargets: ['firmware'],
246
+ });
247
+
248
+ expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual(['firmware']);
249
+ expect(plan.targetsToUpdate).toEqual(['firmware']);
250
+ });
251
+
252
+ test.each([
253
+ {
254
+ label: 'a non-array value',
255
+ forceUpdateTargets: 'firmware',
256
+ },
257
+ {
258
+ label: 'an unknown target',
259
+ forceUpdateTargets: ['firmware', 'unknown'],
260
+ },
261
+ {
262
+ label: 'a duplicate target',
263
+ forceUpdateTargets: ['firmware', 'firmware'],
264
+ },
265
+ ])('rejects $label in forceUpdateTargets', ({ forceUpdateTargets }) => {
266
+ expectFirmwarePlanInvalid(() =>
267
+ buildFirmwareUpdatePlan(createLegacyForceInput(forceUpdateTargets))
268
+ );
269
+ });
270
+
271
+ test('rejects an explicitly forced legacy resource without a resource artifact', () => {
272
+ expectFirmwarePlanInvalid(() => buildFirmwareUpdatePlan(createLegacyForceInput(['resource'])));
273
+ });
274
+
275
+ test.each(['ble', 'bootloader'] as const)(
276
+ 'rejects the unsupported Pro2 %s force target',
277
+ forceTarget => {
278
+ expectFirmwarePlanInvalid(
279
+ () =>
280
+ buildFirmwareUpdatePlan(
281
+ createProtocolV2ForceInput([forceTarget], {
282
+ components: {
283
+ applicationP1: {
284
+ target: 'APPLICATION_P1',
285
+ url: 'https://firmware.onekey.so/pro2/application-p1.bin',
286
+ },
287
+ },
288
+ })
289
+ ),
290
+ 'does not support forced'
291
+ );
292
+ }
293
+ );
294
+
295
+ test.each([
296
+ {
297
+ forceTarget: 'firmware' as const,
298
+ release: {
299
+ components: {},
300
+ resourceBundles: [
301
+ {
302
+ name: 'images',
303
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
304
+ },
305
+ ],
306
+ },
307
+ },
308
+ {
309
+ forceTarget: 'resource' as const,
310
+ release: {
311
+ components: {
312
+ applicationP1: {
313
+ target: 'APPLICATION_P1',
314
+ url: 'https://firmware.onekey.so/pro2/application-p1.bin',
315
+ },
316
+ },
317
+ },
318
+ },
319
+ ])(
320
+ 'rejects a Pro2 $forceTarget force target not represented by an artifact',
321
+ ({ forceTarget, release }) => {
322
+ expectFirmwarePlanInvalid(() =>
323
+ buildFirmwareUpdatePlan(createProtocolV2ForceInput([forceTarget], release))
324
+ );
325
+ }
326
+ );
327
+
328
+ test.each([
329
+ {
330
+ forceTarget: 'firmware' as const,
331
+ expectedArtifactIds: ['component:app_v1'],
332
+ expectedTargets: ['app_v1'],
333
+ },
334
+ {
335
+ forceTarget: 'resource' as const,
336
+ expectedArtifactIds: ['resourceBundle:images'],
337
+ expectedTargets: ['resource'],
338
+ },
339
+ ])(
340
+ 'limits a Pro2 $forceTarget force to the selected artifact role',
341
+ ({ forceTarget, expectedArtifactIds, expectedTargets }) => {
342
+ const plan = buildFirmwareUpdatePlan(
343
+ createProtocolV2ForceInput([forceTarget], {
344
+ components: {
345
+ applicationP1: {
346
+ target: 'APPLICATION_P1',
347
+ url: 'https://firmware.onekey.so/pro2/application-p1.bin',
348
+ },
349
+ },
350
+ resourceBundles: [
351
+ {
352
+ name: 'images',
353
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
354
+ },
355
+ ],
356
+ })
357
+ );
358
+
359
+ expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual(expectedArtifactIds);
360
+ expect(plan.targetsToUpdate).toEqual(expectedTargets);
361
+ }
362
+ );
363
+
364
+ test('rejects a prepared native plan without a stable device identity', () => {
365
+ const features = createFeatures({
366
+ deviceType: EDeviceType.Classic1s,
367
+ });
368
+ delete (features as Partial<Features>).serialNo;
369
+
370
+ expect(() =>
371
+ buildFirmwareUpdatePlan({
372
+ features,
373
+ firmwareType: EFirmwareType.Universal,
374
+ platform: 'native',
375
+ firmware: {
376
+ status: 'outdated',
377
+ release: {
378
+ url: 'https://firmware.onekey.so/classic/firmware.bin',
379
+ },
380
+ },
381
+ ble: noUpdate,
382
+ bootloader: noUpdate,
383
+ })
384
+ ).toThrow('requires a stable device identity');
385
+ });
386
+
387
+ test('maps the Pro2 component order and resource bundles without downloading them', () => {
388
+ const plan = buildFirmwareUpdatePlan({
389
+ features: createFeatures({ deviceType: EDeviceType.Pro2 }),
390
+ firmwareType: EFirmwareType.Universal,
391
+ platform: 'desktop',
392
+ firmware: {
393
+ status: 'required',
394
+ release: {
395
+ installOrder: ['bootloader', 'applicationP1'],
396
+ components: {
397
+ applicationP1: {
398
+ target: 'APPLICATION_P1',
399
+ url: 'https://firmware.onekey.so/pro2/application-p1.bin',
400
+ expectedSize: 1024,
401
+ fingerprint: '1111111111111111111111111111111111111111111111111111111111111111',
402
+ },
403
+ bootloader: {
404
+ target: 'BOOTLOADER',
405
+ url: 'https://firmware.onekey.so/pro2/bootloader.bin',
406
+ },
407
+ se01: {
408
+ target: 'SE01',
409
+ url: 'https://firmware.onekey.so/pro2/se01.bin',
410
+ },
411
+ },
412
+ resourceBundles: [
413
+ {
414
+ name: 'images',
415
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
416
+ },
417
+ ],
418
+ },
419
+ },
420
+ ble: noUpdate,
421
+ bootloader: noUpdate,
422
+ });
423
+
424
+ expect(plan.executor).toBe('v4');
425
+ expect(plan.artifacts.map(artifact => artifact.artifactId)).toEqual([
426
+ 'component:boot',
427
+ 'component:app_v1',
428
+ 'component:se01',
429
+ 'resourceBundle:images',
430
+ ]);
431
+ expect(plan.targetsToUpdate).toEqual(['boot', 'app_v1', 'se01', 'resource']);
432
+ expect(plan.artifacts[1]).toEqual(
433
+ expect.objectContaining({
434
+ expectedSize: 1024,
435
+ expectedSha256: '1111111111111111111111111111111111111111111111111111111111111111',
436
+ })
437
+ );
438
+ });
439
+
440
+ test('rejects Pro2 ROMLOADER because it requires a separate loader flow', () => {
441
+ expect(() =>
442
+ buildFirmwareUpdatePlan({
443
+ features: createFeatures({ deviceType: EDeviceType.Pro2 }),
444
+ firmwareType: EFirmwareType.Universal,
445
+ platform: 'desktop',
446
+ firmware: {
447
+ status: 'required',
448
+ release: {
449
+ components: {
450
+ romloader: {
451
+ target: 'ROMLOADER',
452
+ url: 'https://firmware.onekey.so/pro2/romloader.bin',
453
+ },
454
+ },
455
+ },
456
+ },
457
+ ble: noUpdate,
458
+ bootloader: noUpdate,
459
+ })
460
+ ).toThrow('ROMLOADER requires its dedicated loader flow');
461
+ });
462
+
463
+ test.each([
464
+ {
465
+ label: 'component target',
466
+ release: {
467
+ components: {
468
+ application: {
469
+ target: 'APPLICATION_P1',
470
+ url: 'https://firmware.onekey.so/pro2/application.bin',
471
+ },
472
+ applicationDuplicate: {
473
+ target: 'APPLICATION_P1',
474
+ url: 'https://firmware.onekey.so/pro2/application-duplicate.bin',
475
+ },
476
+ },
477
+ },
478
+ expectedError: 'duplicates target app_v1',
479
+ },
480
+ {
481
+ label: 'resource bundle name',
482
+ release: {
483
+ components: {
484
+ application: {
485
+ target: 'APPLICATION_P1',
486
+ url: 'https://firmware.onekey.so/pro2/application.bin',
487
+ },
488
+ },
489
+ resourceBundles: [
490
+ {
491
+ name: 'images',
492
+ url: 'https://firmware.onekey.so/pro2/images.okpkg',
493
+ },
494
+ {
495
+ name: 'images',
496
+ url: 'https://firmware.onekey.so/pro2/images-duplicate.okpkg',
497
+ },
498
+ ],
499
+ },
500
+ expectedError: 'duplicates name images',
501
+ },
502
+ ])('rejects a duplicate Pro2 $label', ({ release, expectedError }) => {
503
+ expect(() =>
504
+ buildFirmwareUpdatePlan({
505
+ features: createFeatures({ deviceType: EDeviceType.Pro2 }),
506
+ firmwareType: EFirmwareType.Universal,
507
+ platform: 'desktop',
508
+ firmware: {
509
+ status: 'required',
510
+ release,
511
+ },
512
+ ble: noUpdate,
513
+ bootloader: noUpdate,
514
+ })
515
+ ).toThrow(expectedError);
516
+ });
517
+
518
+ test('produces a deterministic digest and binds it to the platform', () => {
519
+ const input = {
520
+ features: createFeatures({ deviceType: EDeviceType.Classic1s }),
521
+ firmwareType: EFirmwareType.Universal,
522
+ firmware: noUpdate,
523
+ ble: noUpdate,
524
+ bootloader: noUpdate,
525
+ };
526
+ const first = buildFirmwareUpdatePlan({ ...input, platform: 'native' });
527
+ const second = buildFirmwareUpdatePlan({ ...input, platform: 'native' });
528
+ const desktop = buildFirmwareUpdatePlan({ ...input, platform: 'desktop' });
529
+
530
+ expect(first.planDigest).toMatch(/^[a-f0-9]{64}$/);
531
+ expect(first.planDigest).toBe(second.planDigest);
532
+ expect(first.planDigest).not.toBe(desktop.planDigest);
533
+ });
534
+
535
+ test('normalizes a malformed artifact URL to FirmwarePlanInvalid', () => {
536
+ let thrown: unknown;
537
+ try {
538
+ buildFirmwareUpdatePlan({
539
+ features: createFeatures({ deviceType: EDeviceType.Classic1s }),
540
+ firmwareType: EFirmwareType.Universal,
541
+ platform: 'native',
542
+ firmware: {
543
+ status: 'outdated',
544
+ release: {
545
+ url: 'not a url',
546
+ version: [3, 0, 0],
547
+ },
548
+ },
549
+ ble: noUpdate,
550
+ bootloader: noUpdate,
551
+ });
552
+ } catch (error) {
553
+ thrown = error;
554
+ }
555
+
556
+ expect(thrown).toMatchObject({
557
+ params: {
558
+ firmwareUpdateCode: 'FirmwarePlanInvalid',
559
+ },
560
+ });
561
+ });
562
+
563
+ test('binds trusted artifact integrity metadata into the plan digest', () => {
564
+ const createPlan = (fingerprint: string) =>
565
+ buildFirmwareUpdatePlan({
566
+ features: createFeatures({ deviceType: EDeviceType.Classic1s }),
567
+ firmwareType: EFirmwareType.Universal,
568
+ platform: 'native',
569
+ firmware: {
570
+ status: 'outdated',
571
+ release: {
572
+ url: 'https://firmware.onekey.so/classic/firmware.bin',
573
+ version: [3, 0, 0],
574
+ expectedSize: 1024,
575
+ fingerprint,
576
+ },
577
+ },
578
+ ble: noUpdate,
579
+ bootloader: noUpdate,
580
+ });
581
+
582
+ const first = createPlan('1111111111111111111111111111111111111111111111111111111111111111');
583
+ const second = createPlan('2222222222222222222222222222222222222222222222222222222222222222');
584
+
585
+ expect(first.artifacts[0]).toEqual(
586
+ expect.objectContaining({
587
+ expectedSize: 1024,
588
+ expectedSha256: '1111111111111111111111111111111111111111111111111111111111111111',
589
+ })
590
+ );
591
+ expect(first.planDigest).not.toBe(second.planDigest);
592
+ });
593
+ });