@onekeyfe/hd-core 1.2.0-alpha.50 → 1.2.0-alpha.51

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 (234) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +0 -78
  2. package/__tests__/DeviceEventRegistration.test.ts +0 -6
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +3 -111
  4. package/__tests__/connectSettings.test.ts +47 -5
  5. package/__tests__/device-initialize-timeout.test.ts +56 -0
  6. package/__tests__/device-lifecycle-events.test.ts +3 -105
  7. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  8. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  9. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  10. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  11. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
  12. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  13. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  14. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
  15. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  16. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
  17. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  18. package/__tests__/get-device-state.test.ts +12 -132
  19. package/__tests__/homescreen.test.ts +1 -41
  20. package/__tests__/logBlockEvent.test.ts +1 -1
  21. package/__tests__/method-protocol-support.test.ts +0 -19
  22. package/__tests__/open-wallet-session.test.ts +33 -446
  23. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -196
  24. package/__tests__/protocol-v2.test.ts +1032 -1056
  25. package/__tests__/protocolV2UiInteraction.test.ts +0 -63
  26. package/__tests__/public-pro2-api-boundary.test.ts +0 -1
  27. package/__tests__/search-devices.test.ts +3 -12
  28. package/dist/api/BaseMethod.d.ts +1 -3
  29. package/dist/api/BaseMethod.d.ts.map +1 -1
  30. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  31. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  32. package/dist/api/FirmwareUpdateV2.d.ts +11 -0
  33. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  34. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  35. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  36. package/dist/api/FirmwareUpdateV4.d.ts +25 -9
  37. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  38. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  39. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  40. package/dist/api/SearchDevices.d.ts.map +1 -1
  41. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  42. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  43. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  44. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  45. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  46. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  47. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  48. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  49. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  50. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +9 -0
  51. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  52. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  53. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  54. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  55. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  56. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
  57. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  58. package/dist/api/firmware/getBinary.d.ts +7 -0
  59. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  60. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  61. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  62. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  63. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  64. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  65. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  66. package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -2
  67. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  68. package/dist/api/index.d.ts +0 -2
  69. package/dist/api/index.d.ts.map +1 -1
  70. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  71. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  72. package/dist/core/deviceEventRegistration.d.ts +0 -2
  73. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  74. package/dist/core/index.d.ts.map +1 -1
  75. package/dist/data-manager/DataManager.d.ts +2 -4
  76. package/dist/data-manager/DataManager.d.ts.map +1 -1
  77. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  78. package/dist/device/Device.d.ts +6 -29
  79. package/dist/device/Device.d.ts.map +1 -1
  80. package/dist/device/DeviceCommands.d.ts +4 -4
  81. package/dist/device/DevicePool.d.ts.map +1 -1
  82. package/dist/events/device.d.ts +0 -4
  83. package/dist/events/device.d.ts.map +1 -1
  84. package/dist/events/logBlockEvent.d.ts.map +1 -1
  85. package/dist/events/ui-promise.d.ts +2 -4
  86. package/dist/events/ui-promise.d.ts.map +1 -1
  87. package/dist/events/ui-request.d.ts +2 -31
  88. package/dist/events/ui-request.d.ts.map +1 -1
  89. package/dist/events/ui-response.d.ts +2 -10
  90. package/dist/events/ui-response.d.ts.map +1 -1
  91. package/dist/index.d.ts +230 -203
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +3445 -2419
  94. package/dist/inject.d.ts +1 -6
  95. package/dist/inject.d.ts.map +1 -1
  96. package/dist/lowLevelInject.d.ts.map +1 -1
  97. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  98. package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
  99. package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
  100. package/dist/protocols/protocol-v2/uiInteraction.d.ts +4 -7
  101. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  102. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
  103. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  104. package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
  105. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
  106. package/dist/protocols/protocol-v2/walletSession.d.ts +1 -4
  107. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  108. package/dist/topLevelInject.d.ts.map +1 -1
  109. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -1
  110. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  111. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  112. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  113. package/dist/types/api/export.d.ts +4 -1
  114. package/dist/types/api/export.d.ts.map +1 -1
  115. package/dist/types/api/firmwareUpdate.d.ts +70 -2
  116. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  117. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  118. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  119. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  120. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  121. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  122. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  123. package/dist/types/api/index.d.ts +10 -7
  124. package/dist/types/api/index.d.ts.map +1 -1
  125. package/dist/types/api/protocolV2.d.ts +0 -3
  126. package/dist/types/api/protocolV2.d.ts.map +1 -1
  127. package/dist/types/params.d.ts +0 -1
  128. package/dist/types/params.d.ts.map +1 -1
  129. package/dist/types/settings.d.ts +13 -22
  130. package/dist/types/settings.d.ts.map +1 -1
  131. package/dist/utils/deviceFeaturesUtils.d.ts +0 -2
  132. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  133. package/dist/utils/homescreen.d.ts +0 -4
  134. package/dist/utils/homescreen.d.ts.map +1 -1
  135. package/dist/utils/index.d.ts +1 -1
  136. package/dist/utils/index.d.ts.map +1 -1
  137. package/dist/utils/patch.d.ts +1 -1
  138. package/dist/utils/patch.d.ts.map +1 -1
  139. package/dist/utils/pro2Wallpaper.d.ts +0 -10
  140. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  141. package/package.json +4 -4
  142. package/src/api/BaseMethod.ts +10 -8
  143. package/src/api/CheckAllFirmwareRelease.ts +53 -41
  144. package/src/api/FirmwareUpdate.ts +15 -7
  145. package/src/api/FirmwareUpdateV2.ts +313 -126
  146. package/src/api/FirmwareUpdateV3.ts +265 -63
  147. package/src/api/FirmwareUpdateV4.ts +937 -430
  148. package/src/api/GetPassphraseState.ts +1 -7
  149. package/src/api/OpenWalletSession.ts +17 -54
  150. package/src/api/SearchDevices.ts +1 -3
  151. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -32
  152. package/src/api/device/DeviceUnlock.ts +1 -1
  153. package/src/api/device/DeviceUpdateBootloader.ts +122 -6
  154. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  155. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  156. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  157. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +83 -16
  158. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  159. package/src/api/firmware/FirmwareUpdatePlan.ts +745 -0
  160. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
  161. package/src/api/firmware/progressThrottle.ts +44 -0
  162. package/src/api/firmware/updateBootloader.ts +19 -4
  163. package/src/api/firmware/uploadFirmware.ts +144 -39
  164. package/src/api/helpers/protocolV2FileWrite.ts +4 -11
  165. package/src/api/index.ts +0 -2
  166. package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
  167. package/src/core/deviceEventRegistration.ts +0 -4
  168. package/src/core/index.ts +83 -167
  169. package/src/data/messages/messages-protocol-v2.json +0 -25
  170. package/src/data-manager/DataManager.ts +38 -67
  171. package/src/data-manager/connectSettings.ts +11 -0
  172. package/src/device/Device.ts +42 -257
  173. package/src/device/DevicePool.ts +2 -7
  174. package/src/events/device.ts +0 -4
  175. package/src/events/logBlockEvent.ts +0 -1
  176. package/src/events/ui-promise.ts +2 -4
  177. package/src/events/ui-request.ts +2 -36
  178. package/src/events/ui-response.ts +2 -12
  179. package/src/index.ts +5 -0
  180. package/src/inject.ts +24 -60
  181. package/src/lowLevelInject.ts +8 -7
  182. package/src/protocols/protocol-v2/features.ts +2 -9
  183. package/src/protocols/protocol-v2/lockedError.ts +10 -0
  184. package/src/protocols/protocol-v2/uiInteraction.ts +6 -46
  185. package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
  186. package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
  187. package/src/protocols/protocol-v2/walletSession.ts +30 -169
  188. package/src/topLevelInject.ts +8 -7
  189. package/src/types/api/checkAllFirmwareRelease.ts +4 -1
  190. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  191. package/src/types/api/export.ts +18 -0
  192. package/src/types/api/firmwareUpdate.ts +76 -3
  193. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  194. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  195. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  196. package/src/types/api/index.ts +14 -13
  197. package/src/types/api/protocolV2.ts +0 -13
  198. package/src/types/params.ts +1 -7
  199. package/src/types/settings.ts +13 -42
  200. package/src/utils/deviceFeaturesUtils.ts +0 -4
  201. package/src/utils/homescreen.ts +6 -32
  202. package/src/utils/index.ts +1 -6
  203. package/src/utils/pro2Wallpaper.ts +8 -35
  204. package/__tests__/deviceUploadNft.test.ts +0 -293
  205. package/__tests__/pro2Nft.test.ts +0 -108
  206. package/__tests__/protocol-binding.test.ts +0 -89
  207. package/__tests__/protocol-v2-resources.test.ts +0 -284
  208. package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -91
  209. package/__tests__/uiProgressThrottle.test.ts +0 -74
  210. package/__tests__/uiPromiseRegistry.test.ts +0 -86
  211. package/dist/api/DetectDeviceConnectProtocol.d.ts +0 -7
  212. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +0 -1
  213. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -31
  214. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
  215. package/dist/core/uiPromiseRegistry.d.ts +0 -4
  216. package/dist/core/uiPromiseRegistry.d.ts.map +0 -1
  217. package/dist/protocols/protocol-v2/resources.d.ts +0 -30
  218. package/dist/protocols/protocol-v2/resources.d.ts.map +0 -1
  219. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
  220. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
  221. package/dist/types/api/detectDeviceConnectProtocol.d.ts +0 -4
  222. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +0 -1
  223. package/dist/utils/pro2Nft.d.ts +0 -31
  224. package/dist/utils/pro2Nft.d.ts.map +0 -1
  225. package/dist/utils/uiProgressThrottle.d.ts +0 -3
  226. package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
  227. package/src/api/DetectDeviceConnectProtocol.ts +0 -18
  228. package/src/api/protocol-v2/DeviceUploadNft.ts +0 -189
  229. package/src/core/uiPromiseRegistry.ts +0 -41
  230. package/src/protocols/protocol-v2/resources.ts +0 -359
  231. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -124
  232. package/src/types/api/detectDeviceConnectProtocol.ts +0 -7
  233. package/src/utils/pro2Nft.ts +0 -142
  234. package/src/utils/uiProgressThrottle.ts +0 -63
@@ -10,6 +10,7 @@ import { UI_REQUEST } from '../constants/ui-request';
10
10
  import { BaseMethod } from './BaseMethod';
11
11
  import { validateParams } from './helpers/paramsValidator';
12
12
  import { getBinary } from './firmware/getBinary';
13
+ import { BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS } from './firmware/FirmwareUpdateBaseMethod';
13
14
  import { uploadFirmware } from './firmware/uploadFirmware';
14
15
  import { createUiMessage } from '../events';
15
16
  import { DeviceModelToTypes, type KnownDevice } from '../types';
@@ -78,6 +79,10 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
78
79
  this.checkPromise = createDeferred();
79
80
  const env = DataManager.getSettings('env');
80
81
  const isBleReconnect = connectId && DataManager.isBleConnect(env);
82
+ // Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
83
+ // Initialize left pending on the V1 transport is exactly what later times out
84
+ // and used to tear down the connection mid-firmware-upload.
85
+ let bleProbeInFlight = false;
81
86
 
82
87
  Log.log('FirmwareUpdate [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
83
88
 
@@ -85,14 +90,18 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
85
90
  const intervalTimer: ReturnType<typeof setInterval> | undefined = setInterval(
86
91
  async () => {
87
92
  if (isBleReconnect) {
93
+ if (bleProbeInFlight) return;
94
+ bleProbeInFlight = true;
88
95
  try {
89
96
  await this.device.deviceConnector?.acquire(
90
97
  this.device.originalDescriptor.id,
91
98
  null,
92
- true,
93
- this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
99
+ true
94
100
  );
95
- await this.device.initialize();
101
+ // Bound each probe so a request the rebooting device never received
102
+ // frees the slot for the next tick instead of hanging into the
103
+ // 30s reboot budget.
104
+ await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
96
105
  if (this.device.isBootloader()) {
97
106
  clearInterval(intervalTimer);
98
107
  this.checkPromise?.resolve(true);
@@ -100,14 +109,13 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
100
109
  } catch (e) {
101
110
  // ignore error because of device is not connected
102
111
  Log.log('catch Bluetooth error when device is restarting: ', e);
112
+ } finally {
113
+ bleProbeInFlight = false;
103
114
  }
104
115
  } else {
105
116
  const deviceDiff = await this.device.deviceConnector?.enumerate();
106
117
  const devicesDescriptor = deviceDiff?.descriptors ?? [];
107
- const { deviceList } = await DevicePool.getDevices(devicesDescriptor, connectId, {
108
- connectProtocol:
109
- this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType,
110
- });
118
+ const { deviceList } = await DevicePool.getDevices(devicesDescriptor, connectId);
111
119
 
112
120
  if (deviceList.length === 1 && deviceList[0]?.isBootloader()) {
113
121
  // should update current device from cache
@@ -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, getDeviceUUID, getLogger, wait } from '../utils';
19
25
  import { FirmwareUpdateTipMessage, createUiMessage } from '../events/ui-request';
20
26
  import { DeviceModelToTypes } from '../types';
21
27
  import { DataManager } from '../data-manager';
22
28
  import { DEVICE } from '../events';
29
+ import { type FirmwareByteSource, openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
30
+ import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
31
+ import {
32
+ assertFirmwareUpdatePreparedPlanBinding,
33
+ assertFirmwareUpdatePreparedPlanDeviceIdentity,
34
+ getFirmwareUpdateResourceName,
35
+ } from './firmware/FirmwareUpdatePreparedPlan';
23
36
 
24
37
  import type { Features, KnownDevice } from '../types';
25
38
  import type { FirmwareBinary } from './firmware/getBinary';
39
+ import type {
40
+ FirmwareArtifactReader,
41
+ FirmwareArtifactReference,
42
+ } from '../types/api/firmwareUpdate';
43
+ import type { FirmwareUpdatePreparedPlan } from '../types/api/firmwareUpdatePreparedPlan';
26
44
 
27
45
  type Params = {
28
46
  binary?: ArrayBuffer;
47
+ artifact?: FirmwareArtifactReference;
48
+ resourceArtifact?: FirmwareArtifactReference;
49
+ resourceEntries?: Array<{
50
+ entryName: string;
51
+ artifact: FirmwareArtifactReference;
52
+ }>;
53
+ artifactReader?: FirmwareArtifactReader;
54
+ preparedPlan?: FirmwareUpdatePreparedPlan;
55
+ platform?: FirmwareUpdatePreparedPlan['platform'];
29
56
  version?: number[];
30
57
  updateType: 'firmware' | 'ble';
31
58
  forcedUpdateRes?: boolean;
@@ -106,6 +133,13 @@ const normalizeFirmwareBinary = (binary: unknown): FirmwareBinary | undefined =>
106
133
  return normalized.buffer;
107
134
  };
108
135
 
136
+ const toArrayBuffer = (binary: FirmwareBinary): ArrayBuffer => {
137
+ if (binary instanceof ArrayBuffer) {
138
+ return binary;
139
+ }
140
+ return new Uint8Array(binary.buffer, binary.byteOffset, binary.byteLength).slice().buffer;
141
+ };
142
+
109
143
  export default class FirmwareUpdateV2 extends BaseMethod<Params> {
110
144
  checkPromise: Deferred<any> | null = null;
111
145
 
@@ -152,6 +186,38 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
152
186
  binary: payload.binary,
153
187
  };
154
188
  }
189
+
190
+ if ('artifact' in payload) {
191
+ const hostBinding = resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration);
192
+ const target = payload.isUpdateBootloader ? 'bootloader' : payload.updateType;
193
+ const resourceBindings = [
194
+ ...(payload.resourceArtifact
195
+ ? [{ target: 'resource' as const, artifact: payload.resourceArtifact }]
196
+ : []),
197
+ ...(payload.resourceEntries ?? []).map(
198
+ (entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
199
+ target: 'resource' as const,
200
+ entryName: entry.entryName,
201
+ artifact: entry.artifact,
202
+ })
203
+ ),
204
+ ];
205
+ assertFirmwareUpdatePreparedPlanBinding({
206
+ preparedPlan: payload.preparedPlan,
207
+ executor: 'v2',
208
+ platform: payload.platform,
209
+ scopeTargets: [target, ...(target === 'firmware' ? (['resource'] as const) : [])],
210
+ bindings: [{ target, artifact: payload.artifact }, ...resourceBindings],
211
+ });
212
+ this.params = {
213
+ ...this.params,
214
+ preparedPlan: payload.preparedPlan,
215
+ artifact: payload.artifact,
216
+ resourceArtifact: payload.resourceArtifact,
217
+ resourceEntries: payload.resourceEntries,
218
+ artifactReader: hostBinding.artifactReader,
219
+ };
220
+ }
155
221
  }
156
222
 
157
223
  postTipMessage = (message: string) => {
@@ -187,6 +253,10 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
187
253
  this.checkPromise = createDeferred();
188
254
  const env = DataManager.getSettings('env');
189
255
  const isBleReconnect = connectId && DataManager.isBleConnect(env);
256
+ // Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
257
+ // Initialize left pending on the V1 transport is exactly what later times out
258
+ // and used to tear down the connection mid-firmware-upload.
259
+ let bleProbeInFlight = false;
190
260
 
191
261
  Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
192
262
 
@@ -234,14 +304,18 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
234
304
  }
235
305
 
236
306
  if (isBleReconnect) {
307
+ if (bleProbeInFlight) return;
308
+ bleProbeInFlight = true;
237
309
  try {
238
310
  await this.device.deviceConnector?.acquire(
239
311
  this.device.originalDescriptor.id,
240
312
  null,
241
- true,
242
- this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
313
+ true
243
314
  );
244
- await this.device.initialize();
315
+ // Bound each probe so a request the rebooting device never received
316
+ // frees the slot for the next tick instead of hanging into the
317
+ // 30s reboot budget.
318
+ await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
245
319
  if (this.device.isBootloader()) {
246
320
  clearInterval(intervalTimer);
247
321
  this.checkPromise?.resolve(true);
@@ -249,6 +323,8 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
249
323
  } catch (e) {
250
324
  // ignore error because of device is not connected
251
325
  Log.log('catch Bluetooth error when device is restarting: ', e);
326
+ } finally {
327
+ bleProbeInFlight = false;
252
328
  }
253
329
  } else {
254
330
  await this._checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer);
@@ -273,9 +349,7 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
273
349
  ) {
274
350
  const deviceDiff = await this.device.deviceConnector?.enumerate();
275
351
  const devicesDescriptor = deviceDiff?.descriptors ?? [];
276
- const { deviceList } = await DevicePool.getDevices(devicesDescriptor, connectId, {
277
- connectProtocol: this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType,
278
- });
352
+ const { deviceList } = await DevicePool.getDevices(devicesDescriptor, connectId);
279
353
 
280
354
  if (deviceList.length === 1 && deviceList[0]?.isBootloader()) {
281
355
  // should update current device from cache
@@ -344,151 +418,264 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
344
418
  const { features, commands } = device;
345
419
  const deviceType = device.getCurrentDeviceType();
346
420
 
421
+ // Protocol V2 (Pro2) uses DeviceFirmwareUpdate and must not enter this legacy flow.
422
+ if (device.isProtocolV2()) {
423
+ throw ERRORS.TypedError(
424
+ HardwareErrorCode.RuntimeError,
425
+ 'Protocol V2 firmware update must use firmwareUpdateV4'
426
+ );
427
+ }
428
+ if (params.preparedPlan) {
429
+ assertFirmwareUpdatePreparedPlanDeviceIdentity({
430
+ preparedPlan: params.preparedPlan,
431
+ deviceIdentity: getDeviceUUID(features) || undefined,
432
+ });
433
+ }
434
+
347
435
  const deviceFirmwareType = device.getCurrentFirmwareType();
348
436
  const firmwareType = params.firmwareType ?? deviceFirmwareType;
349
437
 
350
438
  this.checkVersionForCopyTouchResource(features, firmwareType);
351
439
 
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');
440
+ let preparedSource: FirmwareByteSource | undefined;
441
+ try {
442
+ const acquireFirmwareSource = async (): Promise<FirmwareByteSource> => {
443
+ try {
444
+ if (preparedSource) {
445
+ return preparedSource;
363
446
  }
364
- return preparedBinary;
365
- }
366
447
 
367
- if (!device.features) {
368
- throw ERRORS.TypedError(
369
- HardwareErrorCode.RuntimeError,
370
- 'no features found for this device'
371
- );
372
- }
373
-
374
- this.postTipMessage('DownloadFirmware');
375
- const firmware = await getBinary({
376
- features: device.features,
377
- version: params.version,
378
- updateType: params.updateType,
379
- isUpdateBootloader: params.isUpdateBootloader,
380
- firmwareType,
381
- requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
382
- });
383
- preparedBinary = normalizeFirmwareBinary(firmware.binary);
384
- if (!preparedBinary) {
385
- throw new Error('downloaded firmware binary is empty or invalid');
386
- }
387
- this.postTipMessage('DownloadFirmwareSuccess');
388
- return preparedBinary;
389
- } catch (err) {
390
- throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
391
- }
392
- };
448
+ if (params.binary !== undefined) {
449
+ const binary = normalizeFirmwareBinary(params.binary);
450
+ if (!binary) {
451
+ throw new Error('firmware binary is empty or invalid');
452
+ }
453
+ const source = await openFirmwareByteSource({
454
+ binary: toArrayBuffer(binary),
455
+ });
456
+ if (!source) {
457
+ throw new Error('firmware binary is empty or invalid');
458
+ }
459
+ preparedSource = source;
460
+ return source;
461
+ }
393
462
 
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
- }
463
+ if (params.artifact) {
464
+ const source = await openFirmwareByteSource({
465
+ artifact: params.artifact,
466
+ reader: params.artifactReader,
467
+ });
468
+ if (!source) {
469
+ throw new Error('firmware artifact is not prepared');
470
+ }
471
+ preparedSource = source;
472
+ return source;
473
+ }
400
474
 
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
475
+ if (!device.features) {
476
+ throw ERRORS.TypedError(
477
+ HardwareErrorCode.RuntimeError,
478
+ 'no features found for this device'
419
479
  );
420
480
  }
421
- }
422
- }
423
-
424
- // check if the device commands has been disposed
425
- this.device?.commands?.checkDisposed();
426
481
 
427
- // A failed firmware download must leave the device in normal mode.
428
- await acquireFirmwareBinary();
482
+ if (
483
+ params.artifactReader ||
484
+ DataManager.getSettings('firmwareManifestMode') === 'external-only'
485
+ ) {
486
+ throw ERRORS.TypedError(
487
+ HardwareErrorCode.RuntimeError,
488
+ 'Firmware must be prepared by the external firmware host',
489
+ {
490
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
491
+ artifactName: 'firmware',
492
+ }
493
+ );
494
+ }
429
495
 
430
- // The request may outlive the current transport command instance.
431
- this.device?.commands?.checkDisposed();
496
+ this.postTipMessage('DownloadFirmware');
497
+ const firmware = await getBinary({
498
+ features: device.features,
499
+ version: params.version,
500
+ updateType: params.updateType,
501
+ isUpdateBootloader: params.isUpdateBootloader,
502
+ firmwareType,
503
+ requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
504
+ });
505
+ const binary = normalizeFirmwareBinary(firmware.binary);
506
+ if (!binary) {
507
+ throw new Error('downloaded firmware binary is empty or invalid');
508
+ }
509
+ const source = await openFirmwareByteSource({
510
+ binary: toArrayBuffer(binary),
511
+ });
512
+ if (!source) {
513
+ throw new Error('downloaded firmware binary is empty or invalid');
514
+ }
515
+ preparedSource = source;
516
+ this.postTipMessage('DownloadFirmwareSuccess');
517
+ return source;
518
+ } catch (err) {
519
+ throw normalizeFirmwarePreparationError(err);
520
+ }
521
+ };
432
522
 
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);
523
+ if (!device.isBootloader() && features) {
524
+ const serialNo = device.getCurrentSerialNo();
525
+ // should go to bootloader mode manually
526
+ if (this.isEnteredManuallyBoot()) {
527
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot);
440
528
  }
441
- this.postTipMessage('GoToBootloaderSuccess');
442
- this.checkDeviceToBootloader(this.payload.connectId);
443
529
 
444
- // force clean classic device cache so that the device can initialize again
445
- if (DeviceModelToTypes.model_classic.includes(deviceType)) {
446
- DevicePool.clearDeviceCache(serialNo);
530
+ // All network-backed input must be ready before the first device mutation.
531
+ await acquireFirmwareSource();
532
+
533
+ // check & upgrade firmware resource
534
+ if (this.isSupportResourceUpdate(params.updateType)) {
535
+ this.postTipMessage('CheckLatestUiResource');
536
+ const resourceUrl = DataManager.getSysResourcesLatestRelease({
537
+ features,
538
+ forcedUpdateRes: params.forcedUpdateRes,
539
+ firmwareType,
540
+ });
541
+ if (resourceUrl) {
542
+ this.postTipMessage('DownloadLatestUiResource');
543
+ if (params.resourceEntries?.length) {
544
+ const sources: Array<{
545
+ entryName: string;
546
+ source: FirmwareByteSource;
547
+ }> = [];
548
+ try {
549
+ for (const entry of params.resourceEntries) {
550
+ const resourceName = getFirmwareUpdateResourceName(entry.entryName);
551
+ const source = await openFirmwareByteSource({
552
+ artifact: entry.artifact,
553
+ reader: params.artifactReader,
554
+ });
555
+ if (!source) {
556
+ throw new Error('Firmware resource entry is not prepared');
557
+ }
558
+ sources.push({ entryName: resourceName, source });
559
+ }
560
+ await updateResourcesFromSources(
561
+ this.device.getCommands().typedCall.bind(this.device.getCommands()),
562
+ this.postMessage,
563
+ device,
564
+ sources.map(entry => ({
565
+ entryName: entry.entryName,
566
+ source: entry.source,
567
+ }))
568
+ );
569
+ } finally {
570
+ await Promise.all(
571
+ sources.map(entry => entry.source.close().catch(() => undefined))
572
+ );
573
+ }
574
+ this.postTipMessage('DownloadLatestUiResourceSuccess');
575
+ } else {
576
+ let resourceBinary: ArrayBuffer | Buffer;
577
+ if (params.resourceArtifact) {
578
+ throw ERRORS.TypedError(
579
+ HardwareErrorCode.RuntimeError,
580
+ 'Firmware resource archive must be materialized into prepared entries',
581
+ {
582
+ firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
583
+ artifactName: 'resource',
584
+ }
585
+ );
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
+ resourceBinary = (await getSysResourceBinary(resourceUrl)).binary;
601
+ }
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
  }