@onekeyfe/hd-core 1.2.0-alpha.98 → 1.2.0

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 (204) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +203 -27
  3. package/__tests__/base64Data.test.ts +69 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +89 -38
  5. package/__tests__/check-firmware-release-protocol-v2.test.ts +83 -14
  6. package/__tests__/device-connector-protocol.test.ts +21 -0
  7. package/__tests__/device-lifecycle-events.test.ts +427 -7
  8. package/__tests__/device-pool-state.test.ts +25 -0
  9. package/__tests__/device-settings.test.ts +237 -9
  10. package/__tests__/device-state-events.test.ts +2 -2
  11. package/__tests__/device-state-mapper.test.ts +61 -5
  12. package/__tests__/device-utils.test.ts +15 -1
  13. package/__tests__/device-wallet-session-store.test.ts +147 -21
  14. package/__tests__/deviceSettings.test.ts +7 -3
  15. package/__tests__/deviceUploadNft.test.ts +36 -4
  16. package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -3
  17. package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
  18. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
  19. package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
  20. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +128 -2
  21. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
  22. package/__tests__/firmware-update/firmware-update-plan.test.ts +114 -50
  23. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
  24. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
  25. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +678 -0
  26. package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
  27. package/__tests__/get-device-state.test.ts +60 -21
  28. package/__tests__/logBlockEvent.test.ts +13 -1
  29. package/__tests__/open-wallet-session.test.ts +127 -1
  30. package/__tests__/protocol-v2-firmware-targets.test.ts +39 -0
  31. package/__tests__/protocol-v2-resources.test.ts +185 -88
  32. package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
  33. package/__tests__/protocol-v2.test.ts +2274 -466
  34. package/__tests__/refresh-device-state.test.ts +3 -1
  35. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  36. package/__tests__/search-devices.test.ts +79 -32
  37. package/__tests__/ton-sign-message.test.ts +84 -0
  38. package/dist/api/BaseMethod.d.ts +2 -1
  39. package/dist/api/BaseMethod.d.ts.map +1 -1
  40. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  41. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  42. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  43. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  44. package/dist/api/FirmwareUpdateV4.d.ts +22 -7
  45. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  46. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  47. package/dist/api/SearchDevices.d.ts.map +1 -1
  48. package/dist/api/UploadPortfolio.d.ts +1 -1
  49. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  50. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  51. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  52. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  53. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  54. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  55. package/dist/api/device/DeviceVerify.d.ts +1 -0
  56. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  57. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  58. package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
  59. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  61. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
  62. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  63. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  64. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
  65. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  66. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +1 -0
  67. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  68. package/dist/api/firmware/getBinary.d.ts +3 -2
  69. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  70. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  71. package/dist/api/helpers/base64Data.d.ts +16 -0
  72. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  73. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
  74. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  75. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  76. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  77. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  78. package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
  79. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  80. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  81. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  82. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  83. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  84. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  85. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  86. package/dist/core/index.d.ts +6 -1
  87. package/dist/core/index.d.ts.map +1 -1
  88. package/dist/data-manager/DataManager.d.ts +4 -3
  89. package/dist/data-manager/DataManager.d.ts.map +1 -1
  90. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  91. package/dist/device/Device.d.ts +16 -3
  92. package/dist/device/Device.d.ts.map +1 -1
  93. package/dist/device/DeviceCommands.d.ts +4 -4
  94. package/dist/device/DeviceCommands.d.ts.map +1 -1
  95. package/dist/device/DeviceConnector.d.ts +1 -1
  96. package/dist/device/DeviceConnector.d.ts.map +1 -1
  97. package/dist/device/DevicePool.d.ts.map +1 -1
  98. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  99. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  100. package/dist/events/logBlockEvent.d.ts.map +1 -1
  101. package/dist/events/ui-request.d.ts +3 -0
  102. package/dist/events/ui-request.d.ts.map +1 -1
  103. package/dist/index.d.ts +54 -84
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +2333 -1515
  106. package/dist/protocols/protocol-v2/features.d.ts +13 -5
  107. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  108. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  109. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  110. package/dist/protocols/protocol-v2/resources.d.ts +12 -7
  111. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  112. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
  113. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  114. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  115. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  116. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  117. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  118. package/dist/types/api/protocolV2.d.ts +1 -1
  119. package/dist/types/api/protocolV2.d.ts.map +1 -1
  120. package/dist/types/device.d.ts.map +1 -1
  121. package/dist/types/settings.d.ts +3 -31
  122. package/dist/types/settings.d.ts.map +1 -1
  123. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  124. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  125. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  126. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  127. package/dist/utils/deviceSettings.d.ts +1 -0
  128. package/dist/utils/deviceSettings.d.ts.map +1 -1
  129. package/dist/utils/index.d.ts +1 -1
  130. package/dist/utils/index.d.ts.map +1 -1
  131. package/dist/utils/patch.d.ts +1 -1
  132. package/dist/utils/patch.d.ts.map +1 -1
  133. package/dist/utils/pro2Nft.d.ts +7 -0
  134. package/dist/utils/pro2Nft.d.ts.map +1 -1
  135. package/package.json +6 -4
  136. package/src/api/BaseMethod.ts +8 -1
  137. package/src/api/CheckAllFirmwareRelease.ts +8 -5
  138. package/src/api/CheckBLEFirmwareRelease.ts +2 -2
  139. package/src/api/CheckBootloaderRelease.ts +3 -2
  140. package/src/api/CheckFirmwareRelease.ts +2 -2
  141. package/src/api/FirmwareUpdateV2.ts +130 -69
  142. package/src/api/FirmwareUpdateV3.ts +29 -19
  143. package/src/api/FirmwareUpdateV4.ts +966 -743
  144. package/src/api/OpenWalletSession.ts +39 -9
  145. package/src/api/PromptWebDeviceAccess.ts +2 -2
  146. package/src/api/SearchDevices.ts +6 -3
  147. package/src/api/UploadPortfolio.ts +9 -2
  148. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
  149. package/src/api/device/DeviceChangePin.ts +4 -1
  150. package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
  151. package/src/api/device/DeviceRebootToBootloader.ts +3 -1
  152. package/src/api/device/DeviceSettings.ts +34 -20
  153. package/src/api/device/DeviceVerify.ts +25 -0
  154. package/src/api/device/DeviceWipe.ts +4 -1
  155. package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
  156. package/src/api/firmware/FirmwareHostBinding.ts +10 -0
  157. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
  158. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +131 -76
  159. package/src/api/firmware/FirmwareUpdatePlan.ts +55 -82
  160. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +32 -15
  161. package/src/api/firmware/protocolV2Release.ts +7 -2
  162. package/src/api/helpers/base64Data.ts +85 -0
  163. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  164. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
  165. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  166. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  167. package/src/api/protocol-v2/DeviceReboot.ts +5 -0
  168. package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
  169. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
  170. package/src/api/protocol-v2/helpers.ts +8 -0
  171. package/src/api/ton/TonSignMessage.ts +5 -3
  172. package/src/core/index.ts +264 -55
  173. package/src/data/messages/messages-protocol-v2.json +140 -13
  174. package/src/data-manager/DataManager.ts +46 -39
  175. package/src/data-manager/TransportManager.ts +3 -1
  176. package/src/device/Device.ts +210 -44
  177. package/src/device/DeviceCommands.ts +27 -18
  178. package/src/device/DeviceConnector.ts +17 -3
  179. package/src/device/DevicePool.ts +9 -2
  180. package/src/device/DeviceStateMapper.ts +30 -24
  181. package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
  182. package/src/events/logBlockEvent.ts +14 -1
  183. package/src/events/ui-request.ts +3 -0
  184. package/src/index.ts +0 -10
  185. package/src/protocols/protocol-v2/features.ts +22 -5
  186. package/src/protocols/protocol-v2/index.ts +2 -0
  187. package/src/protocols/protocol-v2/resources.ts +110 -147
  188. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
  189. package/src/protocols/protocol-v2/walletSession.ts +65 -22
  190. package/src/types/api/firmwareUpdate.ts +1 -1
  191. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  192. package/src/types/api/protocolV2.ts +1 -1
  193. package/src/types/device.ts +2 -1
  194. package/src/types/settings.ts +6 -34
  195. package/src/utils/deviceFeaturesCompat.ts +9 -4
  196. package/src/utils/deviceFeaturesUtils.ts +4 -0
  197. package/src/utils/deviceInfoUtils.ts +9 -2
  198. package/src/utils/deviceSettings.ts +11 -4
  199. package/src/utils/index.ts +1 -0
  200. package/src/utils/pro2Nft.ts +31 -8
  201. package/__tests__/firmware-memory-host.test.ts +0 -112
  202. package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
  203. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
  204. package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
@@ -276,94 +276,155 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
276
276
  this.checkPromise = createDeferred();
277
277
  const env = DataManager.getSettings('env');
278
278
  const isBleReconnect = connectId && DataManager.isBleConnect(env);
279
- // Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
280
- // Initialize left pending on the V1 transport is exactly what later times out
281
- // and used to tear down the connection mid-firmware-upload.
282
- let bleProbeInFlight = false;
279
+ // Bootloader probes can outlive the polling interval. Keep discovery and
280
+ // acquire serialized so concurrent ticks never race the same connection.
281
+ let probeInFlight = false;
283
282
 
284
283
  Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
285
284
 
286
285
  // check device goto bootloader mode
287
286
  let isFirstCheck = true;
288
287
  let checkCount = 0;
289
- // eslint-disable-next-line prefer-const
288
+ let hasPromptedWebDevice = false;
289
+ let isPromptingWebDevice = false;
290
+ let isFinished = false;
291
+ let intervalTimer: ReturnType<typeof setInterval> | undefined;
290
292
  let timeoutTimer: ReturnType<typeof setTimeout> | undefined;
291
293
 
292
294
  const deviceType = this.device?.getCurrentDeviceType();
293
295
  const isTouchOrProDevice = deviceType === EDeviceType.Touch || deviceType === EDeviceType.Pro;
294
296
 
295
- const intervalTimer: ReturnType<typeof setInterval> | undefined = setInterval(
296
- async () => {
297
- checkCount += 1;
298
- Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isFirstCheck: ', isFirstCheck);
299
- if (isTouchOrProDevice && isFirstCheck) {
300
- isFirstCheck = false;
301
- Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] wait 3000ms');
302
- await wait(3000);
303
- }
297
+ const clearPollingTimers = () => {
298
+ if (intervalTimer !== undefined) {
299
+ clearInterval(intervalTimer);
300
+ intervalTimer = undefined;
301
+ }
302
+ if (timeoutTimer !== undefined) {
303
+ clearTimeout(timeoutTimer);
304
+ timeoutTimer = undefined;
305
+ }
306
+ };
304
307
 
305
- if (
306
- checkCount > 4 &&
307
- DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
308
- !this.payload.skipWebDevicePrompt
309
- ) {
310
- clearInterval(intervalTimer);
311
- clearTimeout(timeoutTimer);
308
+ const checkForBootloader = async (clearActiveTimers: boolean) => {
309
+ const found = await this._checkDeviceInBootloaderMode(
310
+ connectId,
311
+ clearActiveTimers ? intervalTimer : undefined,
312
+ clearActiveTimers ? timeoutTimer : undefined
313
+ );
314
+ if (found) {
315
+ isFinished = true;
316
+ clearPollingTimers();
317
+ }
318
+ return found;
319
+ };
312
320
 
313
- try {
314
- this.postTipMessage(FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
315
- const confirmed = await this._promptDeviceInBootloaderForWebDevice();
316
- if (confirmed) {
317
- await this._checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer);
318
- }
319
- } catch (e) {
320
- Log.log(
321
- 'FirmwareUpdateV2 [checkDeviceToBootloader] promptDeviceInBootloaderForWebDevice failed: ',
322
- e
323
- );
324
- this.checkPromise?.reject(e);
321
+ let startPolling: () => void = () => undefined;
322
+ const pollForBootloader = async () => {
323
+ if (isFinished || isPromptingWebDevice || probeInFlight) return;
324
+ probeInFlight = true;
325
+ checkCount += 1;
326
+ Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isFirstCheck: ', isFirstCheck);
327
+ if (isTouchOrProDevice && isFirstCheck) {
328
+ isFirstCheck = false;
329
+ Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] wait 3000ms');
330
+ await wait(3000);
331
+ }
332
+
333
+ if (
334
+ checkCount > 4 &&
335
+ DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
336
+ !this.payload.skipWebDevicePrompt &&
337
+ !hasPromptedWebDevice &&
338
+ !isPromptingWebDevice
339
+ ) {
340
+ clearPollingTimers();
341
+ isPromptingWebDevice = true;
342
+ try {
343
+ this.postTipMessage(FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
344
+ const confirmed = await this._promptDeviceInBootloaderForWebDevice();
345
+ hasPromptedWebDevice = true;
346
+ if (confirmed) {
347
+ await checkForBootloader(false);
348
+ }
349
+ // WebUSB enumeration can still be empty immediately after the chooser
350
+ // resolves. Resume a fresh bounded polling window instead of leaving the
351
+ // deferred check pending forever after the original timers were paused.
352
+ if (!isFinished) {
353
+ startPolling();
325
354
  }
326
- return;
355
+ } catch (e) {
356
+ isFinished = true;
357
+ clearPollingTimers();
358
+ Log.log(
359
+ 'FirmwareUpdateV2 [checkDeviceToBootloader] promptDeviceInBootloaderForWebDevice failed: ',
360
+ e
361
+ );
362
+ this.checkPromise?.reject(e);
363
+ } finally {
364
+ isPromptingWebDevice = false;
365
+ probeInFlight = false;
327
366
  }
367
+ return;
368
+ }
328
369
 
329
- if (isBleReconnect) {
330
- if (bleProbeInFlight) return;
331
- bleProbeInFlight = true;
332
- try {
333
- await this.device.deviceConnector?.acquire(
334
- this.device.originalDescriptor.id,
335
- null,
336
- true,
337
- this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
338
- );
339
- // Bound each probe so a request the rebooting device never received
340
- // frees the slot for the next tick instead of hanging into the
341
- // 30s reboot budget.
342
- await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
343
- if (this.device.isBootloader()) {
344
- clearInterval(intervalTimer);
345
- this.checkPromise?.resolve(true);
346
- }
347
- } catch (e) {
348
- // ignore error because of device is not connected
349
- Log.log('catch Bluetooth error when device is restarting: ', e);
350
- } finally {
351
- bleProbeInFlight = false;
370
+ if (isBleReconnect) {
371
+ try {
372
+ await this.device.deviceConnector?.acquire(
373
+ this.device.originalDescriptor.id,
374
+ null,
375
+ true,
376
+ this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
377
+ );
378
+ // Bound each probe so a request the rebooting device never received
379
+ // frees the slot for the next tick instead of hanging into the
380
+ // 30s reboot budget.
381
+ await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
382
+ if (this.device.isBootloader()) {
383
+ isFinished = true;
384
+ clearPollingTimers();
385
+ this.checkPromise?.resolve(true);
352
386
  }
353
- } else {
354
- await this._checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer);
387
+ } catch (e) {
388
+ // ignore error because of device is not connected
389
+ Log.log('catch Bluetooth error when device is restarting: ', e);
390
+ } finally {
391
+ probeInFlight = false;
392
+ }
393
+ } else {
394
+ try {
395
+ await checkForBootloader(true);
396
+ } finally {
397
+ probeInFlight = false;
355
398
  }
356
- },
357
- isBleReconnect ? 3000 : 2000
358
- );
359
-
360
- // check goto bootloader mode timeout and throw error
361
- timeoutTimer = setTimeout(() => {
362
- if (this.checkPromise) {
363
- clearInterval(intervalTimer);
364
- this.checkPromise.reject(new Error());
365
399
  }
366
- }, 30000);
400
+ };
401
+
402
+ startPolling = () => {
403
+ if (isFinished) return;
404
+ clearPollingTimers();
405
+ intervalTimer = setInterval(
406
+ () => {
407
+ pollForBootloader().catch(error => {
408
+ if (isFinished) return;
409
+ isFinished = true;
410
+ clearPollingTimers();
411
+ this.checkPromise?.reject(error);
412
+ });
413
+ },
414
+ isBleReconnect ? 3000 : 2000
415
+ );
416
+ // Each automatic polling phase is bounded. Time spent in the browser's
417
+ // permission chooser is intentionally excluded from this deadline.
418
+ timeoutTimer = setTimeout(() => {
419
+ if (!isFinished && this.checkPromise) {
420
+ isFinished = true;
421
+ clearPollingTimers();
422
+ this.checkPromise.reject(new Error());
423
+ }
424
+ }, 30000);
425
+ };
426
+
427
+ startPolling();
367
428
  }
368
429
 
369
430
  private async _checkDeviceInBootloaderMode(
@@ -609,29 +609,40 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
609
609
  try {
610
610
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
611
611
  const timeoutMs = 3000;
612
- const featuresRes = await Promise.race<TypedResponseMessage<'Features'>>([
613
- typedCall('GetFeatures', 'Features', {}),
614
- new Promise<never>((_, reject) => {
615
- setTimeout(
616
- () =>
617
- reject(
618
- ERRORS.TypedError(
619
- HardwareErrorCode.CallMethodNotResponse,
620
- 'GetFeatures timeout',
621
- { method: 'GetFeatures', timeoutMs }
622
- )
623
- ),
624
- timeoutMs
625
- );
626
- }),
627
- ]);
612
+ const isBrowserWebUsb = DataManager.isBrowserWebUsb(DataManager.getSettings('env'));
613
+ const getFeaturesPromise = isBrowserWebUsb
614
+ ? typedCall('GetFeatures', 'Features', {}, { timeoutMs })
615
+ : typedCall('GetFeatures', 'Features', {});
616
+ // WebUSB owns timeout cleanup so a reboot cannot leave a pending transfer blocking reconnect.
617
+ const featuresRes = isBrowserWebUsb
618
+ ? await getFeaturesPromise
619
+ : await Promise.race<TypedResponseMessage<'Features'>>([
620
+ getFeaturesPromise,
621
+ new Promise<never>((_, reject) => {
622
+ setTimeout(
623
+ () =>
624
+ reject(
625
+ ERRORS.TypedError(
626
+ HardwareErrorCode.CallMethodNotResponse,
627
+ 'GetFeatures timeout',
628
+ { method: 'GetFeatures', timeoutMs }
629
+ )
630
+ ),
631
+ timeoutMs
632
+ );
633
+ }),
634
+ ]);
628
635
  getFeaturesTimeoutCount = 0;
629
636
  const features = buildProtocolV1FeaturesPayload(featuresRes.message, this.device.features);
630
637
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
631
638
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
632
639
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
633
- // Treat update as complete once firmware version becomes non-zero
634
- if (firmwareVersion !== '0.0.0') {
640
+ // Browser WebUSB may read updater Features before the final reboot. Do not let cached
641
+ // pre-update versions promote a current 0.0.0 response to install completion.
642
+ const completionFirmwareVersion = isBrowserWebUsb
643
+ ? getDeviceFirmwareVersion(featuresRes.message).join('.')
644
+ : firmwareVersion;
645
+ if (completionFirmwareVersion !== '0.0.0') {
635
646
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
636
647
  DevicePool.resetState();
637
648
  return {
@@ -640,7 +651,6 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
640
651
  firmwareVersion,
641
652
  };
642
653
  }
643
- // Still in update mode; continue polling (e.g., iOS may return firmwareVersion 0.0.0 during switches)
644
654
  await wait(1000);
645
655
  } catch (error) {
646
656
  Log.log('getFeatures error', error);