@onekeyfe/hd-core 1.2.0-alpha.40 → 1.2.0-alpha.41

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 (195) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +0 -78
  2. package/__tests__/DeviceEventRegistration.test.ts +0 -6
  3. package/__tests__/connectSettings.test.ts +47 -5
  4. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -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-capabilities.test.ts +13 -0
  9. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  10. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
  11. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  12. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
  13. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  14. package/__tests__/get-device-state.test.ts +12 -132
  15. package/__tests__/homescreen.test.ts +1 -41
  16. package/__tests__/logBlockEvent.test.ts +1 -1
  17. package/__tests__/open-wallet-session.test.ts +18 -259
  18. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -86
  19. package/__tests__/protocol-v2.test.ts +632 -333
  20. package/__tests__/public-pro2-api-boundary.test.ts +0 -1
  21. package/dist/api/BaseMethod.d.ts +1 -3
  22. package/dist/api/BaseMethod.d.ts.map +1 -1
  23. package/dist/api/CheckAllFirmwareRelease.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 +22 -5
  29. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  30. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  31. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  32. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  33. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  34. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  35. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  36. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  37. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  38. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  39. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  40. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  41. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
  42. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  43. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  44. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  45. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  46. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  47. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
  48. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  49. package/dist/api/firmware/getBinary.d.ts +7 -0
  50. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  51. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  52. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  53. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  54. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  55. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  56. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  57. package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -2
  58. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  59. package/dist/api/index.d.ts +0 -1
  60. package/dist/api/index.d.ts.map +1 -1
  61. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  62. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  63. package/dist/core/deviceEventRegistration.d.ts +0 -2
  64. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  65. package/dist/core/index.d.ts.map +1 -1
  66. package/dist/data-manager/DataManager.d.ts +1 -0
  67. package/dist/data-manager/DataManager.d.ts.map +1 -1
  68. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  69. package/dist/device/Device.d.ts +4 -22
  70. package/dist/device/Device.d.ts.map +1 -1
  71. package/dist/events/device.d.ts +0 -4
  72. package/dist/events/device.d.ts.map +1 -1
  73. package/dist/events/logBlockEvent.d.ts.map +1 -1
  74. package/dist/events/ui-request.d.ts +2 -27
  75. package/dist/events/ui-request.d.ts.map +1 -1
  76. package/dist/index.d.ts +181 -88
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3162 -1614
  79. package/dist/inject.d.ts.map +1 -1
  80. package/dist/lowLevelInject.d.ts.map +1 -1
  81. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  82. package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
  83. package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
  84. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  85. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  86. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
  87. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  88. package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
  89. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
  90. package/dist/protocols/protocol-v2/walletSession.d.ts +1 -4
  91. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  92. package/dist/topLevelInject.d.ts.map +1 -1
  93. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
  94. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  95. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  96. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  97. package/dist/types/api/export.d.ts +4 -1
  98. package/dist/types/api/export.d.ts.map +1 -1
  99. package/dist/types/api/firmwareUpdate.d.ts +69 -0
  100. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  101. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  102. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  103. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  104. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  105. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  106. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  107. package/dist/types/api/index.d.ts +10 -3
  108. package/dist/types/api/index.d.ts.map +1 -1
  109. package/dist/types/api/protocolV2.d.ts +0 -3
  110. package/dist/types/api/protocolV2.d.ts.map +1 -1
  111. package/dist/types/settings.d.ts +13 -0
  112. package/dist/types/settings.d.ts.map +1 -1
  113. package/dist/utils/deviceFeaturesUtils.d.ts +0 -2
  114. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  115. package/dist/utils/homescreen.d.ts +0 -4
  116. package/dist/utils/homescreen.d.ts.map +1 -1
  117. package/dist/utils/index.d.ts +1 -1
  118. package/dist/utils/index.d.ts.map +1 -1
  119. package/dist/utils/patch.d.ts +1 -1
  120. package/dist/utils/patch.d.ts.map +1 -1
  121. package/dist/utils/pro2Wallpaper.d.ts +0 -10
  122. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  123. package/package.json +4 -4
  124. package/src/api/BaseMethod.ts +10 -8
  125. package/src/api/CheckAllFirmwareRelease.ts +51 -6
  126. package/src/api/FirmwareUpdateV2.ts +298 -120
  127. package/src/api/FirmwareUpdateV3.ts +263 -57
  128. package/src/api/FirmwareUpdateV4.ts +748 -281
  129. package/src/api/GetPassphraseState.ts +1 -7
  130. package/src/api/OpenWalletSession.ts +17 -54
  131. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -32
  132. package/src/api/device/DeviceUnlock.ts +1 -1
  133. package/src/api/device/DeviceUpdateBootloader.ts +122 -6
  134. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  135. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  136. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  137. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +57 -0
  138. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  139. package/src/api/firmware/FirmwareUpdatePlan.ts +745 -0
  140. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
  141. package/src/api/firmware/progressThrottle.ts +44 -0
  142. package/src/api/firmware/updateBootloader.ts +19 -4
  143. package/src/api/firmware/uploadFirmware.ts +140 -22
  144. package/src/api/helpers/protocolV2FileWrite.ts +4 -11
  145. package/src/api/index.ts +0 -1
  146. package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
  147. package/src/core/deviceEventRegistration.ts +0 -4
  148. package/src/core/index.ts +75 -118
  149. package/src/data/messages/messages-protocol-v2.json +0 -10
  150. package/src/data-manager/DataManager.ts +33 -33
  151. package/src/data-manager/connectSettings.ts +11 -0
  152. package/src/device/Device.ts +21 -193
  153. package/src/events/device.ts +0 -4
  154. package/src/events/logBlockEvent.ts +0 -1
  155. package/src/events/ui-request.ts +2 -32
  156. package/src/index.ts +5 -0
  157. package/src/inject.ts +22 -3
  158. package/src/lowLevelInject.ts +5 -1
  159. package/src/protocols/protocol-v2/features.ts +2 -9
  160. package/src/protocols/protocol-v2/lockedError.ts +10 -0
  161. package/src/protocols/protocol-v2/uiInteraction.ts +5 -31
  162. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
  163. package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
  164. package/src/protocols/protocol-v2/walletSession.ts +18 -93
  165. package/src/topLevelInject.ts +5 -1
  166. package/src/types/api/checkAllFirmwareRelease.ts +4 -0
  167. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  168. package/src/types/api/export.ts +18 -0
  169. package/src/types/api/firmwareUpdate.ts +76 -0
  170. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  171. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  172. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  173. package/src/types/api/index.ts +14 -2
  174. package/src/types/api/protocolV2.ts +0 -13
  175. package/src/types/settings.ts +13 -0
  176. package/src/utils/deviceFeaturesUtils.ts +0 -4
  177. package/src/utils/homescreen.ts +6 -32
  178. package/src/utils/index.ts +1 -6
  179. package/src/utils/pro2Wallpaper.ts +8 -35
  180. package/__tests__/deviceUploadNft.test.ts +0 -187
  181. package/__tests__/pro2Nft.test.ts +0 -108
  182. package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -56
  183. package/__tests__/uiProgressThrottle.test.ts +0 -74
  184. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -30
  185. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
  186. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
  187. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
  188. package/dist/utils/pro2Nft.d.ts +0 -29
  189. package/dist/utils/pro2Nft.d.ts.map +0 -1
  190. package/dist/utils/uiProgressThrottle.d.ts +0 -3
  191. package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
  192. package/src/api/protocol-v2/DeviceUploadNft.ts +0 -166
  193. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -117
  194. package/src/utils/pro2Nft.ts +0 -91
  195. package/src/utils/uiProgressThrottle.ts +0 -63
@@ -1,6 +1,7 @@
1
- import { EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { EFirmwareType, ERRORS, HardwareError, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
  import semver from 'semver';
3
3
 
4
+ import { BaseMethod } from './BaseMethod';
4
5
  import { UI_REQUEST } from '../constants/ui-request';
5
6
  import { DataManager } from '../data-manager';
6
7
  import {
@@ -8,9 +9,18 @@ import {
8
9
  getBootloaderReleaseInfo,
9
10
  getFirmwareReleaseInfo,
10
11
  } from './firmware/releaseHelper';
12
+ import {
13
+ buildFirmwareUpdatePlan,
14
+ validateFirmwareUpdatePlanForceTargets,
15
+ } from './firmware/FirmwareUpdatePlan';
11
16
  import { getBridgeReleaseInfo } from '../utils/bridgeUpdate';
12
- import { getDeviceFirmwareVersion, getDeviceType, getFirmwareType } from '../utils';
13
- import { BaseMethod } from './BaseMethod';
17
+ import {
18
+ LoggerNames,
19
+ getDeviceFirmwareVersion,
20
+ getDeviceType,
21
+ getFirmwareType,
22
+ getLogger,
23
+ } from '../utils';
14
24
 
15
25
  import type {
16
26
  AllFirmwareRelease,
@@ -26,6 +36,7 @@ import type {
26
36
  IProtocolV2FirmwareComponent,
27
37
  IProtocolV2FirmwareComponentTarget,
28
38
  } from '../types';
39
+ import type { FirmwareUpdatePlan } from '../types/api/firmwareUpdatePlan';
29
40
 
30
41
  const UPDATE_TARGET_BY_COMPONENT_TARGET: Readonly<
31
42
  Partial<Record<IProtocolV2FirmwareComponentTarget, FirmwareUpdateV4Target>>
@@ -246,6 +257,8 @@ export function buildProtocolV2FirmwareRelease({
246
257
  };
247
258
  }
248
259
 
260
+ const Log = getLogger(LoggerNames.Method);
261
+
249
262
  export default class CheckAllFirmwareRelease extends BaseMethod {
250
263
  getSupportedProtocols() {
251
264
  return ['V1', 'V2'] as const;
@@ -263,8 +276,13 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
263
276
  }
264
277
 
265
278
  const { features } = this.device;
266
- const { checkBridgeRelease, firmwareType: firmwareTypeParams } = this
267
- .payload as CheckAllFirmwareReleaseParams;
279
+ const {
280
+ checkBridgeRelease,
281
+ firmwareType: firmwareTypeParams,
282
+ platform,
283
+ forceUpdateTargets,
284
+ } = this.payload as CheckAllFirmwareReleaseParams;
285
+ const validatedForceUpdateTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
268
286
 
269
287
  if (!features) {
270
288
  return Promise.resolve(null);
@@ -295,6 +313,32 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
295
313
  firmwareType,
296
314
  });
297
315
  const bleFirmwareReleaseInfo = getBleFirmwareReleaseInfo(features);
316
+ let firmwareUpdatePlan: FirmwareUpdatePlan | undefined;
317
+ try {
318
+ firmwareUpdatePlan = buildFirmwareUpdatePlan({
319
+ features,
320
+ firmwareType,
321
+ platform: platform ?? 'web',
322
+ firmware: firmwareRelease,
323
+ ble: bleFirmwareReleaseInfo,
324
+ bootloader: bootloaderRelease,
325
+ forceUpdateTargets: validatedForceUpdateTargets,
326
+ });
327
+ } catch (error) {
328
+ if (
329
+ validatedForceUpdateTargets.length > 0 ||
330
+ !(error instanceof HardwareError) ||
331
+ error.params?.firmwareUpdateCode !== 'FirmwarePlanInvalid'
332
+ ) {
333
+ throw error;
334
+ }
335
+ // A Plan is an optional external-host capability. Release checks remain
336
+ // available for legacy and recovery flows when a Plan cannot be built.
337
+ Log.warn(
338
+ '[CheckAllFirmwareRelease] Optional firmware Plan is unavailable; using the legacy release result'
339
+ );
340
+ firmwareUpdatePlan = undefined;
341
+ }
298
342
 
299
343
  return {
300
344
  firmware: firmwareRelease,
@@ -309,7 +353,8 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
309
353
  }
310
354
  : undefined,
311
355
  features,
312
- } as AllFirmwareRelease;
356
+ firmwareUpdatePlan,
357
+ } as unknown as AllFirmwareRelease;
313
358
  }
314
359
 
315
360
  private async runProtocolV2(): Promise<AllFirmwareRelease> {
@@ -14,18 +14,44 @@ 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 { LoggerNames, getDeviceUUID, getLogger, wait } from '../utils';
19
24
  import { FirmwareUpdateTipMessage, createUiMessage } from '../events/ui-request';
20
25
  import { DeviceModelToTypes } from '../types';
21
26
  import { DataManager } from '../data-manager';
22
27
  import { DEVICE } from '../events';
28
+ import { type FirmwareByteSource, openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
29
+ import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
30
+ import {
31
+ assertFirmwareUpdatePreparedPlanBinding,
32
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
33
+ getFirmwareUpdateResourceName,
34
+ } from './firmware/FirmwareUpdatePreparedPlan';
23
35
 
24
36
  import type { Features, KnownDevice } from '../types';
25
37
  import type { FirmwareBinary } from './firmware/getBinary';
38
+ import type {
39
+ FirmwareArtifactReader,
40
+ FirmwareArtifactReference,
41
+ } from '../types/api/firmwareUpdate';
42
+ import type { FirmwareUpdatePreparedPlan } from '../types/api/firmwareUpdatePreparedPlan';
26
43
 
27
44
  type Params = {
28
45
  binary?: ArrayBuffer;
46
+ artifact?: FirmwareArtifactReference;
47
+ resourceArtifact?: FirmwareArtifactReference;
48
+ resourceEntries?: Array<{
49
+ entryName: string;
50
+ artifact: FirmwareArtifactReference;
51
+ }>;
52
+ artifactReader?: FirmwareArtifactReader;
53
+ preparedPlan?: FirmwareUpdatePreparedPlan;
54
+ platform?: FirmwareUpdatePreparedPlan['platform'];
29
55
  version?: number[];
30
56
  updateType: 'firmware' | 'ble';
31
57
  forcedUpdateRes?: boolean;
@@ -106,6 +132,13 @@ const normalizeFirmwareBinary = (binary: unknown): FirmwareBinary | undefined =>
106
132
  return normalized.buffer;
107
133
  };
108
134
 
135
+ const toArrayBuffer = (binary: FirmwareBinary): ArrayBuffer => {
136
+ if (binary instanceof ArrayBuffer) {
137
+ return binary;
138
+ }
139
+ return new Uint8Array(binary.buffer, binary.byteOffset, binary.byteLength).slice().buffer;
140
+ };
141
+
109
142
  export default class FirmwareUpdateV2 extends BaseMethod<Params> {
110
143
  checkPromise: Deferred<any> | null = null;
111
144
 
@@ -152,6 +185,38 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
152
185
  binary: payload.binary,
153
186
  };
154
187
  }
188
+
189
+ if ('artifact' in payload) {
190
+ const hostBinding = resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration);
191
+ const target = payload.isUpdateBootloader ? 'bootloader' : payload.updateType;
192
+ const resourceBindings = [
193
+ ...(payload.resourceArtifact
194
+ ? [{ target: 'resource' as const, artifact: payload.resourceArtifact }]
195
+ : []),
196
+ ...(payload.resourceEntries ?? []).map(
197
+ (entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
198
+ target: 'resource' as const,
199
+ entryName: entry.entryName,
200
+ artifact: entry.artifact,
201
+ })
202
+ ),
203
+ ];
204
+ assertFirmwareUpdatePreparedPlanBinding({
205
+ preparedPlan: payload.preparedPlan,
206
+ executor: 'v2',
207
+ platform: payload.platform,
208
+ scopeTargets: [target, ...(target === 'firmware' ? (['resource'] as const) : [])],
209
+ bindings: [{ target, artifact: payload.artifact }, ...resourceBindings],
210
+ });
211
+ this.params = {
212
+ ...this.params,
213
+ preparedPlan: payload.preparedPlan,
214
+ artifact: payload.artifact,
215
+ resourceArtifact: payload.resourceArtifact,
216
+ resourceEntries: payload.resourceEntries,
217
+ artifactReader: hostBinding.artifactReader,
218
+ };
219
+ }
155
220
  }
156
221
 
157
222
  postTipMessage = (message: string) => {
@@ -341,151 +406,264 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
341
406
  const { features, commands } = device;
342
407
  const deviceType = device.getCurrentDeviceType();
343
408
 
409
+ // Protocol V2 (Pro2) uses DeviceFirmwareUpdate and must not enter this legacy flow.
410
+ if (device.isProtocolV2()) {
411
+ throw ERRORS.TypedError(
412
+ HardwareErrorCode.RuntimeError,
413
+ 'Protocol V2 firmware update must use firmwareUpdateV4'
414
+ );
415
+ }
416
+ if (params.preparedPlan) {
417
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
418
+ preparedPlan: params.preparedPlan,
419
+ deviceIdentity: getDeviceUUID(features) || undefined,
420
+ });
421
+ }
422
+
344
423
  const deviceFirmwareType = device.getCurrentFirmwareType();
345
424
  const firmwareType = params.firmwareType ?? deviceFirmwareType;
346
425
 
347
426
  this.checkVersionForCopyTouchResource(features, firmwareType);
348
427
 
349
- let preparedBinary: FirmwareBinary | undefined;
350
- const acquireFirmwareBinary = async (): Promise<FirmwareBinary> => {
351
- try {
352
- if (preparedBinary) {
353
- return preparedBinary;
354
- }
355
-
356
- if (params.binary !== undefined) {
357
- preparedBinary = normalizeFirmwareBinary(params.binary);
358
- if (!preparedBinary) {
359
- throw new Error('firmware binary is empty or invalid');
428
+ let preparedSource: FirmwareByteSource | undefined;
429
+ try {
430
+ const acquireFirmwareSource = async (): Promise<FirmwareByteSource> => {
431
+ try {
432
+ if (preparedSource) {
433
+ return preparedSource;
360
434
  }
361
- return preparedBinary;
362
- }
363
-
364
- if (!device.features) {
365
- throw ERRORS.TypedError(
366
- HardwareErrorCode.RuntimeError,
367
- 'no features found for this device'
368
- );
369
- }
370
435
 
371
- this.postTipMessage('DownloadFirmware');
372
- const firmware = await getBinary({
373
- features: device.features,
374
- version: params.version,
375
- updateType: params.updateType,
376
- isUpdateBootloader: params.isUpdateBootloader,
377
- firmwareType,
378
- requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
379
- });
380
- preparedBinary = normalizeFirmwareBinary(firmware.binary);
381
- if (!preparedBinary) {
382
- throw new Error('downloaded firmware binary is empty or invalid');
383
- }
384
- this.postTipMessage('DownloadFirmwareSuccess');
385
- return preparedBinary;
386
- } catch (err) {
387
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
388
- }
389
- };
436
+ if (params.binary !== undefined) {
437
+ const binary = normalizeFirmwareBinary(params.binary);
438
+ if (!binary) {
439
+ throw new Error('firmware binary is empty or invalid');
440
+ }
441
+ const source = await openFirmwareByteSource({
442
+ binary: toArrayBuffer(binary),
443
+ });
444
+ if (!source) {
445
+ throw new Error('firmware binary is empty or invalid');
446
+ }
447
+ preparedSource = source;
448
+ return source;
449
+ }
390
450
 
391
- if (!device.isBootloader() && features) {
392
- const serialNo = device.getCurrentSerialNo();
393
- // should go to bootloader mode manually
394
- if (this.isEnteredManuallyBoot()) {
395
- return Promise.reject(ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot));
396
- }
451
+ if (params.artifact) {
452
+ const source = await openFirmwareByteSource({
453
+ artifact: params.artifact,
454
+ reader: params.artifactReader,
455
+ });
456
+ if (!source) {
457
+ throw new Error('firmware artifact is not prepared');
458
+ }
459
+ preparedSource = source;
460
+ return source;
461
+ }
397
462
 
398
- // check & upgrade firmware resource
399
- if (this.isSupportResourceUpdate(params.updateType)) {
400
- this.postTipMessage('CheckLatestUiResource');
401
- const resourceUrl = DataManager.getSysResourcesLatestRelease({
402
- features,
403
- forcedUpdateRes: params.forcedUpdateRes,
404
- firmwareType,
405
- });
406
- if (resourceUrl) {
407
- this.postTipMessage('DownloadLatestUiResource');
408
- const resource = await getSysResourceBinary(resourceUrl);
409
- this.postTipMessage('DownloadLatestUiResourceSuccess');
410
- if (resource) {
411
- await updateResources(
412
- this.device.getCommands().typedCall.bind(this.device.getCommands()),
413
- this.postMessage,
414
- device,
415
- resource.binary
463
+ if (!device.features) {
464
+ throw ERRORS.TypedError(
465
+ HardwareErrorCode.RuntimeError,
466
+ 'no features found for this device'
416
467
  );
417
468
  }
418
- }
419
- }
420
-
421
- // check if the device commands has been disposed
422
- this.device?.commands?.checkDisposed();
423
469
 
424
- // A failed firmware download must leave the device in normal mode.
425
- await acquireFirmwareBinary();
470
+ if (
471
+ params.artifactReader ||
472
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
473
+ ) {
474
+ throw ERRORS.TypedError(
475
+ HardwareErrorCode.RuntimeError,
476
+ 'Firmware must be prepared by the external firmware host',
477
+ {
478
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
479
+ artifactName: 'firmware',
480
+ }
481
+ );
482
+ }
426
483
 
427
- // The request may outlive the current transport command instance.
428
- this.device?.commands?.checkDisposed();
484
+ this.postTipMessage('DownloadFirmware');
485
+ const firmware = await getBinary({
486
+ features: device.features,
487
+ version: params.version,
488
+ updateType: params.updateType,
489
+ isUpdateBootloader: params.isUpdateBootloader,
490
+ firmwareType,
491
+ requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
492
+ });
493
+ const binary = normalizeFirmwareBinary(firmware.binary);
494
+ if (!binary) {
495
+ throw new Error('downloaded firmware binary is empty or invalid');
496
+ }
497
+ const source = await openFirmwareByteSource({
498
+ binary: toArrayBuffer(binary),
499
+ });
500
+ if (!source) {
501
+ throw new Error('downloaded firmware binary is empty or invalid');
502
+ }
503
+ preparedSource = source;
504
+ this.postTipMessage('DownloadFirmwareSuccess');
505
+ return source;
506
+ } catch (err) {
507
+ throw normalizeFirmwarePreparationError(err);
508
+ }
509
+ };
429
510
 
430
- // auto go to bootloader mode
431
- try {
432
- this.postTipMessage('AutoRebootToBootloader');
433
- const bootRes = await commands.typedCall('DeviceBackToBoot', 'Success');
434
- // @ts-expect-error
435
- if (bootRes.type === 'CallMethodError') {
436
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
511
+ if (!device.isBootloader() && features) {
512
+ const serialNo = device.getCurrentSerialNo();
513
+ // should go to bootloader mode manually
514
+ if (this.isEnteredManuallyBoot()) {
515
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot);
437
516
  }
438
- this.postTipMessage('GoToBootloaderSuccess');
439
- this.checkDeviceToBootloader(this.payload.connectId);
440
517
 
441
- // force clean classic device cache so that the device can initialize again
442
- if (DeviceModelToTypes.model_classic.includes(deviceType)) {
443
- DevicePool.clearDeviceCache(serialNo);
518
+ // All network-backed input must be ready before the first device mutation.
519
+ await acquireFirmwareSource();
520
+
521
+ // check & upgrade firmware resource
522
+ if (this.isSupportResourceUpdate(params.updateType)) {
523
+ this.postTipMessage('CheckLatestUiResource');
524
+ const resourceUrl = DataManager.getSysResourcesLatestRelease({
525
+ features,
526
+ forcedUpdateRes: params.forcedUpdateRes,
527
+ firmwareType,
528
+ });
529
+ if (resourceUrl) {
530
+ this.postTipMessage('DownloadLatestUiResource');
531
+ if (params.resourceEntries?.length) {
532
+ const sources: Array<{
533
+ entryName: string;
534
+ source: FirmwareByteSource;
535
+ }> = [];
536
+ try {
537
+ for (const entry of params.resourceEntries) {
538
+ const resourceName = getFirmwareUpdateResourceName(entry.entryName);
539
+ const source = await openFirmwareByteSource({
540
+ artifact: entry.artifact,
541
+ reader: params.artifactReader,
542
+ });
543
+ if (!source) {
544
+ throw new Error('Firmware resource entry is not prepared');
545
+ }
546
+ sources.push({ entryName: resourceName, source });
547
+ }
548
+ await updateResourcesFromSources(
549
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
550
+ this.postMessage,
551
+ device,
552
+ sources.map(entry => ({
553
+ entryName: entry.entryName,
554
+ source: entry.source,
555
+ }))
556
+ );
557
+ } finally {
558
+ await Promise.all(
559
+ sources.map(entry => entry.source.close().catch(() => undefined))
560
+ );
561
+ }
562
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
563
+ } else {
564
+ let resourceBinary: ArrayBuffer | Buffer;
565
+ if (params.resourceArtifact) {
566
+ throw ERRORS.TypedError(
567
+ HardwareErrorCode.RuntimeError,
568
+ 'Firmware resource archive must be materialized into prepared entries',
569
+ {
570
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
571
+ artifactName: 'resource',
572
+ }
573
+ );
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
+ resourceBinary = (await getSysResourceBinary(resourceUrl)).binary;
589
+ }
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
+ }
444
599
  }
445
- delete DevicePool.devicesCache[''];
446
- await this.checkPromise?.promise;
447
- this.checkPromise = null;
448
600
 
449
- // check if the device commands has been disposed
601
+ // The request may outlive the current transport command instance.
450
602
  this.device?.commands?.checkDisposed();
451
603
 
452
- /**
453
- * 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.
454
- */
455
- const isTouch = DeviceModelToTypes.model_touch.includes(deviceType);
456
- await wait(isTouch ? 3000 : 1500);
457
- } catch (e) {
458
- if (e instanceof HardwareError) {
459
- 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
+ );
460
639
  }
461
- console.log('auto go to bootloader mode failed: ', e);
462
- return Promise.reject(
463
- ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
464
- );
465
640
  }
466
- }
467
641
 
468
- // Devices already in bootloader mode still acquire through the same helper.
469
- const binary = await acquireFirmwareBinary();
642
+ // Devices already in bootloader mode still acquire through the same helper.
643
+ const source = await acquireFirmwareSource();
470
644
 
471
- // check if the device commands has been disposed
472
- this.device?.commands?.checkDisposed();
645
+ // check if the device commands has been disposed
646
+ this.device?.commands?.checkDisposed();
473
647
 
474
- await this.device.acquire();
648
+ await this.device.acquire();
475
649
 
476
- const response = await uploadFirmware(
477
- params.updateType,
478
- this.device.getCommands().typedCall.bind(this.device.getCommands()),
479
- this.postMessage,
480
- device,
481
- { payload: binary, rebootOnSuccess: true },
482
- params.isUpdateBootloader
483
- );
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
+ );
484
659
 
485
- if (this.connectId) {
486
- DevicePool.clearDeviceCache(this.connectId);
487
- }
660
+ if (this.connectId) {
661
+ DevicePool.clearDeviceCache(this.connectId);
662
+ }
488
663
 
489
- return response;
664
+ return response;
665
+ } finally {
666
+ await preparedSource?.close().catch(() => undefined);
667
+ }
490
668
  }
491
669
  }