@onekeyfe/hd-core 1.2.0-alpha.27 → 1.2.0-alpha.28

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.
@@ -1,4 +1,4 @@
1
- import { ERRORS, EDeviceType, EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import CheckAllFirmwareRelease from '../../src/api/CheckAllFirmwareRelease';
4
4
  import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
@@ -15,6 +15,7 @@ jest.mock('../../src/data/config', () => ({
15
15
 
16
16
  jest.mock('../../src/api/firmware/FirmwareUpdatePlan', () => ({
17
17
  buildFirmwareUpdatePlan: jest.fn(),
18
+ validateFirmwareUpdatePlanForceTargets: jest.fn(value => value ?? []),
18
19
  }));
19
20
 
20
21
  jest.mock('../../src/api/firmware/releaseHelper', () => ({
@@ -147,6 +148,21 @@ describe('CheckAllFirmwareRelease', () => {
147
148
  );
148
149
  });
149
150
 
151
+ test('does not hide a Plan failure for an explicitly forced target', async () => {
152
+ const planError = ERRORS.TypedError(
153
+ HardwareErrorCode.RuntimeError,
154
+ 'Forced firmware target is unavailable',
155
+ {
156
+ firmwareUpdateCode: 'FirmwarePlanInvalid',
157
+ }
158
+ );
159
+ mockBuildFirmwareUpdatePlan.mockImplementation(() => {
160
+ throw planError;
161
+ });
162
+
163
+ await expect(createMethodWithForceTargets().run()).rejects.toBe(planError);
164
+ });
165
+
150
166
  test('does not hide unexpected Plan builder failures', async () => {
151
167
  const unexpectedError = new Error('unexpected builder failure');
152
168
  mockBuildFirmwareUpdatePlan.mockImplementation(() => {
@@ -0,0 +1,27 @@
1
+ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ import { normalizeFirmwarePreparationError } from '../../src/api/firmware/FirmwarePreparationError';
4
+
5
+ describe('normalizeFirmwarePreparationError', () => {
6
+ test('preserves structured firmware invariant errors', () => {
7
+ const error = ERRORS.TypedError(
8
+ HardwareErrorCode.RuntimeError,
9
+ 'Firmware must be prepared by the external firmware host',
10
+ {
11
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
12
+ artifactName: 'firmware',
13
+ }
14
+ );
15
+
16
+ expect(normalizeFirmwarePreparationError(error)).toBe(error);
17
+ });
18
+
19
+ test('wraps ordinary preparation failures as firmware download errors', () => {
20
+ const error = normalizeFirmwarePreparationError(new Error('request failed'));
21
+
22
+ expect(error).toMatchObject({
23
+ errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
24
+ message: 'request failed',
25
+ });
26
+ });
27
+ });
@@ -25,6 +25,63 @@ const noUpdate = {
25
25
  release: undefined,
26
26
  };
27
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
+
28
85
  describe('buildFirmwareUpdatePlan', () => {
29
86
  test('selects all legacy artifacts before execution and chooses the desktop full resource', () => {
30
87
  const plan = buildFirmwareUpdatePlan({
@@ -192,6 +249,118 @@ describe('buildFirmwareUpdatePlan', () => {
192
249
  expect(plan.targetsToUpdate).toEqual(['firmware']);
193
250
  });
194
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
+
195
364
  test('rejects a prepared native plan without a stable device identity', () => {
196
365
  const features = createFeatures({
197
366
  deviceType: EDeviceType.Classic1s,
@@ -1,4 +1,4 @@
1
- import { EDeviceType, EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import FirmwareUpdateV2 from '../../src/api/FirmwareUpdateV2';
4
4
  import { getBinary } from '../../src/api/firmware/getBinary';
@@ -181,6 +181,25 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
181
181
  expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
182
182
  });
183
183
 
184
+ it('preserves structured preparation errors before any firmware mutation', async () => {
185
+ const preparationError = ERRORS.TypedError(
186
+ HardwareErrorCode.RuntimeError,
187
+ 'Firmware must be prepared by the external firmware host',
188
+ {
189
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
190
+ artifactName: 'firmware',
191
+ }
192
+ );
193
+ mockGetBinary.mockRejectedValue(preparationError);
194
+ const { method, typedCall, acquire } = createMethod();
195
+
196
+ await expect(method.run()).rejects.toBe(preparationError);
197
+
198
+ expectNoFirmwareMutationCalls(typedCall);
199
+ expect(acquire).not.toHaveBeenCalled();
200
+ expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
201
+ });
202
+
184
203
  it.each([
185
204
  {
186
205
  mode: 'firmware release.url',
@@ -1 +1 @@
1
- {"version":3,"file":"CheckAllFirmwareRelease.d.ts","sourceRoot":"","sources":["../../src/api/CheckAllFirmwareRelease.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiB1C,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,sCAAsC,CAAC;AAK9C,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,UAAU;IAC7D,IAAI;IAME,GAAG;CAgFV"}
1
+ {"version":3,"file":"CheckAllFirmwareRelease.d.ts","sourceRoot":"","sources":["../../src/api/CheckAllFirmwareRelease.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAoB1C,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,sCAAsC,CAAC;AAK9C,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,UAAU;IAC7D,IAAI;IAME,GAAG;CAkFV"}
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV2.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV2.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,aAAa,EAKnB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAsB1C,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,UAAU,CAAC;AAEtD,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAC;AAE1F,KAAK,MAAM,GAAG;IACZ,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAC7C,eAAe,CAAC,EAAE,KAAK,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,yBAAyB,CAAC;KACrC,CAAC,CAAC;IACH,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,YAAY,CAAC,EAAE,0BAA0B,CAAC;IAC1C,QAAQ,CAAC,EAAE,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAClD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,UAAU,GAAG,KAAK,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B,CAAC;AAkFF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,UAAU,CAAC,MAAM,CAAC;IAC9D,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,IAAI;IA6EJ,cAAc,YAAa,MAAM,UAS/B;YAEY,qCAAqC;IAkBnD,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;YAkFvC,4BAA4B;IAuB1C,qBAAqB;IAUrB,uBAAuB,CAAC,UAAU,EAAE,MAAM;IAc1C,gCAAgC,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAAE,YAAY,EAAE,aAAa;IAwBtF,GAAG;CA4QV"}
1
+ {"version":3,"file":"FirmwareUpdateV2.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV2.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,aAAa,EAKnB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAuB1C,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,UAAU,CAAC;AAEtD,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAC;AAE1F,KAAK,MAAM,GAAG;IACZ,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAC7C,eAAe,CAAC,EAAE,KAAK,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,yBAAyB,CAAC;KACrC,CAAC,CAAC;IACH,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,YAAY,CAAC,EAAE,0BAA0B,CAAC;IAC1C,QAAQ,CAAC,EAAE,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAClD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,UAAU,GAAG,KAAK,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B,CAAC;AAkFF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,UAAU,CAAC,MAAM,CAAC;IAC9D,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,IAAI;IA6EJ,cAAc,YAAa,MAAM,UAS/B;YAEY,qCAAqC;IAkBnD,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;YAkFvC,4BAA4B;IAuB1C,qBAAqB;IAUrB,uBAAuB,CAAC,UAAU,EAAE,MAAM;IAc1C,gCAAgC,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAAE,YAAY,EAAE,aAAa;IAwBtF,GAAG;CAyQV"}
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV3.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV3.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAY/E,OAAO,KAAK,EAEV,sBAAsB,EACvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,qBAAqB,CAAC;AAMnE,eAAO,MAAM,gCAAgC,UAAU,CAAC;AAaxD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe,CAA4B;IAEnD,IAAI;IAmFE,GAAG;;;;;YAQK,aAAa;IA6D3B,OAAO,CAAC,wBAAwB;YAiBlB,kBAAkB;YAelB,oBAAoB;IAKlC,OAAO,CAAC,wBAAwB;YAgBlB,oBAAoB;YA2DpB,uBAAuB;YAuBvB,4BAA4B;YA+E5B,aAAa;IAwN3B,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAmB7B,OAAO,CAAC,sCAAsC;IAexC,sBAAsB,CAAC,OAAO,EAAE,MAAM;CAuE7C"}
1
+ {"version":3,"file":"FirmwareUpdateV3.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV3.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAY/E,OAAO,KAAK,EAEV,sBAAsB,EACvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,qBAAqB,CAAC;AAMnE,eAAO,MAAM,gCAAgC,UAAU,CAAC;AAaxD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe,CAA4B;IAEnD,IAAI;IAmFE,GAAG;;;;;YAQK,aAAa;IA6D3B,OAAO,CAAC,wBAAwB;YAiBlB,kBAAkB;YAelB,oBAAoB;IAKlC,OAAO,CAAC,wBAAwB;YAgBlB,oBAAoB;YA2DpB,uBAAuB;YAuBvB,4BAA4B;YA+E5B,aAAa;IAwN3B,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAmB7B,OAAO,CAAC,sCAAsC;IAexC,sBAAsB,CAAC,OAAO,EAAE,MAAM;CAuE7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAkB/E,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AA4VrC,eAAO,MAAM,iCAAiC,0BACrB,MAAM,uBACR,MAAM,SAc5B,CAAC;AAUF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,OAAO,CAAC,8BAA8B,CAAC,CAAS;IAEhD,qBAAqB;IAIrB,OAAO,CAAC,yBAAyB,CAA4B;IAE7D,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,6BAA6B,CAAC,CAAW;IAEjD,OAAO,CAAC,6BAA6B,CAAS;IAE9C,IAAI;IAoHJ,OAAO,CAAC,8BAA8B;IAoBhC,GAAG;;;;;YAKK,aAAa;YAsEb,4BAA4B;YAkB5B,0BAA0B;YAY1B,8BAA8B;YAK9B,+BAA+B;YAqD/B,gCAAgC;YAmDhC,8BAA8B;IAsB5C,OAAO,CAAC,8BAA8B;IA0BtC,OAAO,CAAC,kCAAkC;IAc1C,OAAO,CAAC,gCAAgC;IAsDxC,OAAO,CAAC,6BAA6B;YAsBvB,2BAA2B;IAWzC,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,kCAAkC;YAO5B,iCAAiC;YAOjC,iCAAiC;YAgBjC,iCAAiC;IAM/C,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,wBAAwB;IAkBhC,OAAO,CAAC,6BAA6B;YAMvB,8BAA8B;YA6C9B,iCAAiC;YAmBjC,+BAA+B;IA+D7C,OAAO,CAAC,gCAAgC;YAyC1B,oCAAoC;YAkCpC,kCAAkC;IAgChD,OAAO,CAAC,0BAA0B;IAO5B,6BAA6B;YA0BrB,+BAA+B;IA4C7C,OAAO,CAAC,6BAA6B;IAkCrC,OAAO,CAAC,8BAA8B;YA8CxB,uBAAuB;YAiCvB,6BAA6B;YAa7B,uBAAuB;YA6CvB,8BAA8B;YA2D9B,6BAA6B;IA0D3C,OAAO,CAAC,mCAAmC;YAI7B,0BAA0B;IAaxC,OAAO,CAAC,4BAA4B;YA+FtB,uCAAuC;YA4GvC,gCAAgC;YAShC,8BAA8B;YAsB9B,mCAAmC;YAWnC,qCAAqC;YA2CrC,yBAAyB;YAiDzB,8BAA8B;IAU5C,OAAO,CAAC,kCAAkC;YAI5B,cAAc;YA+Bd,6BAA6B;YAU7B,0BAA0B;YAS1B,6BAA6B;YAmB7B,gBAAgB;IAiB9B,OAAO,CAAC,qBAAqB;CAM9B"}
1
+ {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAkB/E,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AA4VrC,eAAO,MAAM,iCAAiC,0BACrB,MAAM,uBACR,MAAM,SAc5B,CAAC;AAUF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,OAAO,CAAC,8BAA8B,CAAC,CAAS;IAEhD,qBAAqB;IAIrB,OAAO,CAAC,yBAAyB,CAA4B;IAE7D,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,6BAA6B,CAAC,CAAW;IAEjD,OAAO,CAAC,6BAA6B,CAAS;IAE9C,IAAI;IAoHJ,OAAO,CAAC,8BAA8B;IAoBhC,GAAG;;;;;YAKK,aAAa;YAsEb,4BAA4B;YAkB5B,0BAA0B;YAY1B,8BAA8B;YAK9B,+BAA+B;YAqD/B,gCAAgC;YAmDhC,8BAA8B;IAsB5C,OAAO,CAAC,8BAA8B;IA0BtC,OAAO,CAAC,kCAAkC;IAc1C,OAAO,CAAC,gCAAgC;IAsDxC,OAAO,CAAC,6BAA6B;YAsBvB,2BAA2B;IAWzC,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,kCAAkC;YAO5B,iCAAiC;YAOjC,iCAAiC;YAgBjC,iCAAiC;IAM/C,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,wBAAwB;IAkBhC,OAAO,CAAC,6BAA6B;YAMvB,8BAA8B;YA6C9B,iCAAiC;YAmBjC,+BAA+B;IA+D7C,OAAO,CAAC,gCAAgC;YAyC1B,oCAAoC;YAkCpC,kCAAkC;IAgChD,OAAO,CAAC,0BAA0B;IAO5B,6BAA6B;YA0BrB,+BAA+B;IA4C7C,OAAO,CAAC,6BAA6B;IAkCrC,OAAO,CAAC,8BAA8B;YA8CxB,uBAAuB;YAiCvB,6BAA6B;YAa7B,uBAAuB;YA6CvB,8BAA8B;YA2D9B,6BAA6B;IA0D3C,OAAO,CAAC,mCAAmC;YAI7B,0BAA0B;IAaxC,OAAO,CAAC,4BAA4B;YA+FtB,uCAAuC;YA4GvC,gCAAgC;YAShC,8BAA8B;YAsB9B,mCAAmC;YAWnC,qCAAqC;YA2CrC,yBAAyB;YAiDzB,8BAA8B;IAU5C,OAAO,CAAC,kCAAkC;YAI5B,cAAc;YA+Bd,6BAA6B;YAU7B,0BAA0B;YAS1B,6BAA6B;YAmB7B,gBAAgB;IAiB9B,OAAO,CAAC,qBAAqB;CAM9B"}
@@ -0,0 +1,3 @@
1
+ import { HardwareError } from '@onekeyfe/hd-shared';
2
+ export declare const normalizeFirmwarePreparationError: (error: unknown) => HardwareError;
3
+ //# sourceMappingURL=FirmwarePreparationError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FirmwarePreparationError.d.ts","sourceRoot":"","sources":["../../../src/api/firmware/FirmwarePreparationError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AAE/E,eAAO,MAAM,iCAAiC,UAAW,OAAO,kBAS/D,CAAC"}
@@ -8,6 +8,7 @@ type ReleaseSelection = {
8
8
  release?: unknown;
9
9
  };
10
10
  export declare const digestFirmwareUpdateContract: (value: unknown) => string;
11
+ export declare const validateFirmwareUpdatePlanForceTargets: (value: unknown) => FirmwareUpdatePlanForceTarget[];
11
12
  export declare const FIRMWARE_UPDATE_PLAN_TARGETS: Set<FirmwareUpdatePlanTarget>;
12
13
  export declare const FIRMWARE_UPDATE_PLAN_ROLES: Set<string>;
13
14
  export declare const assertFirmwareUpdatePlan: (value: unknown) => FirmwareUpdatePlan;
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdatePlan.d.ts","sourceRoot":"","sources":["../../../src/api/firmware/FirmwareUpdatePlan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AAS5F,OAAO,KAAK,EACV,kBAAkB,EAElB,6BAA6B,EAC7B,wBAAwB,EACzB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,KAAK,sBAAsB,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,WAAW,CAAC;AAyBjF,KAAK,gBAAgB,GAAG;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA0GF,eAAO,MAAM,4BAA4B,UAAW,OAAO,KAAG,MAG3D,CAAC;AAgCJ,eAAO,MAAM,4BAA4B,+BAavC,CAAC;AAEH,eAAO,MAAM,0BAA0B,aAOrC,CAAC;AAYH,eAAO,MAAM,wBAAwB,UAAW,OAAO,KAAG,kBAoHzD,CAAC;AA2JF,eAAO,MAAM,uBAAuB;cASxB,QAAQ;kBACJ,aAAa;cACjB,sBAAsB;cACtB,gBAAgB;SACrB,gBAAgB;;;MAGnB,kBA+HH,CAAC"}
1
+ {"version":3,"file":"FirmwareUpdatePlan.d.ts","sourceRoot":"","sources":["../../../src/api/firmware/FirmwareUpdatePlan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AAS5F,OAAO,KAAK,EACV,kBAAkB,EAElB,6BAA6B,EAC7B,wBAAwB,EACzB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,KAAK,sBAAsB,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,WAAW,CAAC;AAyBjF,KAAK,gBAAgB,GAAG;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA0GF,eAAO,MAAM,4BAA4B,UAAW,OAAO,KAAG,MAG3D,CAAC;AAkBJ,eAAO,MAAM,sCAAsC,UAC1C,OAAO,KACb,6BAA6B,EAa/B,CAAC;AAuBF,eAAO,MAAM,4BAA4B,+BAavC,CAAC;AAEH,eAAO,MAAM,0BAA0B,aAOrC,CAAC;AAYH,eAAO,MAAM,wBAAwB,UAAW,OAAO,KAAG,kBAoHzD,CAAC;AA4NF,eAAO,MAAM,uBAAuB;cASxB,QAAQ;kBACJ,aAAa;cACjB,sBAAsB;cACtB,gBAAgB;SACrB,gBAAgB;;;MAGnB,kBAiJH,CAAC"}
package/dist/index.js CHANGED
@@ -564,6 +564,24 @@ const planError = (message) => {
564
564
  firmwareUpdateCode: 'FirmwarePlanInvalid',
565
565
  });
566
566
  };
567
+ const FIRMWARE_UPDATE_PLAN_FORCE_TARGETS = new Set([
568
+ 'firmware',
569
+ 'ble',
570
+ 'bootloader',
571
+ 'resource',
572
+ ]);
573
+ const validateFirmwareUpdatePlanForceTargets = (value) => {
574
+ if (value === undefined) {
575
+ return [];
576
+ }
577
+ if (!Array.isArray(value) ||
578
+ value.length > FIRMWARE_UPDATE_PLAN_FORCE_TARGETS.size ||
579
+ value.some(target => !FIRMWARE_UPDATE_PLAN_FORCE_TARGETS.has(target)) ||
580
+ new Set(value).size !== value.length) {
581
+ return planError('Firmware update force targets are invalid');
582
+ }
583
+ return [...value];
584
+ };
567
585
  const assertExactKeys = (value, required, optional = []) => {
568
586
  const allowed = new Set([...required, ...optional]);
569
587
  if (required.some(key => !Object.prototype.hasOwnProperty.call(value, key)) ||
@@ -736,24 +754,23 @@ const getExecutor = (features) => {
736
754
  }
737
755
  return 'v2';
738
756
  };
739
- const buildProtocolV2Artifacts = (release) => {
757
+ const buildProtocolV2Artifacts = (release, { includeComponents = true, includeResources = true, } = {}) => {
740
758
  var _a;
741
759
  const components = asRecord(release.components);
742
- if (!components) {
760
+ if (includeComponents && !components) {
743
761
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Protocol V2 release has no component set', { firmwareUpdateCode: 'FirmwarePlanInvalid' });
744
762
  }
745
763
  const installOrder = Array.isArray(release.installOrder)
746
764
  ? release.installOrder.filter((key) => typeof key === 'string')
747
765
  : [];
748
- const componentKeys = [
749
- ...installOrder,
750
- ...Object.keys(components).filter(key => !installOrder.includes(key)),
751
- ];
766
+ const componentKeys = includeComponents && components
767
+ ? [...installOrder, ...Object.keys(components).filter(key => !installOrder.includes(key))]
768
+ : [];
752
769
  const artifacts = [];
753
770
  const targets = [];
754
771
  const componentTargets = new Set();
755
772
  for (const key of componentKeys) {
756
- const component = asRecord(components[key]);
773
+ const component = asRecord(components === null || components === void 0 ? void 0 : components[key]);
757
774
  const targetName = (_a = asString(component === null || component === void 0 ? void 0 : component.target)) === null || _a === void 0 ? void 0 : _a.toUpperCase();
758
775
  if (targetName === 'ROMLOADER') {
759
776
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Protocol V2 ROMLOADER requires its dedicated loader flow', { firmwareUpdateCode: 'FirmwarePlanInvalid' });
@@ -772,7 +789,7 @@ const buildProtocolV2Artifacts = (release) => {
772
789
  })), (asVersion(component.version) ? { targetVersion: asVersion(component.version) } : {})));
773
790
  targets.push(target);
774
791
  }
775
- const bundles = Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
792
+ const bundles = includeResources && Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
776
793
  const resourceBundleNames = new Set();
777
794
  for (const value of bundles) {
778
795
  const bundle = asRecord(value);
@@ -794,17 +811,51 @@ const buildProtocolV2Artifacts = (release) => {
794
811
  }
795
812
  return { artifacts, targets: [...new Set(targets)] };
796
813
  };
814
+ const isForcedTargetRepresented = ({ executor, forcedTarget, artifacts, targetsToUpdate, }) => {
815
+ if (executor !== 'v4') {
816
+ return artifacts.some(artifact => artifact.target === forcedTarget && targetsToUpdate.includes(forcedTarget));
817
+ }
818
+ if (forcedTarget === 'firmware') {
819
+ return artifacts.some(artifact => artifact.role === 'component' && targetsToUpdate.includes(artifact.target));
820
+ }
821
+ if (forcedTarget === 'resource') {
822
+ return artifacts.some(artifact => artifact.role === 'resourceBundle' &&
823
+ artifact.target === 'resource' &&
824
+ targetsToUpdate.includes('resource'));
825
+ }
826
+ return false;
827
+ };
828
+ const assertForcedTargetsRepresented = ({ executor, forcedTargets, artifacts, targetsToUpdate, }) => {
829
+ for (const forcedTarget of forcedTargets) {
830
+ if (!isForcedTargetRepresented({
831
+ executor,
832
+ forcedTarget,
833
+ artifacts,
834
+ targetsToUpdate,
835
+ })) {
836
+ planError(`Forced firmware update target ${forcedTarget} is not represented by the plan`);
837
+ }
838
+ }
839
+ };
797
840
  const buildFirmwareUpdatePlan = ({ features, firmwareType, platform, firmware, ble, bootloader, forceUpdateTargets, }) => {
798
841
  var _a, _b, _c, _d, _e, _f, _g;
799
842
  const executor = getExecutor(features);
800
843
  let artifacts = [];
801
844
  let targetsToUpdate = [];
802
845
  const firmwareRelease = asRelease(firmware);
803
- const forcedTargets = new Set(forceUpdateTargets);
846
+ const validatedForceTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
847
+ const forcedTargets = new Set(validatedForceTargets);
804
848
  const shouldUpdateFirmware = isUpgrade(firmware) || forcedTargets.has('firmware');
805
849
  const shouldUpdateResource = isUpgrade(firmware) || forcedTargets.has('resource');
850
+ if (executor === 'v4' &&
851
+ validatedForceTargets.some(target => target === 'ble' || target === 'bootloader')) {
852
+ planError('Protocol V2 does not support forced BLE or legacy bootloader targets');
853
+ }
806
854
  if (executor === 'v4' && (shouldUpdateFirmware || shouldUpdateResource)) {
807
- const protocolV2 = buildProtocolV2Artifacts(firmwareRelease !== null && firmwareRelease !== void 0 ? firmwareRelease : {});
855
+ const protocolV2 = buildProtocolV2Artifacts(firmwareRelease !== null && firmwareRelease !== void 0 ? firmwareRelease : {}, {
856
+ includeComponents: shouldUpdateFirmware,
857
+ includeResources: shouldUpdateResource,
858
+ });
808
859
  artifacts = protocolV2.artifacts;
809
860
  targetsToUpdate = protocolV2.targets;
810
861
  }
@@ -862,6 +913,12 @@ const buildFirmwareUpdatePlan = ({ features, firmwareType, platform, firmware, b
862
913
  }
863
914
  targetsToUpdate = [...new Set(artifacts.map(artifact => artifact.target))];
864
915
  }
916
+ assertForcedTargetsRepresented({
917
+ executor,
918
+ forcedTargets: validatedForceTargets,
919
+ artifacts,
920
+ targetsToUpdate,
921
+ });
865
922
  const deviceIdentity = getDeviceUUID(features);
866
923
  if (artifacts.length > 0 &&
867
924
  (platform === 'native' || platform === 'desktop') &&
@@ -878,7 +935,7 @@ const buildFirmwareUpdatePlan = ({ features, firmwareType, platform, firmware, b
878
935
  artifacts,
879
936
  targetsToUpdate,
880
937
  };
881
- return Object.assign(Object.assign({}, planWithoutDigest), { planDigest: digestFirmwareUpdatePlan(planWithoutDigest) });
938
+ return assertFirmwareUpdatePlan(Object.assign(Object.assign({}, planWithoutDigest), { planDigest: digestFirmwareUpdatePlan(planWithoutDigest) }));
882
939
  };
883
940
 
884
941
  const preparedPlanError = (message) => {
@@ -46172,6 +46229,7 @@ class CheckAllFirmwareRelease extends BaseMethod {
46172
46229
  return __awaiter(this, void 0, void 0, function* () {
46173
46230
  const { features } = this.device;
46174
46231
  const { checkBridgeRelease, firmwareType: firmwareTypeParams, platform, forceUpdateTargets, } = this.payload;
46232
+ const validatedForceUpdateTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
46175
46233
  if (!features) {
46176
46234
  return Promise.resolve(null);
46177
46235
  }
@@ -46205,11 +46263,12 @@ class CheckAllFirmwareRelease extends BaseMethod {
46205
46263
  firmware: firmwareRelease,
46206
46264
  ble: bleFirmwareReleaseInfo,
46207
46265
  bootloader: bootloaderRelease,
46208
- forceUpdateTargets,
46266
+ forceUpdateTargets: validatedForceUpdateTargets,
46209
46267
  });
46210
46268
  }
46211
46269
  catch (error) {
46212
- if (!(error instanceof hdShared.HardwareError) ||
46270
+ if (validatedForceUpdateTargets.length > 0 ||
46271
+ !(error instanceof hdShared.HardwareError) ||
46213
46272
  ((_c = error.params) === null || _c === void 0 ? void 0 : _c.firmwareUpdateCode) !== 'FirmwarePlanInvalid') {
46214
46273
  throw error;
46215
46274
  }
@@ -48369,6 +48428,14 @@ class FirmwareUpdate extends BaseMethod {
48369
48428
  }
48370
48429
  }
48371
48430
 
48431
+ const normalizeFirmwarePreparationError = (error) => {
48432
+ var _a;
48433
+ if (error instanceof hdShared.HardwareError && ((_a = error.params) === null || _a === void 0 ? void 0 : _a.firmwareUpdateCode)) {
48434
+ return error;
48435
+ }
48436
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.FirmwareUpdateDownloadFailed, error instanceof Error ? error.message : String(error));
48437
+ };
48438
+
48372
48439
  const Log$7 = getLogger(exports.LoggerNames.Method);
48373
48440
  const FIRMWARE_DOWNLOAD_REQUEST_OPTIONS = {
48374
48441
  connectTimeoutMs: 60000,
@@ -48651,7 +48718,6 @@ class FirmwareUpdateV2 extends BaseMethod {
48651
48718
  let preparedSource;
48652
48719
  try {
48653
48720
  const acquireFirmwareSource = () => __awaiter(this, void 0, void 0, function* () {
48654
- var _k;
48655
48721
  try {
48656
48722
  if (preparedSource) {
48657
48723
  return preparedSource;
@@ -48715,7 +48781,7 @@ class FirmwareUpdateV2 extends BaseMethod {
48715
48781
  return source;
48716
48782
  }
48717
48783
  catch (err) {
48718
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.FirmwareUpdateDownloadFailed, (_k = err.message) !== null && _k !== void 0 ? _k : err);
48784
+ throw normalizeFirmwarePreparationError(err);
48719
48785
  }
48720
48786
  });
48721
48787
  if (!device.isBootloader() && features) {
@@ -48916,7 +48982,7 @@ class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod {
48916
48982
  });
48917
48983
  }
48918
48984
  runProtocolV1() {
48919
- var _a, _b, _c;
48985
+ var _a, _b;
48920
48986
  return __awaiter(this, void 0, void 0, function* () {
48921
48987
  const { device } = this;
48922
48988
  const { features } = device;
@@ -48950,7 +49016,7 @@ class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod {
48950
49016
  }
48951
49017
  catch (err) {
48952
49018
  yield this.closeArtifactSources();
48953
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.FirmwareUpdateDownloadFailed, (_c = err.message) !== null && _c !== void 0 ? _c : err);
49019
+ throw normalizeFirmwarePreparationError(err);
48954
49020
  }
48955
49021
  if (!bootloaderSource && fwSources.length === 0) {
48956
49022
  yield this.closeArtifactSources();
@@ -49874,7 +49940,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
49874
49940
  });
49875
49941
  }
49876
49942
  runProtocolV2() {
49877
- var _a, _b, _c;
49943
+ var _a, _b;
49878
49944
  return __awaiter(this, void 0, void 0, function* () {
49879
49945
  yield this.captureProtocolV2PhysicalIdentity();
49880
49946
  const deviceFeatures = yield this.getProtocolV2DeviceFeatures();
@@ -49912,7 +49978,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
49912
49978
  this.postTipMessage(exports.FirmwareUpdateTipMessage.FinishDownloadFirmware);
49913
49979
  }
49914
49980
  catch (err) {
49915
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.FirmwareUpdateDownloadFailed, (_c = err.message) !== null && _c !== void 0 ? _c : err);
49981
+ throw normalizeFirmwarePreparationError(err);
49916
49982
  }
49917
49983
  if (!bootloaderBinary && fwBinaryMap.length === 0 && !(resourceBundles === null || resourceBundles === void 0 ? void 0 : resourceBundles.length)) {
49918
49984
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.FirmwareUpdateDownloadFailed, 'No firmware to update');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.0-alpha.27",
3
+ "version": "1.2.0-alpha.28",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.0-alpha.27",
29
- "@onekeyfe/hd-transport": "1.2.0-alpha.27",
28
+ "@onekeyfe/hd-shared": "1.2.0-alpha.28",
29
+ "@onekeyfe/hd-transport": "1.2.0-alpha.28",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "bytebuffer": "^5.0.1",
@@ -44,5 +44,5 @@
44
44
  "@types/w3c-web-usb": "^1.0.10",
45
45
  "@types/web-bluetooth": "^0.0.21"
46
46
  },
47
- "gitHead": "ea3c188364a6fb968875484805085e4466d3ef99"
47
+ "gitHead": "b7c7f9f53c4d745f127d03e0b173e8b759bf8e15"
48
48
  }
@@ -7,7 +7,10 @@ import {
7
7
  getBootloaderReleaseInfo,
8
8
  getFirmwareReleaseInfo,
9
9
  } from './firmware/releaseHelper';
10
- import { buildFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
10
+ import {
11
+ buildFirmwareUpdatePlan,
12
+ validateFirmwareUpdatePlanForceTargets,
13
+ } from './firmware/FirmwareUpdatePlan';
11
14
  import { getBridgeReleaseInfo } from '../utils/bridgeUpdate';
12
15
  import {
13
16
  LoggerNames,
@@ -40,6 +43,7 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
40
43
  platform,
41
44
  forceUpdateTargets,
42
45
  } = this.payload as CheckAllFirmwareReleaseParams;
46
+ const validatedForceUpdateTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
43
47
 
44
48
  if (!features) {
45
49
  return Promise.resolve(null);
@@ -79,10 +83,11 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
79
83
  firmware: firmwareRelease,
80
84
  ble: bleFirmwareReleaseInfo,
81
85
  bootloader: bootloaderRelease,
82
- forceUpdateTargets,
86
+ forceUpdateTargets: validatedForceUpdateTargets,
83
87
  });
84
88
  } catch (error) {
85
89
  if (
90
+ validatedForceUpdateTargets.length > 0 ||
86
91
  !(error instanceof HardwareError) ||
87
92
  error.params?.firmwareUpdateCode !== 'FirmwarePlanInvalid'
88
93
  ) {
@@ -14,6 +14,7 @@ import { BaseMethod } from './BaseMethod';
14
14
  import { validateParams } from './helpers/paramsValidator';
15
15
  import { DevicePool } from '../device/DevicePool';
16
16
  import { getBinary, getInfo, getSysResourceBinary } from './firmware/getBinary';
17
+ import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
17
18
  import {
18
19
  updateResources,
19
20
  updateResourcesFromSources,
@@ -27,8 +28,8 @@ import { DEVICE } from '../events';
27
28
  import { type FirmwareByteSource, openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
28
29
  import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
29
30
  import {
30
- assertFirmwareUpdatePreparedPlanDeviceIdentity,
31
31
  assertFirmwareUpdatePreparedPlanBinding,
32
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
32
33
  getFirmwareUpdateResourceName,
33
34
  } from './firmware/FirmwareUpdatePreparedPlan';
34
35
 
@@ -503,10 +504,7 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
503
504
  this.postTipMessage('DownloadFirmwareSuccess');
504
505
  return source;
505
506
  } catch (err) {
506
- throw ERRORS.TypedError(
507
- HardwareErrorCode.FirmwareUpdateDownloadFailed,
508
- err.message ?? err
509
- );
507
+ throw normalizeFirmwarePreparationError(err);
510
508
  }
511
509
  };
512
510
 
@@ -13,6 +13,7 @@ import {
13
13
  getLogger,
14
14
  } from '../utils';
15
15
  import { getBinary, getSysResourceBinary } from './firmware/getBinary';
16
+ import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
16
17
  import { DataManager } from '../data-manager';
17
18
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
18
19
  import { DevicePool } from '../device/DevicePool';
@@ -21,8 +22,8 @@ import { buildProtocolV1FeaturesPayload } from '../deviceProfile';
21
22
  import { openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
22
23
  import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
23
24
  import {
24
- assertFirmwareUpdatePreparedPlanDeviceIdentity,
25
25
  assertFirmwareUpdatePreparedPlanBinding,
26
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
26
27
  getFirmwareUpdateResourceName,
27
28
  } from './firmware/FirmwareUpdatePreparedPlan';
28
29
 
@@ -184,7 +185,7 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
184
185
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
185
186
  } catch (err) {
186
187
  await this.closeArtifactSources();
187
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
188
+ throw normalizeFirmwarePreparationError(err);
188
189
  }
189
190
 
190
191
  if (!bootloaderSource && fwSources.length === 0) {
@@ -18,6 +18,7 @@ import {
18
18
  getLogger,
19
19
  } from '../utils';
20
20
  import { getSysResourceBinary } from './firmware/getBinary';
21
+ import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
21
22
  import { DataManager } from '../data-manager';
22
23
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
23
24
  import { DevicePool } from '../device/DevicePool';
@@ -625,7 +626,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
625
626
  : undefined;
626
627
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
627
628
  } catch (err) {
628
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
629
+ throw normalizeFirmwarePreparationError(err);
629
630
  }
630
631
 
631
632
  if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
@@ -0,0 +1,12 @@
1
+ import { ERRORS, HardwareError, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ export const normalizeFirmwarePreparationError = (error: unknown) => {
4
+ if (error instanceof HardwareError && error.params?.firmwareUpdateCode) {
5
+ return error;
6
+ }
7
+
8
+ return ERRORS.TypedError(
9
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
10
+ error instanceof Error ? error.message : String(error)
11
+ );
12
+ };
@@ -166,6 +166,30 @@ const planError = (message: string): never => {
166
166
  });
167
167
  };
168
168
 
169
+ const FIRMWARE_UPDATE_PLAN_FORCE_TARGETS = new Set<FirmwareUpdatePlanForceTarget>([
170
+ 'firmware',
171
+ 'ble',
172
+ 'bootloader',
173
+ 'resource',
174
+ ]);
175
+
176
+ export const validateFirmwareUpdatePlanForceTargets = (
177
+ value: unknown
178
+ ): FirmwareUpdatePlanForceTarget[] => {
179
+ if (value === undefined) {
180
+ return [];
181
+ }
182
+ if (
183
+ !Array.isArray(value) ||
184
+ value.length > FIRMWARE_UPDATE_PLAN_FORCE_TARGETS.size ||
185
+ value.some(target => !FIRMWARE_UPDATE_PLAN_FORCE_TARGETS.has(target)) ||
186
+ new Set(value).size !== value.length
187
+ ) {
188
+ return planError('Firmware update force targets are invalid');
189
+ }
190
+ return [...value] as FirmwareUpdatePlanForceTarget[];
191
+ };
192
+
169
193
  const assertExactKeys = (
170
194
  value: Record<string, unknown>,
171
195
  required: readonly string[],
@@ -388,13 +412,20 @@ const getExecutor = (features: Features): FirmwareUpdatePlan['executor'] => {
388
412
  };
389
413
 
390
414
  const buildProtocolV2Artifacts = (
391
- release: ReleaseRecord
415
+ release: ReleaseRecord,
416
+ {
417
+ includeComponents = true,
418
+ includeResources = true,
419
+ }: {
420
+ includeComponents?: boolean;
421
+ includeResources?: boolean;
422
+ } = {}
392
423
  ): {
393
424
  artifacts: FirmwareUpdatePlanArtifact[];
394
425
  targets: FirmwareUpdatePlanTarget[];
395
426
  } => {
396
427
  const components = asRecord(release.components);
397
- if (!components) {
428
+ if (includeComponents && !components) {
398
429
  throw ERRORS.TypedError(
399
430
  HardwareErrorCode.RuntimeError,
400
431
  'Protocol V2 release has no component set',
@@ -404,15 +435,15 @@ const buildProtocolV2Artifacts = (
404
435
  const installOrder = Array.isArray(release.installOrder)
405
436
  ? release.installOrder.filter((key): key is string => typeof key === 'string')
406
437
  : [];
407
- const componentKeys = [
408
- ...installOrder,
409
- ...Object.keys(components).filter(key => !installOrder.includes(key)),
410
- ];
438
+ const componentKeys =
439
+ includeComponents && components
440
+ ? [...installOrder, ...Object.keys(components).filter(key => !installOrder.includes(key))]
441
+ : [];
411
442
  const artifacts: FirmwareUpdatePlanArtifact[] = [];
412
443
  const targets: FirmwareUpdatePlanTarget[] = [];
413
444
  const componentTargets = new Set<FirmwareUpdatePlanTarget>();
414
445
  for (const key of componentKeys) {
415
- const component = asRecord(components[key]);
446
+ const component = asRecord(components?.[key]);
416
447
  const targetName = asString(component?.target)?.toUpperCase();
417
448
  if (targetName === 'ROMLOADER') {
418
449
  throw ERRORS.TypedError(
@@ -453,7 +484,8 @@ const buildProtocolV2Artifacts = (
453
484
  targets.push(target);
454
485
  }
455
486
 
456
- const bundles = Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
487
+ const bundles =
488
+ includeResources && Array.isArray(release.resourceBundles) ? release.resourceBundles : [];
457
489
  const resourceBundleNames = new Set<string>();
458
490
  for (const value of bundles) {
459
491
  const bundle = asRecord(value);
@@ -492,6 +524,63 @@ const buildProtocolV2Artifacts = (
492
524
  return { artifacts, targets: [...new Set(targets)] };
493
525
  };
494
526
 
527
+ const isForcedTargetRepresented = ({
528
+ executor,
529
+ forcedTarget,
530
+ artifacts,
531
+ targetsToUpdate,
532
+ }: {
533
+ executor: FirmwareUpdatePlan['executor'];
534
+ forcedTarget: FirmwareUpdatePlanForceTarget;
535
+ artifacts: readonly FirmwareUpdatePlanArtifact[];
536
+ targetsToUpdate: readonly FirmwareUpdatePlanTarget[];
537
+ }): boolean => {
538
+ if (executor !== 'v4') {
539
+ return artifacts.some(
540
+ artifact => artifact.target === forcedTarget && targetsToUpdate.includes(forcedTarget)
541
+ );
542
+ }
543
+ if (forcedTarget === 'firmware') {
544
+ return artifacts.some(
545
+ artifact => artifact.role === 'component' && targetsToUpdate.includes(artifact.target)
546
+ );
547
+ }
548
+ if (forcedTarget === 'resource') {
549
+ return artifacts.some(
550
+ artifact =>
551
+ artifact.role === 'resourceBundle' &&
552
+ artifact.target === 'resource' &&
553
+ targetsToUpdate.includes('resource')
554
+ );
555
+ }
556
+ return false;
557
+ };
558
+
559
+ const assertForcedTargetsRepresented = ({
560
+ executor,
561
+ forcedTargets,
562
+ artifacts,
563
+ targetsToUpdate,
564
+ }: {
565
+ executor: FirmwareUpdatePlan['executor'];
566
+ forcedTargets: readonly FirmwareUpdatePlanForceTarget[];
567
+ artifacts: readonly FirmwareUpdatePlanArtifact[];
568
+ targetsToUpdate: readonly FirmwareUpdatePlanTarget[];
569
+ }) => {
570
+ for (const forcedTarget of forcedTargets) {
571
+ if (
572
+ !isForcedTargetRepresented({
573
+ executor,
574
+ forcedTarget,
575
+ artifacts,
576
+ targetsToUpdate,
577
+ })
578
+ ) {
579
+ planError(`Forced firmware update target ${forcedTarget} is not represented by the plan`);
580
+ }
581
+ }
582
+ };
583
+
495
584
  export const buildFirmwareUpdatePlan = ({
496
585
  features,
497
586
  firmwareType,
@@ -513,12 +602,23 @@ export const buildFirmwareUpdatePlan = ({
513
602
  let artifacts: FirmwareUpdatePlanArtifact[] = [];
514
603
  let targetsToUpdate: FirmwareUpdatePlanTarget[] = [];
515
604
  const firmwareRelease = asRelease(firmware);
516
- const forcedTargets = new Set(forceUpdateTargets);
605
+ const validatedForceTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
606
+ const forcedTargets = new Set(validatedForceTargets);
517
607
  const shouldUpdateFirmware = isUpgrade(firmware) || forcedTargets.has('firmware');
518
608
  const shouldUpdateResource = isUpgrade(firmware) || forcedTargets.has('resource');
519
609
 
610
+ if (
611
+ executor === 'v4' &&
612
+ validatedForceTargets.some(target => target === 'ble' || target === 'bootloader')
613
+ ) {
614
+ planError('Protocol V2 does not support forced BLE or legacy bootloader targets');
615
+ }
616
+
520
617
  if (executor === 'v4' && (shouldUpdateFirmware || shouldUpdateResource)) {
521
- const protocolV2 = buildProtocolV2Artifacts(firmwareRelease ?? {});
618
+ const protocolV2 = buildProtocolV2Artifacts(firmwareRelease ?? {}, {
619
+ includeComponents: shouldUpdateFirmware,
620
+ includeResources: shouldUpdateResource,
621
+ });
522
622
  artifacts = protocolV2.artifacts;
523
623
  targetsToUpdate = protocolV2.targets;
524
624
  } else {
@@ -609,6 +709,13 @@ export const buildFirmwareUpdatePlan = ({
609
709
  targetsToUpdate = [...new Set(artifacts.map(artifact => artifact.target))];
610
710
  }
611
711
 
712
+ assertForcedTargetsRepresented({
713
+ executor,
714
+ forcedTargets: validatedForceTargets,
715
+ artifacts,
716
+ targetsToUpdate,
717
+ });
718
+
612
719
  const deviceIdentity = getDeviceUUID(features);
613
720
  if (
614
721
  artifacts.length > 0 &&
@@ -631,8 +738,8 @@ export const buildFirmwareUpdatePlan = ({
631
738
  artifacts,
632
739
  targetsToUpdate,
633
740
  };
634
- return {
741
+ return assertFirmwareUpdatePlan({
635
742
  ...planWithoutDigest,
636
743
  planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
637
- };
744
+ });
638
745
  };