@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.22

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 (175) hide show
  1. package/__tests__/DeviceCommands.test.ts +87 -2
  2. package/__tests__/connectSettings.test.ts +47 -5
  3. package/__tests__/device-identity.test.ts +24 -2
  4. package/__tests__/device-settings.test.ts +1 -1
  5. package/__tests__/device-wallet-session-store.test.ts +111 -7
  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-update-capabilities.test.ts +13 -0
  9. package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
  10. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +200 -0
  11. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
  12. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  13. package/__tests__/open-wallet-session.test.ts +253 -13
  14. package/__tests__/pro2Wallpaper.test.ts +7 -18
  15. package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
  16. package/__tests__/protocol-v2.test.ts +415 -145
  17. package/__tests__/public-device-state-api.test.ts +69 -62
  18. package/__tests__/public-pro2-api-boundary.test.ts +8 -34
  19. package/dist/api/BaseMethod.d.ts +2 -1
  20. package/dist/api/BaseMethod.d.ts.map +1 -1
  21. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  22. package/dist/api/ClearSessionCache.d.ts.map +1 -1
  23. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV2.d.ts +11 -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 +29 -10
  29. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  30. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  31. package/dist/api/conflux/ConfluxGetAddress.d.ts +1 -0
  32. package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
  33. package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
  34. package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
  35. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
  36. package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
  37. package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
  38. package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
  39. package/dist/api/device/DeviceUnlock.d.ts +1 -1
  40. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  41. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  42. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  43. package/dist/api/firmware/FirmwareArtifactSource.d.ts +26 -0
  44. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  45. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  46. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  47. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +7 -0
  48. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  49. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  50. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  51. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +24 -0
  52. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  53. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +25 -0
  54. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  55. package/dist/api/firmware/getBinary.d.ts +7 -0
  56. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  57. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  58. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  59. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  60. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  61. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  62. package/dist/api/index.d.ts +0 -17
  63. package/dist/api/index.d.ts.map +1 -1
  64. package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
  65. package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
  66. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  67. package/dist/data-manager/DataManager.d.ts +1 -0
  68. package/dist/data-manager/DataManager.d.ts.map +1 -1
  69. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  70. package/dist/device/Device.d.ts.map +1 -1
  71. package/dist/device/DeviceCommands.d.ts.map +1 -1
  72. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  73. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  74. package/dist/events/logBlockEvent.d.ts.map +1 -1
  75. package/dist/index.d.ts +265 -226
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +3045 -1550
  78. package/dist/inject.d.ts.map +1 -1
  79. package/dist/lowLevelInject.d.ts.map +1 -1
  80. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  81. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
  82. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
  83. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  84. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  85. package/dist/topLevelInject.d.ts.map +1 -1
  86. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -0
  87. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  88. package/dist/types/api/deviceUnlock.d.ts +2 -2
  89. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  90. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  91. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  92. package/dist/types/api/export.d.ts +4 -1
  93. package/dist/types/api/export.d.ts.map +1 -1
  94. package/dist/types/api/firmwareUpdate.d.ts +69 -0
  95. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  96. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  97. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  98. package/dist/types/api/firmwareUpdatePlan.d.ts +27 -0
  99. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  100. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  101. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  102. package/dist/types/api/index.d.ts +10 -19
  103. package/dist/types/api/index.d.ts.map +1 -1
  104. package/dist/types/api/openWalletSession.d.ts +1 -0
  105. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  106. package/dist/types/api/protocolV2.d.ts +3 -76
  107. package/dist/types/api/protocolV2.d.ts.map +1 -1
  108. package/dist/types/api/sessionCache.d.ts +4 -1
  109. package/dist/types/api/sessionCache.d.ts.map +1 -1
  110. package/dist/types/settings.d.ts +13 -0
  111. package/dist/types/settings.d.ts.map +1 -1
  112. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  113. package/dist/utils/patch.d.ts +1 -1
  114. package/dist/utils/patch.d.ts.map +1 -1
  115. package/package.json +4 -4
  116. package/src/api/BaseMethod.ts +8 -3
  117. package/src/api/CheckAllFirmwareRelease.ts +16 -3
  118. package/src/api/ClearSessionCache.ts +4 -0
  119. package/src/api/FirmwareUpdate.ts +8 -1
  120. package/src/api/FirmwareUpdateV2.ts +284 -119
  121. package/src/api/FirmwareUpdateV3.ts +248 -55
  122. package/src/api/FirmwareUpdateV4.ts +816 -328
  123. package/src/api/OpenWalletSession.ts +74 -25
  124. package/src/api/UploadPortfolio.ts +1 -1
  125. package/src/api/conflux/ConfluxGetAddress.ts +2 -0
  126. package/src/api/conflux/ConfluxSignMessage.ts +2 -0
  127. package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
  128. package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
  129. package/src/api/device/DeviceChangePin.ts +1 -1
  130. package/src/api/device/DeviceUnlock.ts +1 -2
  131. package/src/api/device/DeviceUpdateBootloader.ts +114 -6
  132. package/src/api/device/DeviceWipe.ts +1 -1
  133. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  134. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  135. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
  136. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  137. package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
  138. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +378 -0
  139. package/src/api/firmware/updateBootloader.ts +19 -4
  140. package/src/api/firmware/uploadFirmware.ts +140 -22
  141. package/src/api/helpers/protocolV2FileWrite.ts +6 -0
  142. package/src/api/index.ts +0 -18
  143. package/src/api/nexa/NexaSignTransaction.ts +2 -0
  144. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  145. package/src/data/messages/messages-protocol-v2.json +77 -4
  146. package/src/data-manager/DataManager.ts +33 -33
  147. package/src/data-manager/connectSettings.ts +11 -0
  148. package/src/device/Device.ts +50 -60
  149. package/src/device/DeviceCommands.ts +58 -14
  150. package/src/device/DeviceStateMapper.ts +18 -1
  151. package/src/device/DeviceStateProjector.ts +7 -1
  152. package/src/events/logBlockEvent.ts +1 -0
  153. package/src/index.ts +5 -0
  154. package/src/inject.ts +22 -25
  155. package/src/lowLevelInject.ts +5 -1
  156. package/src/protocols/protocol-v2/deviceSession.ts +1 -1
  157. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -2
  158. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
  159. package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
  160. package/src/protocols/protocol-v2/walletSession.ts +68 -12
  161. package/src/topLevelInject.ts +5 -1
  162. package/src/types/api/checkAllFirmwareRelease.ts +3 -0
  163. package/src/types/api/deviceUnlock.ts +2 -5
  164. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  165. package/src/types/api/export.ts +18 -0
  166. package/src/types/api/firmwareUpdate.ts +76 -0
  167. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  168. package/src/types/api/firmwareUpdatePlan.ts +36 -0
  169. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  170. package/src/types/api/index.ts +14 -34
  171. package/src/types/api/openWalletSession.ts +2 -0
  172. package/src/types/api/protocolV2.ts +3 -156
  173. package/src/types/api/sessionCache.ts +9 -4
  174. package/src/types/settings.ts +13 -0
  175. package/src/utils/deviceFeaturesUtils.ts +5 -1
@@ -23,8 +23,12 @@ import type { CoreMessage } from '../events';
23
23
  import type { ProtocolV2InteractionDescriptor } from '../protocols/protocol-v2/uiInteraction';
24
24
  import type { RequestContext } from '../utils/tracing';
25
25
  import type { CoreContext } from '../core';
26
+ import {
27
+ getProtocolV2UnlockPolicy,
28
+ type UnlockPolicy,
29
+ } from '../protocols/protocol-v2/unlockPolicy';
26
30
 
27
- export type UnlockPolicy = 'none' | 'retry-on-locked';
31
+ export type { UnlockPolicy } from '../protocols/protocol-v2/unlockPolicy';
28
32
  export type ProtocolV2UiMode = 'auto' | 'none';
29
33
 
30
34
  const Log = getLogger(LoggerNames.Method);
@@ -171,8 +175,8 @@ export abstract class BaseMethod<Params = undefined> {
171
175
  */
172
176
  requireProtocolV2 = false;
173
177
 
174
- /** Core unlocks and retries Protocol V2 business methods once when the device is locked. */
175
- unlockPolicy: UnlockPolicy = 'retry-on-locked';
178
+ /** Core unlocks and retries only explicitly replay-safe Protocol V2 methods. */
179
+ unlockPolicy: UnlockPolicy = 'none';
176
180
 
177
181
  /** Non-blocking Protocol V2 interaction synthesized by the SDK. */
178
182
  protocolV2UiInteraction?: ProtocolV2InteractionDescriptor;
@@ -206,6 +210,7 @@ export abstract class BaseMethod<Params = undefined> {
206
210
  this.useDevice = true;
207
211
  this.allowDeviceMode = [UI_REQUEST.NOT_INITIALIZE];
208
212
  this.requireDeviceMode = [];
213
+ this.unlockPolicy = getProtocolV2UnlockPolicy(this.name);
209
214
  }
210
215
 
211
216
  abstract init(): void;
@@ -5,6 +5,7 @@ import {
5
5
  getBootloaderReleaseInfo,
6
6
  getFirmwareReleaseInfo,
7
7
  } from './firmware/releaseHelper';
8
+ import { buildFirmwareUpdatePlan } from './firmware/FirmwareUpdatePlan';
8
9
  import { getBridgeReleaseInfo } from '../utils/bridgeUpdate';
9
10
  import { getDeviceFirmwareVersion, getDeviceType, getFirmwareType } from '../utils';
10
11
 
@@ -22,8 +23,11 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
22
23
 
23
24
  async run() {
24
25
  const { features } = this.device;
25
- const { checkBridgeRelease, firmwareType: firmwareTypeParams } = this
26
- .payload as CheckAllFirmwareReleaseParams;
26
+ const {
27
+ checkBridgeRelease,
28
+ firmwareType: firmwareTypeParams,
29
+ platform,
30
+ } = this.payload as CheckAllFirmwareReleaseParams;
27
31
 
28
32
  if (!features) {
29
33
  return Promise.resolve(null);
@@ -54,6 +58,14 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
54
58
  firmwareType,
55
59
  });
56
60
  const bleFirmwareReleaseInfo = getBleFirmwareReleaseInfo(features);
61
+ const firmwareUpdatePlan = buildFirmwareUpdatePlan({
62
+ features,
63
+ firmwareType,
64
+ platform: platform ?? 'web',
65
+ firmware: firmwareRelease,
66
+ ble: bleFirmwareReleaseInfo,
67
+ bootloader: bootloaderRelease,
68
+ });
57
69
 
58
70
  return {
59
71
  firmware: firmwareRelease,
@@ -68,6 +80,7 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
68
80
  }
69
81
  : undefined,
70
82
  features,
71
- } as AllFirmwareRelease;
83
+ firmwareUpdatePlan,
84
+ } as unknown as AllFirmwareRelease;
72
85
  }
73
86
  }
@@ -1,10 +1,14 @@
1
1
  import { BaseMethod } from './BaseMethod';
2
+ import { invalidParameter } from './helpers/paramsValidator';
2
3
  import { deviceWalletSessionStore } from '../device/DeviceWalletSessionStore';
3
4
 
4
5
  import type { ClearSessionCacheParams } from '../types/api/sessionCache';
5
6
 
6
7
  export default class ClearSessionCache extends BaseMethod<ClearSessionCacheParams> {
7
8
  init() {
9
+ if (this.payload.passphraseState !== undefined && !this.payload.deviceId) {
10
+ throw invalidParameter('Parameter [deviceId] is required with [passphraseState].');
11
+ }
8
12
  this.useDevice = false;
9
13
  this.useDevicePassphraseState = false;
10
14
  this.skipForceUpdateCheck = true;
@@ -182,7 +182,7 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
182
182
  }
183
183
  }
184
184
 
185
- let binary;
185
+ let binary: ArrayBuffer | undefined;
186
186
  try {
187
187
  if (params.binary) {
188
188
  binary = this.params.binary;
@@ -207,6 +207,13 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
207
207
  throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
208
208
  }
209
209
 
210
+ if (!binary) {
211
+ throw ERRORS.TypedError(
212
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
213
+ 'Firmware binary is unavailable'
214
+ );
215
+ }
216
+
210
217
  await this.device.acquire();
211
218
 
212
219
  const response = await uploadFirmware(
@@ -14,18 +14,42 @@ 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';
17
+ import {
18
+ updateResources,
19
+ updateResourcesFromSources,
20
+ uploadFirmwareFromSource,
21
+ } from './firmware/uploadFirmware';
18
22
  import { LoggerNames, getLogger, wait } from '../utils';
19
23
  import { FirmwareUpdateTipMessage, createUiMessage } from '../events/ui-request';
20
24
  import { DeviceModelToTypes } from '../types';
21
25
  import { DataManager } from '../data-manager';
22
26
  import { DEVICE } from '../events';
27
+ import { type FirmwareByteSource, openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
28
+ import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
29
+ import {
30
+ assertFirmwareUpdatePreparedPlanBinding,
31
+ getFirmwareUpdateResourceName,
32
+ } from './firmware/FirmwareUpdatePreparedPlan';
23
33
 
24
34
  import type { Features, KnownDevice } from '../types';
25
35
  import type { FirmwareBinary } from './firmware/getBinary';
36
+ import type {
37
+ FirmwareArtifactReader,
38
+ FirmwareArtifactReference,
39
+ } from '../types/api/firmwareUpdate';
40
+ import type { FirmwareUpdatePreparedPlan } from '../types/api/firmwareUpdatePreparedPlan';
26
41
 
27
42
  type Params = {
28
43
  binary?: ArrayBuffer;
44
+ artifact?: FirmwareArtifactReference;
45
+ resourceArtifact?: FirmwareArtifactReference;
46
+ resourceEntries?: Array<{
47
+ entryName: string;
48
+ artifact: FirmwareArtifactReference;
49
+ }>;
50
+ artifactReader?: FirmwareArtifactReader;
51
+ preparedPlan?: FirmwareUpdatePreparedPlan;
52
+ platform?: FirmwareUpdatePreparedPlan['platform'];
29
53
  version?: number[];
30
54
  updateType: 'firmware' | 'ble';
31
55
  forcedUpdateRes?: boolean;
@@ -106,6 +130,13 @@ const normalizeFirmwareBinary = (binary: unknown): FirmwareBinary | undefined =>
106
130
  return normalized.buffer;
107
131
  };
108
132
 
133
+ const toArrayBuffer = (binary: FirmwareBinary): ArrayBuffer => {
134
+ if (binary instanceof ArrayBuffer) {
135
+ return binary;
136
+ }
137
+ return new Uint8Array(binary.buffer, binary.byteOffset, binary.byteLength).slice().buffer;
138
+ };
139
+
109
140
  export default class FirmwareUpdateV2 extends BaseMethod<Params> {
110
141
  checkPromise: Deferred<any> | null = null;
111
142
 
@@ -152,6 +183,38 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
152
183
  binary: payload.binary,
153
184
  };
154
185
  }
186
+
187
+ if ('artifact' in payload) {
188
+ const hostBinding = resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration);
189
+ const target = payload.isUpdateBootloader ? 'bootloader' : payload.updateType;
190
+ const resourceBindings = [
191
+ ...(payload.resourceArtifact
192
+ ? [{ target: 'resource' as const, artifact: payload.resourceArtifact }]
193
+ : []),
194
+ ...(payload.resourceEntries ?? []).map(
195
+ (entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
196
+ target: 'resource' as const,
197
+ entryName: entry.entryName,
198
+ artifact: entry.artifact,
199
+ })
200
+ ),
201
+ ];
202
+ assertFirmwareUpdatePreparedPlanBinding({
203
+ preparedPlan: payload.preparedPlan,
204
+ executor: 'v2',
205
+ platform: payload.platform,
206
+ scopeTargets: [target, ...(target === 'firmware' ? (['resource'] as const) : [])],
207
+ bindings: [{ target, artifact: payload.artifact }, ...resourceBindings],
208
+ });
209
+ this.params = {
210
+ ...this.params,
211
+ preparedPlan: payload.preparedPlan,
212
+ artifact: payload.artifact,
213
+ resourceArtifact: payload.resourceArtifact,
214
+ resourceEntries: payload.resourceEntries,
215
+ artifactReader: hostBinding.artifactReader,
216
+ };
217
+ }
155
218
  }
156
219
 
157
220
  postTipMessage = (message: string) => {
@@ -354,146 +417,248 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
354
417
 
355
418
  this.checkVersionForCopyTouchResource(features, firmwareType);
356
419
 
357
- let preparedBinary: FirmwareBinary | undefined;
358
- const acquireFirmwareBinary = async (): Promise<FirmwareBinary> => {
359
- try {
360
- if (preparedBinary) {
361
- return preparedBinary;
362
- }
363
-
364
- if (params.binary !== undefined) {
365
- preparedBinary = normalizeFirmwareBinary(params.binary);
366
- if (!preparedBinary) {
367
- throw new Error('firmware binary is empty or invalid');
420
+ let preparedSource: FirmwareByteSource | undefined;
421
+ try {
422
+ const acquireFirmwareSource = async (): Promise<FirmwareByteSource> => {
423
+ try {
424
+ if (preparedSource) {
425
+ return preparedSource;
368
426
  }
369
- return preparedBinary;
370
- }
371
427
 
372
- if (!device.features) {
373
- throw ERRORS.TypedError(
374
- HardwareErrorCode.RuntimeError,
375
- 'no features found for this device'
376
- );
377
- }
378
-
379
- this.postTipMessage('DownloadFirmware');
380
- const firmware = await getBinary({
381
- features: device.features,
382
- version: params.version,
383
- updateType: params.updateType,
384
- isUpdateBootloader: params.isUpdateBootloader,
385
- firmwareType,
386
- requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
387
- });
388
- preparedBinary = normalizeFirmwareBinary(firmware.binary);
389
- if (!preparedBinary) {
390
- throw new Error('downloaded firmware binary is empty or invalid');
391
- }
392
- this.postTipMessage('DownloadFirmwareSuccess');
393
- return preparedBinary;
394
- } catch (err) {
395
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
396
- }
397
- };
428
+ if (params.binary !== undefined) {
429
+ const binary = normalizeFirmwareBinary(params.binary);
430
+ if (!binary) {
431
+ throw new Error('firmware binary is empty or invalid');
432
+ }
433
+ const source = await openFirmwareByteSource({
434
+ binary: toArrayBuffer(binary),
435
+ });
436
+ if (!source) {
437
+ throw new Error('firmware binary is empty or invalid');
438
+ }
439
+ preparedSource = source;
440
+ return source;
441
+ }
398
442
 
399
- if (!device.isBootloader() && features) {
400
- const serialNo = device.getCurrentSerialNo();
401
- // should go to bootloader mode manually
402
- if (this.isEnteredManuallyBoot()) {
403
- return Promise.reject(ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot));
404
- }
443
+ if (params.artifact) {
444
+ const source = await openFirmwareByteSource({
445
+ artifact: params.artifact,
446
+ reader: params.artifactReader,
447
+ });
448
+ if (!source) {
449
+ throw new Error('firmware artifact is not prepared');
450
+ }
451
+ preparedSource = source;
452
+ return source;
453
+ }
405
454
 
406
- // check & upgrade firmware resource
407
- if (this.isSupportResourceUpdate(params.updateType)) {
408
- this.postTipMessage('CheckLatestUiResource');
409
- const resourceUrl = DataManager.getSysResourcesLatestRelease({
410
- features,
411
- forcedUpdateRes: params.forcedUpdateRes,
412
- firmwareType,
413
- });
414
- if (resourceUrl) {
415
- this.postTipMessage('DownloadLatestUiResource');
416
- const resource = await getSysResourceBinary(resourceUrl);
417
- this.postTipMessage('DownloadLatestUiResourceSuccess');
418
- if (resource) {
419
- await updateResources(
420
- this.device.getCommands().typedCall.bind(this.device.getCommands()),
421
- this.postMessage,
422
- device,
423
- resource.binary
455
+ if (!device.features) {
456
+ throw ERRORS.TypedError(
457
+ HardwareErrorCode.RuntimeError,
458
+ 'no features found for this device'
424
459
  );
425
460
  }
426
- }
427
- }
428
461
 
429
- // check if the device commands has been disposed
430
- this.device?.commands?.checkDisposed();
431
-
432
- // A failed firmware download must leave the device in normal mode.
433
- await acquireFirmwareBinary();
462
+ if (
463
+ params.artifactReader ||
464
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
465
+ ) {
466
+ throw ERRORS.TypedError(
467
+ HardwareErrorCode.RuntimeError,
468
+ 'Firmware must be prepared by the external firmware host',
469
+ {
470
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
471
+ artifactName: 'firmware',
472
+ }
473
+ );
474
+ }
434
475
 
435
- // The request may outlive the current transport command instance.
436
- this.device?.commands?.checkDisposed();
476
+ this.postTipMessage('DownloadFirmware');
477
+ const firmware = await getBinary({
478
+ features: device.features,
479
+ version: params.version,
480
+ updateType: params.updateType,
481
+ isUpdateBootloader: params.isUpdateBootloader,
482
+ firmwareType,
483
+ requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
484
+ });
485
+ const binary = normalizeFirmwareBinary(firmware.binary);
486
+ if (!binary) {
487
+ throw new Error('downloaded firmware binary is empty or invalid');
488
+ }
489
+ const source = await openFirmwareByteSource({
490
+ binary: toArrayBuffer(binary),
491
+ });
492
+ if (!source) {
493
+ throw new Error('downloaded firmware binary is empty or invalid');
494
+ }
495
+ preparedSource = source;
496
+ this.postTipMessage('DownloadFirmwareSuccess');
497
+ return source;
498
+ } catch (err) {
499
+ throw ERRORS.TypedError(
500
+ HardwareErrorCode.FirmwareUpdateDownloadFailed,
501
+ err.message ?? err
502
+ );
503
+ }
504
+ };
437
505
 
438
- // auto go to bootloader mode
439
- try {
440
- this.postTipMessage('AutoRebootToBootloader');
441
- const bootRes = await commands.typedCall('DeviceBackToBoot', 'Success');
442
- // @ts-expect-error
443
- if (bootRes.type === 'CallMethodError') {
444
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
506
+ if (!device.isBootloader() && features) {
507
+ const serialNo = device.getCurrentSerialNo();
508
+ // should go to bootloader mode manually
509
+ if (this.isEnteredManuallyBoot()) {
510
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot);
445
511
  }
446
- this.postTipMessage('GoToBootloaderSuccess');
447
- this.checkDeviceToBootloader(this.payload.connectId);
448
512
 
449
- // force clean classic device cache so that the device can initialize again
450
- if (DeviceModelToTypes.model_classic.includes(deviceType)) {
451
- DevicePool.clearDeviceCache(serialNo);
513
+ // All network-backed input must be ready before the first device mutation.
514
+ await acquireFirmwareSource();
515
+
516
+ // check & upgrade firmware resource
517
+ if (this.isSupportResourceUpdate(params.updateType)) {
518
+ this.postTipMessage('CheckLatestUiResource');
519
+ const resourceUrl = DataManager.getSysResourcesLatestRelease({
520
+ features,
521
+ forcedUpdateRes: params.forcedUpdateRes,
522
+ firmwareType,
523
+ });
524
+ if (resourceUrl) {
525
+ this.postTipMessage('DownloadLatestUiResource');
526
+ if (params.resourceEntries?.length) {
527
+ const sources: Array<{
528
+ entryName: string;
529
+ source: FirmwareByteSource;
530
+ }> = [];
531
+ try {
532
+ for (const entry of params.resourceEntries) {
533
+ const resourceName = getFirmwareUpdateResourceName(entry.entryName);
534
+ const source = await openFirmwareByteSource({
535
+ artifact: entry.artifact,
536
+ reader: params.artifactReader,
537
+ });
538
+ if (!source) {
539
+ throw new Error('Firmware resource entry is not prepared');
540
+ }
541
+ sources.push({ entryName: resourceName, source });
542
+ }
543
+ await updateResourcesFromSources(
544
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
545
+ this.postMessage,
546
+ device,
547
+ sources.map(entry => ({
548
+ entryName: entry.entryName,
549
+ source: entry.source,
550
+ }))
551
+ );
552
+ } finally {
553
+ await Promise.all(
554
+ sources.map(entry => entry.source.close().catch(() => undefined))
555
+ );
556
+ }
557
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
558
+ } else {
559
+ let resourceBinary: ArrayBuffer | Buffer;
560
+ if (params.resourceArtifact) {
561
+ throw ERRORS.TypedError(
562
+ HardwareErrorCode.RuntimeError,
563
+ 'Firmware resource archive must be materialized into prepared entries',
564
+ {
565
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
566
+ artifactName: 'resource',
567
+ }
568
+ );
569
+ } else {
570
+ if (
571
+ params.artifactReader ||
572
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
573
+ ) {
574
+ throw ERRORS.TypedError(
575
+ HardwareErrorCode.RuntimeError,
576
+ 'Firmware resource must be prepared by the external firmware host',
577
+ {
578
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
579
+ artifactName: 'resource',
580
+ }
581
+ );
582
+ }
583
+ resourceBinary = (await getSysResourceBinary(resourceUrl)).binary;
584
+ }
585
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
586
+ await updateResources(
587
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
588
+ this.postMessage,
589
+ device,
590
+ resourceBinary
591
+ );
592
+ }
593
+ }
452
594
  }
453
- delete DevicePool.devicesCache[''];
454
- await this.checkPromise?.promise;
455
- this.checkPromise = null;
456
595
 
457
- // check if the device commands has been disposed
596
+ // The request may outlive the current transport command instance.
458
597
  this.device?.commands?.checkDisposed();
459
598
 
460
- /**
461
- * 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.
462
- */
463
- const isTouch = DeviceModelToTypes.model_touch.includes(deviceType);
464
- await wait(isTouch ? 3000 : 1500);
465
- } catch (e) {
466
- if (e instanceof HardwareError) {
467
- return Promise.reject(e);
599
+ // auto go to bootloader mode
600
+ try {
601
+ this.postTipMessage('AutoRebootToBootloader');
602
+ const bootRes = await commands.typedCall('DeviceBackToBoot', 'Success');
603
+ // @ts-expect-error
604
+ if (bootRes.type === 'CallMethodError') {
605
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
606
+ }
607
+ this.postTipMessage('GoToBootloaderSuccess');
608
+ this.checkDeviceToBootloader(this.payload.connectId);
609
+
610
+ // force clean classic device cache so that the device can initialize again
611
+ if (DeviceModelToTypes.model_classic.includes(deviceType)) {
612
+ DevicePool.clearDeviceCache(serialNo);
613
+ }
614
+ delete DevicePool.devicesCache[''];
615
+ await this.checkPromise?.promise;
616
+ this.checkPromise = null;
617
+
618
+ // check if the device commands has been disposed
619
+ this.device?.commands?.checkDisposed();
620
+
621
+ /**
622
+ * 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.
623
+ */
624
+ const isTouch = DeviceModelToTypes.model_touch.includes(deviceType);
625
+ await wait(isTouch ? 3000 : 1500);
626
+ } catch (e) {
627
+ if (e instanceof HardwareError) {
628
+ return Promise.reject(e);
629
+ }
630
+ console.log('auto go to bootloader mode failed: ', e);
631
+ return Promise.reject(
632
+ ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
633
+ );
468
634
  }
469
- console.log('auto go to bootloader mode failed: ', e);
470
- return Promise.reject(
471
- ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
472
- );
473
635
  }
474
- }
475
636
 
476
- // Devices already in bootloader mode still acquire through the same helper.
477
- const binary = await acquireFirmwareBinary();
637
+ // Devices already in bootloader mode still acquire through the same helper.
638
+ const source = await acquireFirmwareSource();
478
639
 
479
- // check if the device commands has been disposed
480
- this.device?.commands?.checkDisposed();
640
+ // check if the device commands has been disposed
641
+ this.device?.commands?.checkDisposed();
481
642
 
482
- await this.device.acquire();
643
+ await this.device.acquire();
483
644
 
484
- const response = await uploadFirmware(
485
- params.updateType,
486
- this.device.getCommands().typedCall.bind(this.device.getCommands()),
487
- this.postMessage,
488
- device,
489
- { payload: binary, rebootOnSuccess: true },
490
- params.isUpdateBootloader
491
- );
645
+ const response = await uploadFirmwareFromSource(
646
+ params.updateType,
647
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
648
+ this.postMessage,
649
+ device,
650
+ source,
651
+ true,
652
+ params.isUpdateBootloader
653
+ );
492
654
 
493
- if (this.connectId) {
494
- DevicePool.clearDeviceCache(this.connectId);
495
- }
655
+ if (this.connectId) {
656
+ DevicePool.clearDeviceCache(this.connectId);
657
+ }
496
658
 
497
- return response;
659
+ return response;
660
+ } finally {
661
+ await preparedSource?.close().catch(() => undefined);
662
+ }
498
663
  }
499
664
  }