@onekeyfe/hd-core 1.2.0-alpha.75 → 1.2.0-alpha.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/__tests__/connectSettings.test.ts +45 -5
  2. package/__tests__/device-initialize-timeout.test.ts +56 -0
  3. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  4. package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
  5. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  6. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  7. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  8. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
  9. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  10. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
  11. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  12. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
  13. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +86 -31
  14. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +121 -0
  15. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  16. package/__tests__/protocol-v2.test.ts +405 -100
  17. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  18. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  19. package/dist/api/FirmwareUpdateV2.d.ts +10 -0
  20. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  21. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  22. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  23. package/dist/api/FirmwareUpdateV4.d.ts +25 -5
  24. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  25. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  26. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  27. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  28. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  29. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  30. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  31. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  32. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  33. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
  34. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  35. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  36. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  37. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  38. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  39. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
  40. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  41. package/dist/api/firmware/getBinary.d.ts +7 -0
  42. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  43. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  44. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  45. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  46. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  47. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  48. package/dist/data-manager/DataManager.d.ts +1 -0
  49. package/dist/data-manager/DataManager.d.ts.map +1 -1
  50. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  51. package/dist/device/Device.d.ts +1 -0
  52. package/dist/device/Device.d.ts.map +1 -1
  53. package/dist/index.d.ts +179 -1
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3088 -1063
  56. package/dist/inject.d.ts.map +1 -1
  57. package/dist/lowLevelInject.d.ts.map +1 -1
  58. package/dist/topLevelInject.d.ts.map +1 -1
  59. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
  60. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  61. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  62. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  63. package/dist/types/api/export.d.ts +4 -1
  64. package/dist/types/api/export.d.ts.map +1 -1
  65. package/dist/types/api/firmwareUpdate.d.ts +68 -0
  66. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  67. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  68. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  69. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  70. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  71. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  72. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  73. package/dist/types/api/index.d.ts +9 -1
  74. package/dist/types/api/index.d.ts.map +1 -1
  75. package/dist/types/settings.d.ts +13 -0
  76. package/dist/types/settings.d.ts.map +1 -1
  77. package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
  78. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  79. package/package.json +4 -4
  80. package/src/api/CheckAllFirmwareRelease.ts +57 -6
  81. package/src/api/FirmwareUpdate.ts +13 -1
  82. package/src/api/FirmwareUpdateV2.ts +308 -121
  83. package/src/api/FirmwareUpdateV3.ts +265 -58
  84. package/src/api/FirmwareUpdateV4.ts +881 -349
  85. package/src/api/device/DeviceUpdateBootloader.ts +125 -6
  86. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  87. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  88. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  89. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
  90. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  91. package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
  92. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
  93. package/src/api/firmware/updateBootloader.ts +19 -4
  94. package/src/api/firmware/uploadFirmware.ts +140 -22
  95. package/src/data-manager/DataManager.ts +62 -54
  96. package/src/data-manager/connectSettings.ts +11 -0
  97. package/src/device/Device.ts +6 -1
  98. package/src/index.ts +5 -0
  99. package/src/inject.ts +22 -2
  100. package/src/lowLevelInject.ts +5 -1
  101. package/src/topLevelInject.ts +5 -1
  102. package/src/types/api/checkAllFirmwareRelease.ts +4 -0
  103. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  104. package/src/types/api/export.ts +18 -0
  105. package/src/types/api/firmwareUpdate.ts +75 -0
  106. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  107. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  108. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  109. package/src/types/api/index.ts +14 -0
  110. package/src/types/settings.ts +13 -0
  111. package/src/utils/deviceFeaturesCompat.ts +6 -0
@@ -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
 
@@ -125,6 +159,18 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
125
159
  { name: 'firmwareType', type: 'string' },
126
160
  ]);
127
161
 
162
+ if (
163
+ 'binary' in payload &&
164
+ payload.binary !== undefined &&
165
+ 'artifact' in payload &&
166
+ payload.artifact !== undefined
167
+ ) {
168
+ throw ERRORS.TypedError(
169
+ HardwareErrorCode.CallMethodInvalidParameter,
170
+ 'Firmware update binary and artifact are mutually exclusive'
171
+ );
172
+ }
173
+
128
174
  if (!payload.updateType) {
129
175
  throw ERRORS.TypedError(
130
176
  HardwareErrorCode.CallMethodInvalidParameter,
@@ -152,6 +198,32 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
152
198
  binary: payload.binary,
153
199
  };
154
200
  }
201
+
202
+ if ('artifact' in payload) {
203
+ const hostBinding = resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration);
204
+ const target = payload.isUpdateBootloader ? 'bootloader' : payload.updateType;
205
+ const resourceBindings = (payload.resourceEntries ?? []).map(
206
+ (entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
207
+ target: 'resource' as const,
208
+ entryName: entry.entryName,
209
+ artifact: entry.artifact,
210
+ })
211
+ );
212
+ assertFirmwareUpdatePreparedPlanBinding({
213
+ preparedPlan: payload.preparedPlan,
214
+ executor: 'v2',
215
+ platform: payload.platform,
216
+ scopeTargets: [target, ...(target === 'firmware' ? (['resource'] as const) : [])],
217
+ bindings: [{ target, artifact: payload.artifact }, ...resourceBindings],
218
+ });
219
+ this.params = {
220
+ ...this.params,
221
+ preparedPlan: payload.preparedPlan,
222
+ artifact: payload.artifact,
223
+ resourceEntries: payload.resourceEntries,
224
+ artifactReader: hostBinding.artifactReader,
225
+ };
226
+ }
155
227
  }
156
228
 
157
229
  postTipMessage = (message: string) => {
@@ -187,6 +259,10 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
187
259
  this.checkPromise = createDeferred();
188
260
  const env = DataManager.getSettings('env');
189
261
  const isBleReconnect = connectId && DataManager.isBleConnect(env);
262
+ // Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
263
+ // Initialize left pending on the V1 transport is exactly what later times out
264
+ // and used to tear down the connection mid-firmware-upload.
265
+ let bleProbeInFlight = false;
190
266
 
191
267
  Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
192
268
 
@@ -234,6 +310,8 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
234
310
  }
235
311
 
236
312
  if (isBleReconnect) {
313
+ if (bleProbeInFlight) return;
314
+ bleProbeInFlight = true;
237
315
  try {
238
316
  await this.device.deviceConnector?.acquire(
239
317
  this.device.originalDescriptor.id,
@@ -241,7 +319,10 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
241
319
  true,
242
320
  this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
243
321
  );
244
- await this.device.initialize();
322
+ // Bound each probe so a request the rebooting device never received
323
+ // frees the slot for the next tick instead of hanging into the
324
+ // 30s reboot budget.
325
+ await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
245
326
  if (this.device.isBootloader()) {
246
327
  clearInterval(intervalTimer);
247
328
  this.checkPromise?.resolve(true);
@@ -249,6 +330,8 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
249
330
  } catch (e) {
250
331
  // ignore error because of device is not connected
251
332
  Log.log('catch Bluetooth error when device is restarting: ', e);
333
+ } finally {
334
+ bleProbeInFlight = false;
252
335
  }
253
336
  } else {
254
337
  await this._checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer);
@@ -344,151 +427,255 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
344
427
  const { features, commands } = device;
345
428
  const deviceType = device.getCurrentDeviceType();
346
429
 
430
+ // Protocol V2 (Pro2) uses DeviceFirmwareUpdate and must not enter this legacy flow.
431
+ if (device.isProtocolV2()) {
432
+ throw ERRORS.TypedError(
433
+ HardwareErrorCode.RuntimeError,
434
+ 'Protocol V2 firmware update must use firmwareUpdateV4'
435
+ );
436
+ }
437
+ if (params.preparedPlan) {
438
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
439
+ preparedPlan: params.preparedPlan,
440
+ deviceIdentity: getDeviceUUID(features) || undefined,
441
+ bootloaderMode: resolveDeviceBootloaderMode(features),
442
+ deviceModel: String(getDeviceType(features)),
443
+ });
444
+ }
445
+
347
446
  const deviceFirmwareType = device.getCurrentFirmwareType();
348
447
  const firmwareType = params.firmwareType ?? deviceFirmwareType;
349
448
 
350
449
  this.checkVersionForCopyTouchResource(features, firmwareType);
351
450
 
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');
451
+ let preparedSource: FirmwareByteSource | undefined;
452
+ try {
453
+ const acquireFirmwareSource = async (): Promise<FirmwareByteSource> => {
454
+ try {
455
+ if (preparedSource) {
456
+ return preparedSource;
363
457
  }
364
- return preparedBinary;
365
- }
366
-
367
- if (!device.features) {
368
- throw ERRORS.TypedError(
369
- HardwareErrorCode.RuntimeError,
370
- 'no features found for this device'
371
- );
372
- }
373
458
 
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
- };
459
+ if (params.binary !== undefined) {
460
+ const binary = normalizeFirmwareBinary(params.binary);
461
+ if (!binary) {
462
+ throw new Error('firmware binary is empty or invalid');
463
+ }
464
+ const source = await openFirmwareByteSource({
465
+ binary: toArrayBuffer(binary),
466
+ });
467
+ if (!source) {
468
+ throw new Error('firmware binary is empty or invalid');
469
+ }
470
+ preparedSource = source;
471
+ return source;
472
+ }
393
473
 
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
- }
474
+ if (params.artifact) {
475
+ const source = await openFirmwareByteSource({
476
+ artifact: params.artifact,
477
+ reader: params.artifactReader,
478
+ });
479
+ if (!source) {
480
+ throw new Error('firmware artifact is not prepared');
481
+ }
482
+ preparedSource = source;
483
+ return source;
484
+ }
400
485
 
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
486
+ if (!device.features) {
487
+ throw ERRORS.TypedError(
488
+ HardwareErrorCode.RuntimeError,
489
+ 'no features found for this device'
419
490
  );
420
491
  }
421
- }
422
- }
423
-
424
- // check if the device commands has been disposed
425
- this.device?.commands?.checkDisposed();
426
492
 
427
- // A failed firmware download must leave the device in normal mode.
428
- await acquireFirmwareBinary();
493
+ if (
494
+ params.artifactReader ||
495
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
496
+ ) {
497
+ throw ERRORS.TypedError(
498
+ HardwareErrorCode.RuntimeError,
499
+ 'Firmware must be prepared by the external firmware host',
500
+ {
501
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
502
+ artifactName: 'firmware',
503
+ }
504
+ );
505
+ }
429
506
 
430
- // The request may outlive the current transport command instance.
431
- this.device?.commands?.checkDisposed();
507
+ this.postTipMessage('DownloadFirmware');
508
+ const firmware = await getBinary({
509
+ features: device.features,
510
+ version: params.version,
511
+ updateType: params.updateType,
512
+ isUpdateBootloader: params.isUpdateBootloader,
513
+ firmwareType,
514
+ requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
515
+ });
516
+ const binary = normalizeFirmwareBinary(firmware.binary);
517
+ if (!binary) {
518
+ throw new Error('downloaded firmware binary is empty or invalid');
519
+ }
520
+ const source = await openFirmwareByteSource({
521
+ binary: toArrayBuffer(binary),
522
+ });
523
+ if (!source) {
524
+ throw new Error('downloaded firmware binary is empty or invalid');
525
+ }
526
+ preparedSource = source;
527
+ this.postTipMessage('DownloadFirmwareSuccess');
528
+ return source;
529
+ } catch (err) {
530
+ throw normalizeFirmwarePreparationError(err);
531
+ }
532
+ };
432
533
 
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);
534
+ if (!device.isBootloader() && features) {
535
+ const serialNo = device.getCurrentSerialNo();
536
+ // should go to bootloader mode manually
537
+ if (this.isEnteredManuallyBoot()) {
538
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot);
440
539
  }
441
- this.postTipMessage('GoToBootloaderSuccess');
442
- this.checkDeviceToBootloader(this.payload.connectId);
443
540
 
444
- // force clean classic device cache so that the device can initialize again
445
- if (DeviceModelToTypes.model_classic.includes(deviceType)) {
446
- DevicePool.clearDeviceCache(serialNo);
541
+ // All network-backed input must be ready before the first device mutation.
542
+ await acquireFirmwareSource();
543
+
544
+ // check & upgrade firmware resource
545
+ if (this.isSupportResourceUpdate(params.updateType)) {
546
+ this.postTipMessage('CheckLatestUiResource');
547
+ const resourceUrl = DataManager.getSysResourcesLatestRelease({
548
+ features,
549
+ forcedUpdateRes: params.forcedUpdateRes,
550
+ firmwareType,
551
+ });
552
+ if (resourceUrl) {
553
+ this.postTipMessage('DownloadLatestUiResource');
554
+ if (params.resourceEntries?.length) {
555
+ const sources: Array<{
556
+ entryName: string;
557
+ source: FirmwareByteSource;
558
+ }> = [];
559
+ try {
560
+ for (const entry of params.resourceEntries) {
561
+ const resourceName = getFirmwareUpdateResourceName(entry.entryName);
562
+ const source = await openFirmwareByteSource({
563
+ artifact: entry.artifact,
564
+ reader: params.artifactReader,
565
+ });
566
+ if (!source) {
567
+ throw new Error('Firmware resource entry is not prepared');
568
+ }
569
+ sources.push({ entryName: resourceName, source });
570
+ }
571
+ await updateResourcesFromSources(
572
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
573
+ this.postMessage,
574
+ device,
575
+ sources.map(entry => ({
576
+ entryName: entry.entryName,
577
+ source: entry.source,
578
+ }))
579
+ );
580
+ } finally {
581
+ await Promise.all(
582
+ sources.map(entry => entry.source.close().catch(() => undefined))
583
+ );
584
+ }
585
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
586
+ } else {
587
+ if (
588
+ params.artifactReader ||
589
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
590
+ ) {
591
+ throw ERRORS.TypedError(
592
+ HardwareErrorCode.RuntimeError,
593
+ 'Firmware resource must be prepared by the external firmware host',
594
+ {
595
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
596
+ artifactName: 'resource',
597
+ }
598
+ );
599
+ }
600
+ const resourceBinary: ArrayBuffer | Buffer = (await getSysResourceBinary(resourceUrl))
601
+ .binary;
602
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
603
+ await updateResources(
604
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
605
+ this.postMessage,
606
+ device,
607
+ resourceBinary
608
+ );
609
+ }
610
+ }
447
611
  }
448
- delete DevicePool.devicesCache[''];
449
- await this.checkPromise?.promise;
450
- this.checkPromise = null;
451
612
 
452
- // check if the device commands has been disposed
613
+ // The request may outlive the current transport command instance.
453
614
  this.device?.commands?.checkDisposed();
454
615
 
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);
616
+ // auto go to bootloader mode
617
+ try {
618
+ this.postTipMessage('AutoRebootToBootloader');
619
+ const bootRes = await commands.typedCall('DeviceBackToBoot', 'Success');
620
+ // @ts-expect-error
621
+ if (bootRes.type === 'CallMethodError') {
622
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
623
+ }
624
+ this.postTipMessage('GoToBootloaderSuccess');
625
+ this.checkDeviceToBootloader(this.payload.connectId);
626
+
627
+ // force clean classic device cache so that the device can initialize again
628
+ if (DeviceModelToTypes.model_classic.includes(deviceType)) {
629
+ DevicePool.clearDeviceCache(serialNo);
630
+ }
631
+ delete DevicePool.devicesCache[''];
632
+ await this.checkPromise?.promise;
633
+ this.checkPromise = null;
634
+
635
+ // check if the device commands has been disposed
636
+ this.device?.commands?.checkDisposed();
637
+
638
+ /**
639
+ * 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.
640
+ */
641
+ const isTouch = DeviceModelToTypes.model_touch.includes(deviceType);
642
+ await wait(isTouch ? 3000 : 1500);
643
+ } catch (e) {
644
+ if (e instanceof HardwareError) {
645
+ return Promise.reject(e);
646
+ }
647
+ console.log('auto go to bootloader mode failed: ', e);
648
+ return Promise.reject(
649
+ ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
650
+ );
463
651
  }
464
- console.log('auto go to bootloader mode failed: ', e);
465
- return Promise.reject(
466
- ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
467
- );
468
652
  }
469
- }
470
653
 
471
- // Devices already in bootloader mode still acquire through the same helper.
472
- const binary = await acquireFirmwareBinary();
654
+ // Devices already in bootloader mode still acquire through the same helper.
655
+ const source = await acquireFirmwareSource();
473
656
 
474
- // check if the device commands has been disposed
475
- this.device?.commands?.checkDisposed();
657
+ // check if the device commands has been disposed
658
+ this.device?.commands?.checkDisposed();
476
659
 
477
- await this.device.acquire();
660
+ await this.device.acquire();
478
661
 
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
- );
662
+ const response = await uploadFirmwareFromSource(
663
+ params.updateType,
664
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
665
+ this.postMessage,
666
+ device,
667
+ source,
668
+ true,
669
+ params.isUpdateBootloader
670
+ );
487
671
 
488
- if (this.connectId) {
489
- DevicePool.clearDeviceCache(this.connectId);
490
- }
672
+ if (this.connectId) {
673
+ DevicePool.clearDeviceCache(this.connectId);
674
+ }
491
675
 
492
- return response;
676
+ return response;
677
+ } finally {
678
+ await preparedSource?.close().catch(() => undefined);
679
+ }
493
680
  }
494
681
  }