@onekeyfe/hd-core 1.2.0-alpha.81 → 1.2.0-alpha.84

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 (38) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +266 -0
  2. package/__tests__/check-firmware-release-protocol-v2.test.ts +36 -0
  3. package/__tests__/protocol-v2.test.ts +181 -2
  4. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  5. package/dist/api/CheckBLEFirmwareRelease.d.ts +1 -1
  6. package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
  7. package/dist/api/CheckBootloaderRelease.d.ts +1 -1
  8. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  9. package/dist/api/CheckFirmwareRelease.d.ts +1 -1
  10. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  11. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +33 -0
  14. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  15. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  16. package/dist/index.d.ts +4 -3
  17. package/dist/index.js +202 -66
  18. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -0
  19. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  20. package/dist/types/api/checkBLEFirmwareRelease.d.ts +1 -1
  21. package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
  22. package/dist/types/api/checkBootloaderRelease.d.ts +1 -1
  23. package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
  24. package/dist/types/api/checkFirmwareRelease.d.ts +1 -1
  25. package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
  26. package/package.json +4 -4
  27. package/src/api/CheckAllFirmwareRelease.ts +76 -5
  28. package/src/api/CheckBLEFirmwareRelease.ts +4 -10
  29. package/src/api/CheckBootloaderRelease.ts +1 -8
  30. package/src/api/CheckFirmwareRelease.ts +6 -12
  31. package/src/api/FirmwareUpdateV4.ts +61 -21
  32. package/src/api/firmware/FirmwareUpdatePlan.ts +154 -37
  33. package/src/api/firmware/protocolV2Release.ts +2 -0
  34. package/src/types/api/checkAllFirmwareRelease.ts +1 -0
  35. package/src/types/api/checkBLEFirmwareRelease.ts +1 -1
  36. package/src/types/api/checkBootloaderRelease.ts +1 -1
  37. package/src/types/api/checkFirmwareRelease.ts +1 -1
  38. package/src/utils/deviceFeaturesUtils.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.0-alpha.81",
3
+ "version": "1.2.0-alpha.84",
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.81",
29
- "@onekeyfe/hd-transport": "1.2.0-alpha.81",
28
+ "@onekeyfe/hd-shared": "1.2.0-alpha.84",
29
+ "@onekeyfe/hd-transport": "1.2.0-alpha.84",
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": "8f6b4dca4ab97dc0a83f7ace61c24a0f0e193016"
47
+ "gitHead": "577746142ef64d2c97de487c7a9cc8a5a0422e0b"
48
48
  }
@@ -11,7 +11,9 @@ import {
11
11
  } from './firmware/releaseHelper';
12
12
  import {
13
13
  buildFirmwareUpdatePlan,
14
+ buildProtocolV2FirmwareUpdatePlan,
14
15
  validateFirmwareUpdatePlanForceTargets,
16
+ validateProtocolV2FirmwareUpdateTargets,
15
17
  } from './firmware/FirmwareUpdatePlan';
16
18
  import {
17
19
  PROTOCOL_V2_BLE_TARGETS,
@@ -26,6 +28,7 @@ import { getBridgeReleaseInfo } from '../utils/bridgeUpdate';
26
28
  import {
27
29
  LoggerNames,
28
30
  getDeviceFirmwareVersion,
31
+ getDeviceSerialNo,
29
32
  getDeviceType,
30
33
  getFirmwareType,
31
34
  getLogger,
@@ -384,8 +387,17 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
384
387
  }
385
388
 
386
389
  private async runProtocolV2(): Promise<AllFirmwareRelease> {
387
- const { checkFirmwareHash = false, firmwareType: firmwareTypeParam } = this
388
- .payload as CheckAllFirmwareReleaseParams;
390
+ const {
391
+ checkFirmwareHash = false,
392
+ firmwareType: firmwareTypeParam,
393
+ platform,
394
+ forceUpdateTargets,
395
+ protocolV2ForceUpdateTargets,
396
+ } = this.payload as CheckAllFirmwareReleaseParams;
397
+ const validatedForceUpdateTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
398
+ const validatedProtocolV2ForceUpdateTargets = validateProtocolV2FirmwareUpdateTargets(
399
+ protocolV2ForceUpdateTargets
400
+ );
389
401
  const { state, features, firmwareType, release } = await loadProtocolV2FirmwareReleaseContext({
390
402
  device: this.device,
391
403
  firmwareType: firmwareTypeParam,
@@ -405,12 +417,70 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
405
417
  const resourceSource = DataManager.getProtocolV2ResourceSource(resourceDeviceType);
406
418
  const resourceStatus = 'unknown' as const;
407
419
  const resourcePreparationRequired = !!resourceSource;
420
+ const detectedComponentTargets = validatedForceUpdateTargets.includes('firmware')
421
+ ? plan.components.flatMap(component =>
422
+ component.updateTarget ? [component.updateTarget] : []
423
+ )
424
+ : plan.targetsToUpdate;
425
+ const componentTargetsToUpdate = Array.from(
426
+ new Set([
427
+ ...detectedComponentTargets,
428
+ ...validatedProtocolV2ForceUpdateTargets.filter(target => target !== 'resource'),
429
+ ])
430
+ );
431
+ const forceResourceUpdate =
432
+ validatedForceUpdateTargets.includes('resource') ||
433
+ validatedProtocolV2ForceUpdateTargets.includes('resource');
408
434
  const targetsToUpdate = Array.from(
409
435
  new Set([
410
- ...plan.targetsToUpdate,
411
- ...(resourcePreparationRequired ? (['resource'] as const) : []),
436
+ ...componentTargetsToUpdate,
437
+ ...(resourcePreparationRequired || forceResourceUpdate ? (['resource'] as const) : []),
412
438
  ])
413
439
  );
440
+ let firmwareUpdatePlan: FirmwareUpdatePlan | undefined;
441
+ try {
442
+ const shouldBuildFirmwareUpdatePlan =
443
+ componentTargetsToUpdate.length > 0 ||
444
+ validatedForceUpdateTargets.some(target => target !== 'resource') ||
445
+ validatedProtocolV2ForceUpdateTargets.some(target => target !== 'resource') ||
446
+ (forceResourceUpdate && !resourcePreparationRequired);
447
+ const requestedPlatform = platform ?? 'web';
448
+ const requiresDeviceIdentity =
449
+ requestedPlatform === 'native' || requestedPlatform === 'desktop';
450
+ const canBindPreparedPlan = !requiresDeviceIdentity || !!getDeviceSerialNo(features);
451
+ if (shouldBuildFirmwareUpdatePlan) {
452
+ const validatedPlan = buildProtocolV2FirmwareUpdatePlan({
453
+ features,
454
+ firmwareType,
455
+ platform: canBindPreparedPlan ? requestedPlatform : 'web',
456
+ release,
457
+ targetsToUpdate,
458
+ forceUpdateTargets: validatedForceUpdateTargets,
459
+ resourceArchiveAvailable: resourcePreparationRequired,
460
+ });
461
+ firmwareUpdatePlan = canBindPreparedPlan ? validatedPlan : undefined;
462
+ if (!canBindPreparedPlan) {
463
+ Log.warn(
464
+ '[CheckAllFirmwareRelease] Protocol V2 device identity is unavailable; using the release result without a prepared Plan'
465
+ );
466
+ }
467
+ } else {
468
+ firmwareUpdatePlan = undefined;
469
+ }
470
+ } catch (error) {
471
+ if (
472
+ validatedForceUpdateTargets.length > 0 ||
473
+ validatedProtocolV2ForceUpdateTargets.length > 0 ||
474
+ !(error instanceof HardwareError) ||
475
+ error.params?.firmwareUpdateCode !== 'FirmwarePlanInvalid'
476
+ ) {
477
+ throw error;
478
+ }
479
+ Log.warn(
480
+ '[CheckAllFirmwareRelease] Optional Protocol V2 firmware Plan is unavailable; using the release result'
481
+ );
482
+ firmwareUpdatePlan = undefined;
483
+ }
414
484
  const firmwarePlan = summarizeProtocolV2FirmwareRelease(
415
485
  plan,
416
486
  PROTOCOL_V2_MAIN_FIRMWARE_TARGETS
@@ -442,8 +512,9 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
442
512
  resourceStatus,
443
513
  resourceArchive: resourceSource,
444
514
  resourcePreparationRequired,
445
- hasUpgrade: plan.hasUpgrade || resourcePreparationRequired,
515
+ hasUpgrade: targetsToUpdate.length > 0,
446
516
  targetsToUpdate,
517
+ firmwareUpdatePlan,
447
518
  };
448
519
  }
449
520
  }
@@ -1,5 +1,3 @@
1
- import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
-
3
1
  import { BaseMethod } from './BaseMethod';
4
2
  import { UI_REQUEST } from '../constants/ui-request';
5
3
  import { getBleFirmwareReleaseInfo } from './firmware/releaseHelper';
@@ -28,6 +26,8 @@ export default class CheckBLEFirmwareRelease extends BaseMethod {
28
26
  }
29
27
 
30
28
  async run() {
29
+ if (!this.device.features) return null;
30
+
31
31
  if (this.device.isProtocolV2()) {
32
32
  const { state, firmwareType, release } = await loadProtocolV2FirmwareReleaseContext({
33
33
  device: this.device,
@@ -49,13 +49,7 @@ export default class CheckBLEFirmwareRelease extends BaseMethod {
49
49
  });
50
50
  }
51
51
 
52
- if (this.device.features) {
53
- const releaseInfo = getBleFirmwareReleaseInfo(this.device.features);
54
- return releaseInfo;
55
- }
56
- throw ERRORS.TypedError(
57
- HardwareErrorCode.RuntimeError,
58
- 'checkBLEFirmwareRelease requires initialized device features'
59
- );
52
+ const releaseInfo = getBleFirmwareReleaseInfo(this.device.features);
53
+ return releaseInfo;
60
54
  }
61
55
  }
@@ -1,5 +1,3 @@
1
- import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
-
3
1
  import { BaseMethod } from './BaseMethod';
4
2
  import { UI_REQUEST } from '../constants/ui-request';
5
3
  import { getBootloaderReleaseInfo } from './firmware/releaseHelper';
@@ -26,12 +24,7 @@ export default class CheckBootloaderRelease extends BaseMethod {
26
24
  }
27
25
 
28
26
  async run() {
29
- if (!this.device.features) {
30
- throw ERRORS.TypedError(
31
- HardwareErrorCode.RuntimeError,
32
- 'checkBootloaderRelease requires initialized device features'
33
- );
34
- }
27
+ if (!this.device.features) return null;
35
28
  const { features } = this.device;
36
29
  const payload = this.payload as CheckBootloaderReleaseParams;
37
30
 
@@ -1,5 +1,3 @@
1
- import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
-
3
1
  import { BaseMethod } from './BaseMethod';
4
2
  import { UI_REQUEST } from '../constants/ui-request';
5
3
  import { getFirmwareReleaseInfo } from './firmware/releaseHelper';
@@ -31,6 +29,8 @@ export default class CheckFirmwareRelease extends BaseMethod {
31
29
  async run() {
32
30
  const payload = this.payload as CheckFirmwareReleaseParams;
33
31
 
32
+ if (!this.device.features) return null;
33
+
34
34
  if (this.device.isProtocolV2()) {
35
35
  const { state, firmwareType, release } = await loadProtocolV2FirmwareReleaseContext({
36
36
  device: this.device,
@@ -49,16 +49,10 @@ export default class CheckFirmwareRelease extends BaseMethod {
49
49
  return toProtocolV2FirmwareReleaseInfo({ plan, state, release });
50
50
  }
51
51
 
52
- if (this.device.features) {
53
- const deviceFirmwareType = this.device.getCurrentFirmwareType();
54
- const firmwareType = payload.firmwareType ?? deviceFirmwareType;
52
+ const deviceFirmwareType = this.device.getCurrentFirmwareType();
53
+ const firmwareType = payload.firmwareType ?? deviceFirmwareType;
55
54
 
56
- const releaseInfo = getFirmwareReleaseInfo(this.device.features, firmwareType);
57
- return releaseInfo;
58
- }
59
- throw ERRORS.TypedError(
60
- HardwareErrorCode.RuntimeError,
61
- 'checkFirmwareRelease requires initialized device features'
62
- );
55
+ const releaseInfo = getFirmwareReleaseInfo(this.device.features, firmwareType);
56
+ return releaseInfo;
63
57
  }
64
58
  }
@@ -683,7 +683,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
683
683
  resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
684
684
  fwBinaryMap = this.collectExplicitTargetBinaries();
685
685
  bootloaderBinary = this.prepareBootloaderBinary();
686
- const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
686
+ const explicitInstallItems = this.buildProtocolV2InstallItems({
687
+ bootloaderBinary,
688
+ fwBinaryMap,
689
+ });
690
+ const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
691
+ const needsRemoteFirmware = this.params.targetsToUpdate?.length
692
+ ? missingFirmwareTargets.length > 0
693
+ : !this.hasExplicitProtocolV2Payload(explicitInstallItems);
687
694
  if (needsPreparedResources) {
688
695
  throw ERRORS.TypedError(
689
696
  HardwareErrorCode.RuntimeError,
@@ -713,7 +720,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
713
720
  if (needsRemoteFirmware) {
714
721
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
715
722
  firmwareType,
716
- deviceFeatures
723
+ deviceFeatures,
724
+ explicitInstallItems
717
725
  );
718
726
  bootloaderBinary = remoteBinaries.bootloaderBinary;
719
727
  fwBinaryMap = remoteBinaries.fwBinaryMap;
@@ -1110,11 +1118,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1110
1118
  return this.params.bootloaderBinary ?? null;
1111
1119
  }
1112
1120
 
1113
- private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
1114
- return (
1115
- !!this.params.resourceFiles?.length ||
1116
- !!this.params.bootloaderBinary ||
1117
- fwBinaryMap.length > 0
1121
+ private hasExplicitProtocolV2Payload(installItems: ProtocolV2InstallItem[]) {
1122
+ return !!this.params.resourceFiles?.length || installItems.length > 0;
1123
+ }
1124
+
1125
+ private getMissingProtocolV2FirmwareTargets(installItems: ProtocolV2InstallItem[]) {
1126
+ const preparedTargets = new Set(
1127
+ installItems.flatMap(item => {
1128
+ const target = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(item.targetId);
1129
+ return target ? [target] : [];
1130
+ })
1131
+ );
1132
+ return (this.params.targetsToUpdate ?? []).filter(
1133
+ target => target !== 'resource' && !preparedTargets.has(target)
1118
1134
  );
1119
1135
  }
1120
1136
 
@@ -1210,7 +1226,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1210
1226
  };
1211
1227
  }
1212
1228
 
1213
- private async prepareRemoteProtocolV2Binaries(firmwareType: EFirmwareType, features: Features) {
1229
+ private async prepareRemoteProtocolV2Binaries(
1230
+ firmwareType: EFirmwareType,
1231
+ features: Features,
1232
+ explicitInstallItems: ProtocolV2InstallItem[] = []
1233
+ ) {
1214
1234
  const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
1215
1235
 
1216
1236
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -1218,6 +1238,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1218
1238
  const installItems: ProtocolV2InstallItem[] = [];
1219
1239
 
1220
1240
  if (!release) {
1241
+ const missingFirmwareTargets = this.getMissingProtocolV2FirmwareTargets(explicitInstallItems);
1242
+ if (missingFirmwareTargets.length > 0) {
1243
+ throw ERRORS.TypedError(
1244
+ HardwareErrorCode.RuntimeError,
1245
+ `Protocol V2 firmware release is unavailable for requested targets: ${missingFirmwareTargets.join(
1246
+ ', '
1247
+ )}`
1248
+ );
1249
+ }
1221
1250
  return {
1222
1251
  bootloaderBinary,
1223
1252
  fwBinaryMap,
@@ -1227,6 +1256,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1227
1256
 
1228
1257
  const entries = this.getRemoteComponentEntries(release);
1229
1258
  const targetsToUpdate = new Set(this.params.targetsToUpdate ?? []);
1259
+ const explicitInstallItemByTargetId = new Map(
1260
+ explicitInstallItems.map(item => [item.targetId, item] as const)
1261
+ );
1262
+ const preparedTargets = new Set<FirmwareUpdateV4Target>();
1230
1263
 
1231
1264
  for (const [key, component] of entries) {
1232
1265
  const targetName = component.target?.toUpperCase();
@@ -1235,27 +1268,34 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1235
1268
  ? PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId)
1236
1269
  : undefined;
1237
1270
  if (updateTarget && targetsToUpdate.has(updateTarget)) {
1238
- const remoteBinary = await this.downloadRemoteProtocolV2Component(key, component);
1239
- if (remoteBinary.kind === 'bootloader') {
1240
- bootloaderBinary = remoteBinary.binary;
1241
- installItems.push({
1242
- fileName: remoteBinary.fileName,
1243
- binary: remoteBinary.binary,
1244
- targetId: remoteBinary.targetId,
1245
- kind: remoteBinary.kind,
1246
- });
1271
+ const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
1272
+ const installItem =
1273
+ explicitInstallItem ?? (await this.downloadRemoteProtocolV2Component(key, component));
1274
+ if (installItem.kind === 'bootloader') {
1275
+ bootloaderBinary = installItem.binary;
1247
1276
  } else {
1248
1277
  const binaryEntry = {
1249
- fileName: remoteBinary.fileName,
1250
- binary: remoteBinary.binary,
1251
- targetId: remoteBinary.targetId,
1278
+ fileName: installItem.fileName,
1279
+ binary: installItem.binary,
1280
+ targetId: installItem.targetId,
1252
1281
  };
1253
1282
  fwBinaryMap.push(binaryEntry);
1254
- installItems.push({ ...binaryEntry, kind: remoteBinary.kind });
1255
1283
  }
1284
+ installItems.push({ ...installItem });
1285
+ preparedTargets.add(updateTarget);
1256
1286
  }
1257
1287
  }
1258
1288
 
1289
+ const missingTarget = Array.from(targetsToUpdate).find(
1290
+ target => target !== 'resource' && !preparedTargets.has(target)
1291
+ );
1292
+ if (missingTarget) {
1293
+ throw ERRORS.TypedError(
1294
+ HardwareErrorCode.RuntimeError,
1295
+ `Protocol V2 release does not contain requested target ${missingTarget}`
1296
+ );
1297
+ }
1298
+
1259
1299
  return {
1260
1300
  bootloaderBinary,
1261
1301
  fwBinaryMap,
@@ -10,6 +10,7 @@ import {
10
10
  getDeviceFirmwareVersion,
11
11
  } from '../../utils/deviceVersionUtils';
12
12
 
13
+ import type { FirmwareUpdateV4Target } from '../../types/api/firmwareUpdate';
13
14
  import type {
14
15
  FirmwareUpdatePlan,
15
16
  FirmwareUpdatePlanArtifact,
@@ -62,6 +63,11 @@ const PROTOCOL_V2_TARGETS: Readonly<
62
63
  SE04: 'se04',
63
64
  };
64
65
 
66
+ const PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS = new Set<FirmwareUpdateV4Target>([
67
+ ...Object.values(PROTOCOL_V2_TARGETS),
68
+ 'resource',
69
+ ]);
70
+
65
71
  const asRecord = (value: unknown): Record<string, unknown> | undefined =>
66
72
  value && typeof value === 'object' && !Array.isArray(value)
67
73
  ? (value as Record<string, unknown>)
@@ -191,6 +197,23 @@ export const validateFirmwareUpdatePlanForceTargets = (
191
197
  return [...value] as FirmwareUpdatePlanForceTarget[];
192
198
  };
193
199
 
200
+ export const validateProtocolV2FirmwareUpdateTargets = (
201
+ value: unknown
202
+ ): FirmwareUpdateV4Target[] => {
203
+ if (value === undefined) {
204
+ return [];
205
+ }
206
+ if (
207
+ !Array.isArray(value) ||
208
+ value.length > PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.size ||
209
+ value.some(target => !PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.has(target)) ||
210
+ new Set(value).size !== value.length
211
+ ) {
212
+ return planError('Protocol V2 firmware update targets are invalid');
213
+ }
214
+ return [...value] as FirmwareUpdateV4Target[];
215
+ };
216
+
194
217
  const assertExactKeys = (
195
218
  value: Record<string, unknown>,
196
219
  required: readonly string[],
@@ -405,7 +428,7 @@ const selectResourceUrl = ({
405
428
 
406
429
  const getExecutor = (features: Features): FirmwareUpdatePlan['executor'] => {
407
430
  const deviceType = getDeviceType(features);
408
- if (deviceType === EDeviceType.Pro2) {
431
+ if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) {
409
432
  return 'v4';
410
433
  }
411
434
  if (
@@ -422,9 +445,11 @@ const buildProtocolV2Artifacts = (
422
445
  {
423
446
  includeComponents = true,
424
447
  includeResources = true,
448
+ componentTargets: selectedComponentTargets,
425
449
  }: {
426
450
  includeComponents?: boolean;
427
451
  includeResources?: boolean;
452
+ componentTargets?: ReadonlySet<FirmwareUpdatePlanTarget>;
428
453
  } = {}
429
454
  ): {
430
455
  artifacts: FirmwareUpdatePlanArtifact[];
@@ -445,12 +470,21 @@ const buildProtocolV2Artifacts = (
445
470
  includeComponents && components
446
471
  ? [...installOrder, ...Object.keys(components).filter(key => !installOrder.includes(key))]
447
472
  : [];
473
+ const selectedComponentKeys = selectedComponentTargets
474
+ ? componentKeys.filter(key => {
475
+ const component = asRecord(components?.[key]);
476
+ const targetName = asString(component?.target)?.toUpperCase();
477
+ const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
478
+ return !!target && selectedComponentTargets.has(target);
479
+ })
480
+ : componentKeys;
448
481
  const artifacts: FirmwareUpdatePlanArtifact[] = [];
449
482
  const targets: FirmwareUpdatePlanTarget[] = [];
450
- const componentTargets = new Set<FirmwareUpdatePlanTarget>();
451
- for (const key of componentKeys) {
483
+ const componentTargetSet = new Set<FirmwareUpdatePlanTarget>();
484
+ for (const key of selectedComponentKeys) {
452
485
  const component = asRecord(components?.[key]);
453
486
  const targetName = asString(component?.target)?.toUpperCase();
487
+ const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
454
488
  if (targetName === 'ROMLOADER') {
455
489
  throw ERRORS.TypedError(
456
490
  HardwareErrorCode.RuntimeError,
@@ -458,7 +492,6 @@ const buildProtocolV2Artifacts = (
458
492
  { firmwareUpdateCode: 'FirmwarePlanInvalid' }
459
493
  );
460
494
  }
461
- const target = targetName ? PROTOCOL_V2_TARGETS[targetName] : undefined;
462
495
  if (!component || !target) {
463
496
  throw ERRORS.TypedError(
464
497
  HardwareErrorCode.RuntimeError,
@@ -466,14 +499,14 @@ const buildProtocolV2Artifacts = (
466
499
  { firmwareUpdateCode: 'FirmwarePlanInvalid' }
467
500
  );
468
501
  }
469
- if (componentTargets.has(target)) {
502
+ if (componentTargetSet.has(target)) {
470
503
  throw ERRORS.TypedError(
471
504
  HardwareErrorCode.RuntimeError,
472
505
  `Protocol V2 component ${key} duplicates target ${target}`,
473
506
  { firmwareUpdateCode: 'FirmwarePlanInvalid' }
474
507
  );
475
508
  }
476
- componentTargets.add(target);
509
+ componentTargetSet.add(target);
477
510
  artifacts.push({
478
511
  artifactId: `component:${target}`,
479
512
  role: 'component',
@@ -587,6 +620,119 @@ const assertForcedTargetsRepresented = ({
587
620
  }
588
621
  };
589
622
 
623
+ const finalizeFirmwareUpdatePlan = ({
624
+ features,
625
+ firmwareType,
626
+ platform,
627
+ artifacts,
628
+ targetsToUpdate,
629
+ }: {
630
+ features: Features;
631
+ firmwareType: EFirmwareType;
632
+ platform: FirmwareUpdatePlatform;
633
+ artifacts: FirmwareUpdatePlanArtifact[];
634
+ targetsToUpdate: FirmwareUpdatePlanTarget[];
635
+ }): FirmwareUpdatePlan => {
636
+ const executor = getExecutor(features);
637
+ const bootloaderMode = resolveDeviceBootloaderMode(features);
638
+ // 'unavailable' is the reserved degraded-identity sentinel; a device-reported
639
+ // serial must never be able to collide with it.
640
+ const reportedIdentity = getDeviceUUID(features);
641
+ const deviceIdentity = reportedIdentity === 'unavailable' ? '' : reportedIdentity;
642
+ // Bootloader-mode classic-family devices (executor v2, e.g. classic1s) cannot report
643
+ // a serial number, so their recovery plans fall back to the degraded 'unavailable'
644
+ // identity. Binding is then enforced against the live device at install time instead
645
+ // (assertFirmwareUpdatePreparedPlanDeviceIdentity), and plan integrity by the digest.
646
+ // Pro (v3), Pro2, and Neo (v4) report a serial even in loader mode and stay strict.
647
+ if (
648
+ artifacts.length > 0 &&
649
+ (platform === 'native' || platform === 'desktop') &&
650
+ !deviceIdentity &&
651
+ !(bootloaderMode && executor === 'v2')
652
+ ) {
653
+ throw ERRORS.TypedError(
654
+ HardwareErrorCode.RuntimeError,
655
+ 'Prepared firmware update requires a stable device identity',
656
+ { firmwareUpdateCode: 'FirmwarePlanInvalid' }
657
+ );
658
+ }
659
+ const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
660
+ schemaVersion: 2,
661
+ executor,
662
+ deviceIdentity: deviceIdentity || 'unavailable',
663
+ deviceModel: String(getDeviceType(features)),
664
+ firmwareType,
665
+ platform,
666
+ artifacts,
667
+ targetsToUpdate,
668
+ };
669
+ return assertFirmwareUpdatePlan({
670
+ ...planWithoutDigest,
671
+ planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
672
+ });
673
+ };
674
+
675
+ export const buildProtocolV2FirmwareUpdatePlan = ({
676
+ features,
677
+ firmwareType,
678
+ platform,
679
+ release,
680
+ targetsToUpdate,
681
+ forceUpdateTargets,
682
+ resourceArchiveAvailable,
683
+ }: {
684
+ features: Features;
685
+ firmwareType: EFirmwareType;
686
+ platform: FirmwareUpdatePlatform;
687
+ release: ReleaseRecord | undefined;
688
+ targetsToUpdate: readonly FirmwareUpdateV4Target[];
689
+ forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
690
+ resourceArchiveAvailable: boolean;
691
+ }): FirmwareUpdatePlan => {
692
+ const validatedForceTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
693
+ if (validatedForceTargets.some(target => target === 'ble' || target === 'bootloader')) {
694
+ planError('Protocol V2 does not support forced BLE or legacy bootloader targets');
695
+ }
696
+
697
+ const requestedComponentTargets = new Set<FirmwareUpdatePlanTarget>(
698
+ targetsToUpdate.filter(target => target !== 'resource')
699
+ );
700
+ if (
701
+ getDeviceType(features) === EDeviceType.Neo &&
702
+ (requestedComponentTargets.has('se03') || requestedComponentTargets.has('se04'))
703
+ ) {
704
+ planError('Neo does not support Protocol V2 targets se03 or se04');
705
+ }
706
+ const protocolV2 = buildProtocolV2Artifacts(release ?? {}, {
707
+ includeComponents: requestedComponentTargets.size > 0,
708
+ includeResources: false,
709
+ componentTargets: requestedComponentTargets,
710
+ });
711
+ const representedTargets = new Set(protocolV2.targets);
712
+ const missingTarget = Array.from(requestedComponentTargets).find(
713
+ target => !representedTargets.has(target)
714
+ );
715
+ if (missingTarget) {
716
+ planError(`Protocol V2 release does not contain requested target ${missingTarget}`);
717
+ }
718
+ if (validatedForceTargets.includes('firmware') && protocolV2.targets.length === 0) {
719
+ planError('Forced firmware update target firmware is not represented by the plan');
720
+ }
721
+ if (validatedForceTargets.includes('resource') && !resourceArchiveAvailable) {
722
+ planError('Forced firmware update target resource has no Protocol V2 resource archive');
723
+ }
724
+
725
+ // Protocol V2 resource archives are materialized by the host into explicit
726
+ // resourceFiles. The prepared artifact plan therefore binds firmware components only.
727
+ return finalizeFirmwareUpdatePlan({
728
+ features,
729
+ firmwareType,
730
+ platform,
731
+ artifacts: protocolV2.artifacts,
732
+ targetsToUpdate: protocolV2.targets,
733
+ });
734
+ };
735
+
590
736
  export const buildFirmwareUpdatePlan = ({
591
737
  features,
592
738
  firmwareType,
@@ -733,40 +879,11 @@ export const buildFirmwareUpdatePlan = ({
733
879
  artifacts,
734
880
  targetsToUpdate,
735
881
  });
736
-
737
- // 'unavailable' is the reserved degraded-identity sentinel; a device-reported
738
- // serial must never be able to collide with it.
739
- const reportedIdentity = getDeviceUUID(features);
740
- const deviceIdentity = reportedIdentity === 'unavailable' ? '' : reportedIdentity;
741
- // Bootloader-mode classic-family devices (executor v2, e.g. classic1s) cannot report
742
- // a serial number, so their recovery plans fall back to the degraded 'unavailable'
743
- // identity. Binding is then enforced against the live device at install time instead
744
- // (assertFirmwareUpdatePreparedPlanDeviceIdentity), and plan integrity by the digest.
745
- // Pro (v3) and Pro2 (v4) report a serial even in bootloader mode and stay strict.
746
- if (
747
- artifacts.length > 0 &&
748
- (platform === 'native' || platform === 'desktop') &&
749
- !deviceIdentity &&
750
- !(bootloaderMode && executor === 'v2')
751
- ) {
752
- throw ERRORS.TypedError(
753
- HardwareErrorCode.RuntimeError,
754
- 'Prepared firmware update requires a stable device identity',
755
- { firmwareUpdateCode: 'FirmwarePlanInvalid' }
756
- );
757
- }
758
- const planWithoutDigest: Omit<FirmwareUpdatePlan, 'planDigest'> = {
759
- schemaVersion: 2,
760
- executor,
761
- deviceIdentity: deviceIdentity || 'unavailable',
762
- deviceModel: String(getDeviceType(features)),
882
+ return finalizeFirmwareUpdatePlan({
883
+ features,
763
884
  firmwareType,
764
885
  platform,
765
886
  artifacts,
766
887
  targetsToUpdate,
767
- };
768
- return assertFirmwareUpdatePlan({
769
- ...planWithoutDigest,
770
- planDigest: digestFirmwareUpdatePlan(planWithoutDigest),
771
888
  });
772
889
  };
@@ -135,6 +135,8 @@ export function toProtocolV2FirmwareReleaseInfo({
135
135
  return {
136
136
  shouldUpdate: plan.hasUpgrade,
137
137
  status: plan.status === 'unavailable' ? 'unknown' : plan.status,
138
+ // Protocol V2 components share package-set release notes. The optional
139
+ // bootloaderChangelog field belongs to the legacy Protocol V1 release model.
138
140
  changelog: plan.release ? [plan.release.changelog] : [],
139
141
  release,
140
142
  bootloaderMode: state.status.mode === 'bootloader' || state.status.mode === 'romloader',
@@ -89,6 +89,7 @@ export type CheckAllFirmwareReleaseParams = {
89
89
  firmwareType?: EFirmwareType;
90
90
  platform?: 'native' | 'desktop' | 'ext' | 'web' | 'web-embed';
91
91
  forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
92
+ protocolV2ForceUpdateTargets?: FirmwareUpdateV4Target[];
92
93
  };
93
94
 
94
95
  export declare function checkAllFirmwareRelease(
@@ -3,4 +3,4 @@ import type { FirmwareReleaseCheckResult } from './checkAllFirmwareRelease';
3
3
 
4
4
  export declare function checkBLEFirmwareRelease(
5
5
  connectId?: string
6
- ): Response<FirmwareReleaseCheckResult>;
6
+ ): Response<FirmwareReleaseCheckResult | null>;
@@ -2,7 +2,7 @@ import type { EFirmwareType } from '@onekeyfe/hd-shared';
2
2
  import type { CommonParams, Response } from '../params';
3
3
  import type { FirmwareReleaseCheckResult } from './checkAllFirmwareRelease';
4
4
 
5
- export type CheckBootloaderReleaseResponse = FirmwareReleaseCheckResult;
5
+ export type CheckBootloaderReleaseResponse = FirmwareReleaseCheckResult | null;
6
6
 
7
7
  export type CheckBootloaderReleaseParams = {
8
8
  willUpdateFirmwareVersion?: string;