@onekeyfe/hd-core 1.2.0-alpha.69 → 1.2.0-alpha.70

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 (121) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -28
  2. package/__tests__/connectSettings.test.ts +45 -5
  3. package/__tests__/device-initialize-timeout.test.ts +56 -0
  4. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  5. package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
  6. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  7. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  8. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  9. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
  10. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  11. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
  12. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  13. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
  14. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  15. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +63 -0
  16. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  17. package/__tests__/protocol-v2-firmware-targets.test.ts +1 -33
  18. package/__tests__/protocol-v2-resources.test.ts +6 -9
  19. package/__tests__/protocol-v2.test.ts +383 -174
  20. package/__tests__/protocolV2FileWrite.test.ts +7 -48
  21. package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
  22. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  23. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV2.d.ts +10 -0
  25. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  27. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  28. package/dist/api/FirmwareUpdateV4.d.ts +25 -6
  29. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  30. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  32. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  33. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  34. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  35. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  36. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  37. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  38. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
  39. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  40. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  41. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  42. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  43. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  44. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
  45. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  46. package/dist/api/firmware/getBinary.d.ts +7 -0
  47. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  48. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  49. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  50. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  51. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  52. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  53. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  54. package/dist/data-manager/DataManager.d.ts +1 -0
  55. package/dist/data-manager/DataManager.d.ts.map +1 -1
  56. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  57. package/dist/device/Device.d.ts +1 -0
  58. package/dist/device/Device.d.ts.map +1 -1
  59. package/dist/index.d.ts +180 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +3038 -1041
  62. package/dist/inject.d.ts.map +1 -1
  63. package/dist/lowLevelInject.d.ts.map +1 -1
  64. package/dist/protocols/protocol-v2/resources.d.ts +1 -1
  65. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  66. package/dist/topLevelInject.d.ts.map +1 -1
  67. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
  68. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  69. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  70. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  71. package/dist/types/api/export.d.ts +4 -1
  72. package/dist/types/api/export.d.ts.map +1 -1
  73. package/dist/types/api/firmwareUpdate.d.ts +68 -0
  74. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  75. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  76. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  77. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  78. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  79. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  80. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  81. package/dist/types/api/index.d.ts +9 -1
  82. package/dist/types/api/index.d.ts.map +1 -1
  83. package/dist/types/settings.d.ts +14 -1
  84. package/dist/types/settings.d.ts.map +1 -1
  85. package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
  86. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  87. package/package.json +4 -4
  88. package/src/api/CheckAllFirmwareRelease.ts +61 -25
  89. package/src/api/FirmwareUpdate.ts +13 -1
  90. package/src/api/FirmwareUpdateV2.ts +296 -121
  91. package/src/api/FirmwareUpdateV3.ts +263 -57
  92. package/src/api/FirmwareUpdateV4.ts +865 -287
  93. package/src/api/device/DeviceUpdateBootloader.ts +125 -6
  94. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  95. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  96. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  97. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
  98. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  99. package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
  100. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
  101. package/src/api/firmware/updateBootloader.ts +19 -4
  102. package/src/api/firmware/uploadFirmware.ts +140 -22
  103. package/src/api/helpers/protocolV2FileWrite.ts +4 -54
  104. package/src/data-manager/DataManager.ts +62 -54
  105. package/src/data-manager/connectSettings.ts +11 -0
  106. package/src/device/Device.ts +6 -1
  107. package/src/index.ts +5 -0
  108. package/src/inject.ts +22 -2
  109. package/src/lowLevelInject.ts +5 -1
  110. package/src/protocols/protocol-v2/resources.ts +1 -5
  111. package/src/topLevelInject.ts +5 -1
  112. package/src/types/api/checkAllFirmwareRelease.ts +4 -0
  113. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  114. package/src/types/api/export.ts +18 -0
  115. package/src/types/api/firmwareUpdate.ts +75 -0
  116. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  117. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  118. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  119. package/src/types/api/index.ts +14 -0
  120. package/src/types/settings.ts +14 -2
  121. package/src/utils/deviceFeaturesCompat.ts +6 -0
@@ -14,18 +14,45 @@ 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 { updateResources, uploadFirmware } from './firmware/uploadFirmware';
18
- import { LoggerNames, getLogger, wait } from '../utils';
17
+ import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
18
+ import {
19
+ updateResources,
20
+ updateResourcesFromSources,
21
+ uploadFirmwareFromSource,
22
+ } from './firmware/uploadFirmware';
23
+ import { BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS } from './firmware/FirmwareUpdateBaseMethod';
24
+ import { LoggerNames, getDeviceType, getDeviceUUID, getLogger, wait } from '../utils';
25
+ import { resolveDeviceBootloaderMode } from '../utils/deviceFeaturesCompat';
19
26
  import { FirmwareUpdateTipMessage, createUiMessage } from '../events/ui-request';
20
27
  import { DeviceModelToTypes } from '../types';
21
28
  import { DataManager } from '../data-manager';
22
29
  import { DEVICE } from '../events';
30
+ import { type FirmwareByteSource, openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
31
+ import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
32
+ import {
33
+ assertFirmwareUpdatePreparedPlanBinding,
34
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
35
+ getFirmwareUpdateResourceName,
36
+ } from './firmware/FirmwareUpdatePreparedPlan';
23
37
 
24
38
  import type { Features, KnownDevice } from '../types';
25
39
  import type { FirmwareBinary } from './firmware/getBinary';
40
+ import type {
41
+ FirmwareArtifactReader,
42
+ FirmwareArtifactReference,
43
+ } from '../types/api/firmwareUpdate';
44
+ import type { FirmwareUpdatePreparedPlan } from '../types/api/firmwareUpdatePreparedPlan';
26
45
 
27
46
  type Params = {
28
47
  binary?: ArrayBuffer;
48
+ artifact?: FirmwareArtifactReference;
49
+ resourceEntries?: Array<{
50
+ entryName: string;
51
+ artifact: FirmwareArtifactReference;
52
+ }>;
53
+ artifactReader?: FirmwareArtifactReader;
54
+ preparedPlan?: FirmwareUpdatePreparedPlan;
55
+ platform?: FirmwareUpdatePreparedPlan['platform'];
29
56
  version?: number[];
30
57
  updateType: 'firmware' | 'ble';
31
58
  forcedUpdateRes?: boolean;
@@ -106,6 +133,13 @@ const normalizeFirmwareBinary = (binary: unknown): FirmwareBinary | undefined =>
106
133
  return normalized.buffer;
107
134
  };
108
135
 
136
+ const toArrayBuffer = (binary: FirmwareBinary): ArrayBuffer => {
137
+ if (binary instanceof ArrayBuffer) {
138
+ return binary;
139
+ }
140
+ return new Uint8Array(binary.buffer, binary.byteOffset, binary.byteLength).slice().buffer;
141
+ };
142
+
109
143
  export default class FirmwareUpdateV2 extends BaseMethod<Params> {
110
144
  checkPromise: Deferred<any> | null = null;
111
145
 
@@ -152,6 +186,32 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
152
186
  binary: payload.binary,
153
187
  };
154
188
  }
189
+
190
+ if ('artifact' in payload) {
191
+ const hostBinding = resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration);
192
+ const target = payload.isUpdateBootloader ? 'bootloader' : payload.updateType;
193
+ const resourceBindings = (payload.resourceEntries ?? []).map(
194
+ (entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
195
+ target: 'resource' as const,
196
+ entryName: entry.entryName,
197
+ artifact: entry.artifact,
198
+ })
199
+ );
200
+ assertFirmwareUpdatePreparedPlanBinding({
201
+ preparedPlan: payload.preparedPlan,
202
+ executor: 'v2',
203
+ platform: payload.platform,
204
+ scopeTargets: [target, ...(target === 'firmware' ? (['resource'] as const) : [])],
205
+ bindings: [{ target, artifact: payload.artifact }, ...resourceBindings],
206
+ });
207
+ this.params = {
208
+ ...this.params,
209
+ preparedPlan: payload.preparedPlan,
210
+ artifact: payload.artifact,
211
+ resourceEntries: payload.resourceEntries,
212
+ artifactReader: hostBinding.artifactReader,
213
+ };
214
+ }
155
215
  }
156
216
 
157
217
  postTipMessage = (message: string) => {
@@ -187,6 +247,10 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
187
247
  this.checkPromise = createDeferred();
188
248
  const env = DataManager.getSettings('env');
189
249
  const isBleReconnect = connectId && DataManager.isBleConnect(env);
250
+ // Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
251
+ // Initialize left pending on the V1 transport is exactly what later times out
252
+ // and used to tear down the connection mid-firmware-upload.
253
+ let bleProbeInFlight = false;
190
254
 
191
255
  Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
192
256
 
@@ -234,6 +298,8 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
234
298
  }
235
299
 
236
300
  if (isBleReconnect) {
301
+ if (bleProbeInFlight) return;
302
+ bleProbeInFlight = true;
237
303
  try {
238
304
  await this.device.deviceConnector?.acquire(
239
305
  this.device.originalDescriptor.id,
@@ -241,7 +307,10 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
241
307
  true,
242
308
  this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
243
309
  );
244
- await this.device.initialize();
310
+ // Bound each probe so a request the rebooting device never received
311
+ // frees the slot for the next tick instead of hanging into the
312
+ // 30s reboot budget.
313
+ await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
245
314
  if (this.device.isBootloader()) {
246
315
  clearInterval(intervalTimer);
247
316
  this.checkPromise?.resolve(true);
@@ -249,6 +318,8 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
249
318
  } catch (e) {
250
319
  // ignore error because of device is not connected
251
320
  Log.log('catch Bluetooth error when device is restarting: ', e);
321
+ } finally {
322
+ bleProbeInFlight = false;
252
323
  }
253
324
  } else {
254
325
  await this._checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer);
@@ -344,151 +415,255 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
344
415
  const { features, commands } = device;
345
416
  const deviceType = device.getCurrentDeviceType();
346
417
 
418
+ // Protocol V2 (Pro2) uses DeviceFirmwareUpdate and must not enter this legacy flow.
419
+ if (device.isProtocolV2()) {
420
+ throw ERRORS.TypedError(
421
+ HardwareErrorCode.RuntimeError,
422
+ 'Protocol V2 firmware update must use firmwareUpdateV4'
423
+ );
424
+ }
425
+ if (params.preparedPlan) {
426
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
427
+ preparedPlan: params.preparedPlan,
428
+ deviceIdentity: getDeviceUUID(features) || undefined,
429
+ bootloaderMode: resolveDeviceBootloaderMode(features),
430
+ deviceModel: String(getDeviceType(features)),
431
+ });
432
+ }
433
+
347
434
  const deviceFirmwareType = device.getCurrentFirmwareType();
348
435
  const firmwareType = params.firmwareType ?? deviceFirmwareType;
349
436
 
350
437
  this.checkVersionForCopyTouchResource(features, firmwareType);
351
438
 
352
- let preparedBinary: FirmwareBinary | undefined;
353
- const acquireFirmwareBinary = async (): Promise<FirmwareBinary> => {
354
- try {
355
- if (preparedBinary) {
356
- return preparedBinary;
357
- }
358
-
359
- if (params.binary !== undefined) {
360
- preparedBinary = normalizeFirmwareBinary(params.binary);
361
- if (!preparedBinary) {
362
- throw new Error('firmware binary is empty or invalid');
439
+ let preparedSource: FirmwareByteSource | undefined;
440
+ try {
441
+ const acquireFirmwareSource = async (): Promise<FirmwareByteSource> => {
442
+ try {
443
+ if (preparedSource) {
444
+ return preparedSource;
363
445
  }
364
- return preparedBinary;
365
- }
366
446
 
367
- if (!device.features) {
368
- throw ERRORS.TypedError(
369
- HardwareErrorCode.RuntimeError,
370
- 'no features found for this device'
371
- );
372
- }
373
-
374
- this.postTipMessage('DownloadFirmware');
375
- const firmware = await getBinary({
376
- features: device.features,
377
- version: params.version,
378
- updateType: params.updateType,
379
- isUpdateBootloader: params.isUpdateBootloader,
380
- firmwareType,
381
- requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
382
- });
383
- preparedBinary = normalizeFirmwareBinary(firmware.binary);
384
- if (!preparedBinary) {
385
- throw new Error('downloaded firmware binary is empty or invalid');
386
- }
387
- this.postTipMessage('DownloadFirmwareSuccess');
388
- return preparedBinary;
389
- } catch (err) {
390
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
391
- }
392
- };
447
+ if (params.binary !== undefined) {
448
+ const binary = normalizeFirmwareBinary(params.binary);
449
+ if (!binary) {
450
+ throw new Error('firmware binary is empty or invalid');
451
+ }
452
+ const source = await openFirmwareByteSource({
453
+ binary: toArrayBuffer(binary),
454
+ });
455
+ if (!source) {
456
+ throw new Error('firmware binary is empty or invalid');
457
+ }
458
+ preparedSource = source;
459
+ return source;
460
+ }
393
461
 
394
- if (!device.isBootloader() && features) {
395
- const serialNo = device.getCurrentSerialNo();
396
- // should go to bootloader mode manually
397
- if (this.isEnteredManuallyBoot()) {
398
- return Promise.reject(ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot));
399
- }
462
+ if (params.artifact) {
463
+ const source = await openFirmwareByteSource({
464
+ artifact: params.artifact,
465
+ reader: params.artifactReader,
466
+ });
467
+ if (!source) {
468
+ throw new Error('firmware artifact is not prepared');
469
+ }
470
+ preparedSource = source;
471
+ return source;
472
+ }
400
473
 
401
- // check & upgrade firmware resource
402
- if (this.isSupportResourceUpdate(params.updateType)) {
403
- this.postTipMessage('CheckLatestUiResource');
404
- const resourceUrl = DataManager.getSysResourcesLatestRelease({
405
- features,
406
- forcedUpdateRes: params.forcedUpdateRes,
407
- firmwareType,
408
- });
409
- if (resourceUrl) {
410
- this.postTipMessage('DownloadLatestUiResource');
411
- const resource = await getSysResourceBinary(resourceUrl);
412
- this.postTipMessage('DownloadLatestUiResourceSuccess');
413
- if (resource) {
414
- await updateResources(
415
- this.device.getCommands().typedCall.bind(this.device.getCommands()),
416
- this.postMessage,
417
- device,
418
- resource.binary
474
+ if (!device.features) {
475
+ throw ERRORS.TypedError(
476
+ HardwareErrorCode.RuntimeError,
477
+ 'no features found for this device'
419
478
  );
420
479
  }
421
- }
422
- }
423
-
424
- // check if the device commands has been disposed
425
- this.device?.commands?.checkDisposed();
426
480
 
427
- // A failed firmware download must leave the device in normal mode.
428
- await acquireFirmwareBinary();
481
+ if (
482
+ params.artifactReader ||
483
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
484
+ ) {
485
+ throw ERRORS.TypedError(
486
+ HardwareErrorCode.RuntimeError,
487
+ 'Firmware must be prepared by the external firmware host',
488
+ {
489
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
490
+ artifactName: 'firmware',
491
+ }
492
+ );
493
+ }
429
494
 
430
- // The request may outlive the current transport command instance.
431
- this.device?.commands?.checkDisposed();
495
+ this.postTipMessage('DownloadFirmware');
496
+ const firmware = await getBinary({
497
+ features: device.features,
498
+ version: params.version,
499
+ updateType: params.updateType,
500
+ isUpdateBootloader: params.isUpdateBootloader,
501
+ firmwareType,
502
+ requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
503
+ });
504
+ const binary = normalizeFirmwareBinary(firmware.binary);
505
+ if (!binary) {
506
+ throw new Error('downloaded firmware binary is empty or invalid');
507
+ }
508
+ const source = await openFirmwareByteSource({
509
+ binary: toArrayBuffer(binary),
510
+ });
511
+ if (!source) {
512
+ throw new Error('downloaded firmware binary is empty or invalid');
513
+ }
514
+ preparedSource = source;
515
+ this.postTipMessage('DownloadFirmwareSuccess');
516
+ return source;
517
+ } catch (err) {
518
+ throw normalizeFirmwarePreparationError(err);
519
+ }
520
+ };
432
521
 
433
- // auto go to bootloader mode
434
- try {
435
- this.postTipMessage('AutoRebootToBootloader');
436
- const bootRes = await commands.typedCall('DeviceBackToBoot', 'Success');
437
- // @ts-expect-error
438
- if (bootRes.type === 'CallMethodError') {
439
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
522
+ if (!device.isBootloader() && features) {
523
+ const serialNo = device.getCurrentSerialNo();
524
+ // should go to bootloader mode manually
525
+ if (this.isEnteredManuallyBoot()) {
526
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot);
440
527
  }
441
- this.postTipMessage('GoToBootloaderSuccess');
442
- this.checkDeviceToBootloader(this.payload.connectId);
443
528
 
444
- // force clean classic device cache so that the device can initialize again
445
- if (DeviceModelToTypes.model_classic.includes(deviceType)) {
446
- DevicePool.clearDeviceCache(serialNo);
529
+ // All network-backed input must be ready before the first device mutation.
530
+ await acquireFirmwareSource();
531
+
532
+ // check & upgrade firmware resource
533
+ if (this.isSupportResourceUpdate(params.updateType)) {
534
+ this.postTipMessage('CheckLatestUiResource');
535
+ const resourceUrl = DataManager.getSysResourcesLatestRelease({
536
+ features,
537
+ forcedUpdateRes: params.forcedUpdateRes,
538
+ firmwareType,
539
+ });
540
+ if (resourceUrl) {
541
+ this.postTipMessage('DownloadLatestUiResource');
542
+ if (params.resourceEntries?.length) {
543
+ const sources: Array<{
544
+ entryName: string;
545
+ source: FirmwareByteSource;
546
+ }> = [];
547
+ try {
548
+ for (const entry of params.resourceEntries) {
549
+ const resourceName = getFirmwareUpdateResourceName(entry.entryName);
550
+ const source = await openFirmwareByteSource({
551
+ artifact: entry.artifact,
552
+ reader: params.artifactReader,
553
+ });
554
+ if (!source) {
555
+ throw new Error('Firmware resource entry is not prepared');
556
+ }
557
+ sources.push({ entryName: resourceName, source });
558
+ }
559
+ await updateResourcesFromSources(
560
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
561
+ this.postMessage,
562
+ device,
563
+ sources.map(entry => ({
564
+ entryName: entry.entryName,
565
+ source: entry.source,
566
+ }))
567
+ );
568
+ } finally {
569
+ await Promise.all(
570
+ sources.map(entry => entry.source.close().catch(() => undefined))
571
+ );
572
+ }
573
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
574
+ } else {
575
+ if (
576
+ params.artifactReader ||
577
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
578
+ ) {
579
+ throw ERRORS.TypedError(
580
+ HardwareErrorCode.RuntimeError,
581
+ 'Firmware resource must be prepared by the external firmware host',
582
+ {
583
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
584
+ artifactName: 'resource',
585
+ }
586
+ );
587
+ }
588
+ const resourceBinary: ArrayBuffer | Buffer = (await getSysResourceBinary(resourceUrl))
589
+ .binary;
590
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
591
+ await updateResources(
592
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
593
+ this.postMessage,
594
+ device,
595
+ resourceBinary
596
+ );
597
+ }
598
+ }
447
599
  }
448
- delete DevicePool.devicesCache[''];
449
- await this.checkPromise?.promise;
450
- this.checkPromise = null;
451
600
 
452
- // check if the device commands has been disposed
601
+ // The request may outlive the current transport command instance.
453
602
  this.device?.commands?.checkDisposed();
454
603
 
455
- /**
456
- * Touch 1 with bootloader v2.5.0 issue: BLE chip need more time for looking up name, here change the delay time to 3000ms after rebooting.
457
- */
458
- const isTouch = DeviceModelToTypes.model_touch.includes(deviceType);
459
- await wait(isTouch ? 3000 : 1500);
460
- } catch (e) {
461
- if (e instanceof HardwareError) {
462
- return Promise.reject(e);
604
+ // auto go to bootloader mode
605
+ try {
606
+ this.postTipMessage('AutoRebootToBootloader');
607
+ const bootRes = await commands.typedCall('DeviceBackToBoot', 'Success');
608
+ // @ts-expect-error
609
+ if (bootRes.type === 'CallMethodError') {
610
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
611
+ }
612
+ this.postTipMessage('GoToBootloaderSuccess');
613
+ this.checkDeviceToBootloader(this.payload.connectId);
614
+
615
+ // force clean classic device cache so that the device can initialize again
616
+ if (DeviceModelToTypes.model_classic.includes(deviceType)) {
617
+ DevicePool.clearDeviceCache(serialNo);
618
+ }
619
+ delete DevicePool.devicesCache[''];
620
+ await this.checkPromise?.promise;
621
+ this.checkPromise = null;
622
+
623
+ // check if the device commands has been disposed
624
+ this.device?.commands?.checkDisposed();
625
+
626
+ /**
627
+ * Touch 1 with bootloader v2.5.0 issue: BLE chip need more time for looking up name, here change the delay time to 3000ms after rebooting.
628
+ */
629
+ const isTouch = DeviceModelToTypes.model_touch.includes(deviceType);
630
+ await wait(isTouch ? 3000 : 1500);
631
+ } catch (e) {
632
+ if (e instanceof HardwareError) {
633
+ return Promise.reject(e);
634
+ }
635
+ console.log('auto go to bootloader mode failed: ', e);
636
+ return Promise.reject(
637
+ ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
638
+ );
463
639
  }
464
- console.log('auto go to bootloader mode failed: ', e);
465
- return Promise.reject(
466
- ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
467
- );
468
640
  }
469
- }
470
641
 
471
- // Devices already in bootloader mode still acquire through the same helper.
472
- const binary = await acquireFirmwareBinary();
642
+ // Devices already in bootloader mode still acquire through the same helper.
643
+ const source = await acquireFirmwareSource();
473
644
 
474
- // check if the device commands has been disposed
475
- this.device?.commands?.checkDisposed();
645
+ // check if the device commands has been disposed
646
+ this.device?.commands?.checkDisposed();
476
647
 
477
- await this.device.acquire();
648
+ await this.device.acquire();
478
649
 
479
- const response = await uploadFirmware(
480
- params.updateType,
481
- this.device.getCommands().typedCall.bind(this.device.getCommands()),
482
- this.postMessage,
483
- device,
484
- { payload: binary, rebootOnSuccess: true },
485
- params.isUpdateBootloader
486
- );
650
+ const response = await uploadFirmwareFromSource(
651
+ params.updateType,
652
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
653
+ this.postMessage,
654
+ device,
655
+ source,
656
+ true,
657
+ params.isUpdateBootloader
658
+ );
487
659
 
488
- if (this.connectId) {
489
- DevicePool.clearDeviceCache(this.connectId);
490
- }
660
+ if (this.connectId) {
661
+ DevicePool.clearDeviceCache(this.connectId);
662
+ }
491
663
 
492
- return response;
664
+ return response;
665
+ } finally {
666
+ await preparedSource?.close().catch(() => undefined);
667
+ }
493
668
  }
494
669
  }