@onekeyfe/hd-core 1.1.21-alpha.1 → 1.1.21-alpha.2

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.
@@ -16,6 +16,7 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
16
16
  private prepareFirmwareAndBleBinary;
17
17
  private executeUpdate;
18
18
  private extractUpdateModeProgress;
19
+ private isGetFeaturesTimeoutError;
19
20
  private normalizeErrorMessage;
20
21
  waitForDeviceReconnect(timeout: number): Promise<void>;
21
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV3.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV3.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAG/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,qBAAqB,CAAC;AAKnE,eAAO,MAAM,gCAAgC,UAAU,CAAC;AAaxD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,IAAI;IAmCE,GAAG;;;;;IA8CT,OAAO,CAAC,wBAAwB;YAiBlB,qBAAqB;YAoBrB,uBAAuB;YAiBvB,2BAA2B;YAiD3B,aAAa;IA6K3B,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,qBAAqB;IAuBvB,sBAAsB,CAAC,OAAO,EAAE,MAAM;CAoD7C"}
1
+ {"version":3,"file":"FirmwareUpdateV3.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV3.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAI/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,qBAAqB,CAAC;AAKnE,eAAO,MAAM,gCAAgC,UAAU,CAAC;AAcxD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,IAAI;IAmCE,GAAG;;;;;IA8CT,OAAO,CAAC,wBAAwB;YAiBlB,qBAAqB;YAoBrB,uBAAuB;YAiBvB,2BAA2B;YAiD3B,aAAa;IA2M3B,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAuBvB,sBAAsB,CAAC,OAAO,EAAE,MAAM;CA0E7C"}
@@ -7,16 +7,14 @@ import type { Deferred } from '@onekeyfe/hd-shared';
7
7
  import type { TypedResponseMessage } from '../../device/DeviceCommands';
8
8
  export declare class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
9
9
  checkPromise: Deferred<any> | null;
10
- protected hasPromptedWebUsbBootloaderReauth: boolean;
11
10
  init(): void;
12
11
  run(): Promise<any>;
13
12
  isBleReconnect(): boolean;
14
13
  postTipMessage: (message: IFirmwareUpdateTipMessage) => void;
15
14
  postProcessingMessage: (type: 'firmware' | 'ble' | 'bootloader' | 'resource') => void;
16
15
  postProgressMessage: (progress: number, progressType: IFirmwareUpdateProgressType) => void;
17
- private _promptDeviceInBootloaderForWebDevice;
16
+ protected _promptDeviceInBootloaderForWebDevice(): Promise<unknown>;
18
17
  checkDeviceToBootloader(connectId: string | undefined): void;
19
- protected ensureWebUsbBootloaderReauthPrompt(): Promise<void>;
20
18
  private _checkDeviceInBootloaderMode;
21
19
  enterBootloaderMode(): Promise<true | undefined>;
22
20
  startEmmcFirmwareUpdate({ path }: {
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateBaseMethod.d.ts","sourceRoot":"","sources":["../../../src/api/firmware/FirmwareUpdateBaseMethod.ts"],"names":[],"mappings":";AAaA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,OAAO,KAAK,EACV,2BAA2B,EAC3B,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAexE,qBAAa,wBAAwB,CAAC,MAAM,CAAE,SAAQ,UAAU,CAAC,MAAM,CAAC;IACtE,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,SAAS,CAAC,iCAAiC,UAAS;IAEpD,IAAI,IAAI,IAAI;IAEZ,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IAInB,cAAc,IAAI,OAAO;IASzB,cAAc,YAAa,yBAAyB,UASlD;IAMF,qBAAqB,SAAU,UAAU,GAAG,KAAK,GAAG,YAAY,GAAG,UAAU,UAM3E;IAMF,mBAAmB,aAAc,MAAM,gBAAgB,2BAA2B,UAQhF;YAEY,qCAAqC;IAkBnD,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;cAuFrC,kCAAkC;YAmBpC,4BAA4B;IAuBpC,mBAAmB;IA8CnB,uBAAuB,CAAC,EAAE,IAAI,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;IA6BlD,8BAA8B,CAAC,IAAI,EAAE,MAAM;IAW3C,uBAAuB,CAAC,EAC5B,OAAO,EACP,QAAQ,EACR,aAAa,EACb,SAAS,GACV,EAAE,KAAK,CAAC,cAAc,GAAG;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IA4CK,sBAAsB,CAC1B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,GAAG,MAAM,EAC3B,SAAS,EAAE,OAAO,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI;IAwEnB,MAAM,CAAC,UAAU,EAAE,UAAU;CAsBpC"}
1
+ {"version":3,"file":"FirmwareUpdateBaseMethod.d.ts","sourceRoot":"","sources":["../../../src/api/firmware/FirmwareUpdateBaseMethod.ts"],"names":[],"mappings":";AAaA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,OAAO,KAAK,EACV,2BAA2B,EAC3B,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAexE,qBAAa,wBAAwB,CAAC,MAAM,CAAE,SAAQ,UAAU,CAAC,MAAM,CAAC;IACtE,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAE1C,IAAI,IAAI,IAAI;IAEZ,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IAInB,cAAc,IAAI,OAAO;IASzB,cAAc,YAAa,yBAAyB,UASlD;IAMF,qBAAqB,SAAU,UAAU,GAAG,KAAK,GAAG,YAAY,GAAG,UAAU,UAM3E;IAMF,mBAAmB,aAAc,MAAM,gBAAgB,2BAA2B,UAQhF;cAEc,qCAAqC;IAkBrD,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;YAmFvC,4BAA4B;IAuBpC,mBAAmB;IA8CnB,uBAAuB,CAAC,EAAE,IAAI,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;IA6BlD,8BAA8B,CAAC,IAAI,EAAE,MAAM;IAW3C,uBAAuB,CAAC,EAC5B,OAAO,EACP,QAAQ,EACR,aAAa,EACb,SAAS,GACV,EAAE,KAAK,CAAC,cAAc,GAAG;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IA4CK,sBAAsB,CAC1B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,GAAG,MAAM,EAC3B,SAAS,EAAE,OAAO,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI;IAwEnB,MAAM,CAAC,UAAU,EAAE,UAAU;CAsBpC"}
package/dist/index.js CHANGED
@@ -29369,7 +29369,6 @@ class FirmwareUpdateBaseMethod extends BaseMethod {
29369
29369
  constructor() {
29370
29370
  super(...arguments);
29371
29371
  this.checkPromise = null;
29372
- this.hasPromptedWebUsbBootloaderReauth = false;
29373
29372
  this.postTipMessage = (message) => {
29374
29373
  this.postMessage(createUiMessage(UI_REQUEST.FIRMWARE_TIP, {
29375
29374
  device: this.device.toMessageObject(),
@@ -29477,23 +29476,6 @@ class FirmwareUpdateBaseMethod extends BaseMethod {
29477
29476
  }
29478
29477
  }, 30000);
29479
29478
  }
29480
- ensureWebUsbBootloaderReauthPrompt() {
29481
- return __awaiter(this, void 0, void 0, function* () {
29482
- if (this.hasPromptedWebUsbBootloaderReauth ||
29483
- !DataManager.isBrowserWebUsb(DataManager.getSettings('env')) ||
29484
- this.device.listenerCount(DEVICE.SELECT_DEVICE_IN_BOOTLOADER_FOR_WEB_DEVICE) === 0) {
29485
- return;
29486
- }
29487
- this.hasPromptedWebUsbBootloaderReauth = true;
29488
- this.postTipMessage(exports.FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
29489
- try {
29490
- yield this._promptDeviceInBootloaderForWebDevice();
29491
- }
29492
- catch (error) {
29493
- Log$7.log('WebUSB 设备重新授权失败: ', error);
29494
- }
29495
- });
29496
- }
29497
29479
  _checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer) {
29498
29480
  var _a, _b, _c, _d, _e;
29499
29481
  return __awaiter(this, void 0, void 0, function* () {
@@ -30504,18 +30486,22 @@ class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod {
30504
30486
  this.postProgressMessage(0, 'installingFirmware');
30505
30487
  const installStartTime = Date.now();
30506
30488
  const maxWaitTimeForInstallingFirmware = 5 * 60 * 1000;
30489
+ let getFeaturesTimeoutCount = 0;
30490
+ const maxGetFeaturesTimeoutBeforeReauth = 3;
30507
30491
  while (true) {
30508
30492
  if (Date.now() - installStartTime > maxWaitTimeForInstallingFirmware) {
30509
30493
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Firmware update process timeout after 5 minutes');
30510
30494
  }
30511
30495
  try {
30512
30496
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
30497
+ const timeoutMs = 3000;
30513
30498
  const featuresRes = yield Promise.race([
30514
30499
  typedCall('GetFeatures', 'Features', {}),
30515
30500
  new Promise((_, reject) => {
30516
- setTimeout(() => reject(new Error('GetFeatures timeout after 3 seconds')), 3000);
30501
+ setTimeout(() => reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallMethodNotResponse, 'GetFeatures timeout', { method: 'GetFeatures', timeoutMs })), timeoutMs);
30517
30502
  }),
30518
30503
  ]);
30504
+ getFeaturesTimeoutCount = 0;
30519
30505
  const features = featuresRes.message;
30520
30506
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
30521
30507
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
@@ -30532,17 +30518,31 @@ class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod {
30532
30518
  yield hdShared.wait(1000);
30533
30519
  }
30534
30520
  catch (error) {
30521
+ Log$4.log('getFeatures error', error);
30522
+ let shouldReconnect = true;
30535
30523
  const progress = this.extractUpdateModeProgress(error);
30536
30524
  if (progress !== null) {
30525
+ getFeaturesTimeoutCount = 0;
30537
30526
  this.postProgressMessage(progress, 'installingFirmware');
30538
30527
  yield hdShared.wait(1000);
30528
+ shouldReconnect = false;
30529
+ }
30530
+ else if (this.isGetFeaturesTimeoutError(error)) {
30531
+ getFeaturesTimeoutCount += 1;
30532
+ if (getFeaturesTimeoutCount <= maxGetFeaturesTimeoutBeforeReauth) {
30533
+ yield hdShared.wait(1000);
30534
+ shouldReconnect = false;
30535
+ }
30539
30536
  }
30540
30537
  else {
30538
+ getFeaturesTimeoutCount = 0;
30539
+ }
30540
+ if (shouldReconnect) {
30541
30541
  yield hdShared.wait(1000);
30542
30542
  const reconnectTimeout = this.isBleReconnect() && (this.params.bleBinary || this.params.bleVersion)
30543
30543
  ? 3 * 60 * 1000
30544
30544
  : 60 * 1000;
30545
- yield this.ensureWebUsbBootloaderReauthPrompt();
30545
+ getFeaturesTimeoutCount = 0;
30546
30546
  yield this.waitForDeviceReconnect(reconnectTimeout);
30547
30547
  }
30548
30548
  }
@@ -30561,6 +30561,12 @@ class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod {
30561
30561
  const progress = parseInt(match[1], 10);
30562
30562
  return Number.isNaN(progress) ? null : progress;
30563
30563
  }
30564
+ isGetFeaturesTimeoutError(error) {
30565
+ var _a;
30566
+ return (error instanceof hdShared.HardwareError &&
30567
+ error.errorCode === hdShared.HardwareErrorCode.CallMethodNotResponse &&
30568
+ ((_a = error.params) === null || _a === void 0 ? void 0 : _a.method) === 'GetFeatures');
30569
+ }
30564
30570
  normalizeErrorMessage(error) {
30565
30571
  if (!error) {
30566
30572
  return '';
@@ -30584,6 +30590,7 @@ class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod {
30584
30590
  return __awaiter(this, void 0, void 0, function* () {
30585
30591
  const startTime = Date.now();
30586
30592
  const isBleReconnect = this.isBleReconnect();
30593
+ let webUsbCheckCount = 0;
30587
30594
  while (Date.now() - startTime < timeout) {
30588
30595
  try {
30589
30596
  if (isBleReconnect) {
@@ -30607,6 +30614,25 @@ class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod {
30607
30614
  else {
30608
30615
  const deviceDiff = yield ((_b = this.device.deviceConnector) === null || _b === void 0 ? void 0 : _b.enumerate());
30609
30616
  const devicesDescriptor = (_c = deviceDiff === null || deviceDiff === void 0 ? void 0 : deviceDiff.descriptors) !== null && _c !== void 0 ? _c : [];
30617
+ const canPromptWebUsbBootloader = DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
30618
+ !this.payload.skipWebDevicePrompt &&
30619
+ this.device.listenerCount(DEVICE.SELECT_DEVICE_IN_BOOTLOADER_FOR_WEB_DEVICE) > 0;
30620
+ if (canPromptWebUsbBootloader) {
30621
+ webUsbCheckCount += 1;
30622
+ if (webUsbCheckCount > 4) {
30623
+ this.postTipMessage(exports.FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
30624
+ try {
30625
+ yield this._promptDeviceInBootloaderForWebDevice();
30626
+ }
30627
+ catch (e) {
30628
+ Log$4.log('WebUSB re-authorization failed: ', e);
30629
+ }
30630
+ webUsbCheckCount = 0;
30631
+ }
30632
+ }
30633
+ else {
30634
+ webUsbCheckCount = 0;
30635
+ }
30610
30636
  const { deviceList } = yield DevicePool.getDevices(devicesDescriptor, this.connectId);
30611
30637
  if (deviceList.length === 1) {
30612
30638
  this.device.updateFromCache(deviceList[0]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.1.21-alpha.1",
3
+ "version": "1.1.21-alpha.2",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.1.21-alpha.1",
29
- "@onekeyfe/hd-transport": "1.1.21-alpha.1",
28
+ "@onekeyfe/hd-shared": "1.1.21-alpha.2",
29
+ "@onekeyfe/hd-transport": "1.1.21-alpha.2",
30
30
  "axios": "1.12.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "bytebuffer": "^5.0.1",
@@ -44,5 +44,5 @@
44
44
  "@types/w3c-web-usb": "^1.0.10",
45
45
  "@types/web-bluetooth": "^0.0.21"
46
46
  },
47
- "gitHead": "933dbbcf45753b685ba7f76500158de42b68215b"
47
+ "gitHead": "ed487b957d52b83b8fb5766578fe6b65f478100a"
48
48
  }
@@ -1,4 +1,4 @@
1
- import { EDeviceType, ERRORS, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
2
2
  import semver from 'semver';
3
3
  import JSZip from 'jszip';
4
4
 
@@ -17,6 +17,7 @@ import { getBinary, getSysResourceBinary } from './firmware/getBinary';
17
17
  import { DataManager } from '../data-manager';
18
18
  import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
19
19
  import { DevicePool } from '../device/DevicePool';
20
+ import { DEVICE } from '../events';
20
21
 
21
22
  import type { FirmwareUpdateV3Params } from '../types/api/firmwareUpdate';
22
23
  import type { Deferred, EFirmwareType } from '@onekeyfe/hd-shared';
@@ -26,6 +27,7 @@ const Log = getLogger(LoggerNames.Method);
26
27
 
27
28
  export const MIN_UPDATE_V3_BOOTLOADER_VERSION = '2.8.0';
28
29
 
30
+
29
31
  /**
30
32
  * FirmwareUpdateV3 flow
31
33
  1. StartDownloadFirmware
@@ -336,6 +338,9 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
336
338
  const installStartTime = Date.now();
337
339
  const maxWaitTimeForInstallingFirmware = 5 * 60 * 1000; // 5 minutes in milliseconds
338
340
 
341
+ let getFeaturesTimeoutCount = 0;
342
+ const maxGetFeaturesTimeoutBeforeReauth = 3;
343
+
339
344
  // eslint-disable-next-line no-constant-condition
340
345
  while (true) {
341
346
  // Check if timeout exceeded
@@ -348,12 +353,24 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
348
353
 
349
354
  try {
350
355
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
356
+ const timeoutMs = 3000;
351
357
  const featuresRes = await Promise.race<TypedResponseMessage<'Features'>>([
352
358
  typedCall('GetFeatures', 'Features', {}),
353
359
  new Promise<never>((_, reject) => {
354
- setTimeout(() => reject(new Error('GetFeatures timeout after 3 seconds')), 3000);
360
+ setTimeout(
361
+ () =>
362
+ reject(
363
+ ERRORS.TypedError(
364
+ HardwareErrorCode.CallMethodNotResponse,
365
+ 'GetFeatures timeout',
366
+ { method: 'GetFeatures', timeoutMs }
367
+ )
368
+ ),
369
+ timeoutMs
370
+ );
355
371
  }),
356
372
  ]);
373
+ getFeaturesTimeoutCount = 0;
357
374
  const features = featuresRes.message;
358
375
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
359
376
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
@@ -371,11 +388,26 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
371
388
  // Still in update mode; continue polling (e.g., iOS may return firmwareVersion 0.0.0 during switches)
372
389
  await wait(1000);
373
390
  } catch (error) {
391
+ Log.log('getFeatures error', error);
392
+ let shouldReconnect = true;
374
393
  const progress = this.extractUpdateModeProgress(error);
375
394
  if (progress !== null) {
395
+ getFeaturesTimeoutCount = 0;
376
396
  this.postProgressMessage(progress, 'installingFirmware');
377
397
  await wait(1000);
398
+ shouldReconnect = false;
399
+ } else if (this.isGetFeaturesTimeoutError(error)) {
400
+ getFeaturesTimeoutCount += 1;
401
+ // Retry transient GetFeatures timeouts to avoid unnecessary WebUSB re-authorization prompts.
402
+ if (getFeaturesTimeoutCount <= maxGetFeaturesTimeoutBeforeReauth) {
403
+ await wait(1000);
404
+ shouldReconnect = false;
405
+ }
378
406
  } else {
407
+ getFeaturesTimeoutCount = 0;
408
+ }
409
+
410
+ if (shouldReconnect) {
379
411
  await wait(1000);
380
412
  /**
381
413
  * Needs second reconnect case:
@@ -387,7 +419,7 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
387
419
  ? 3 * 60 * 1000 // 3 minutes for BLE reconnect
388
420
  : 60 * 1000; // 1 minute for normal reconnect
389
421
 
390
- await this.ensureWebUsbBootloaderReauthPrompt();
422
+ getFeaturesTimeoutCount = 0;
391
423
  await this.waitForDeviceReconnect(reconnectTimeout);
392
424
  }
393
425
  }
@@ -410,6 +442,14 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
410
442
  return Number.isNaN(progress) ? null : progress;
411
443
  }
412
444
 
445
+ private isGetFeaturesTimeoutError(error: unknown): boolean {
446
+ return (
447
+ error instanceof HardwareError &&
448
+ error.errorCode === HardwareErrorCode.CallMethodNotResponse &&
449
+ error.params?.method === 'GetFeatures'
450
+ );
451
+ }
452
+
413
453
  private normalizeErrorMessage(error: unknown): string {
414
454
  if (!error) {
415
455
  return '';
@@ -436,6 +476,7 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
436
476
  async waitForDeviceReconnect(timeout: number) {
437
477
  const startTime = Date.now();
438
478
  const isBleReconnect = this.isBleReconnect();
479
+ let webUsbCheckCount = 0;
439
480
  while (Date.now() - startTime < timeout) {
440
481
  try {
441
482
  if (isBleReconnect) {
@@ -462,6 +503,27 @@ export default class FirmwareUpdateV3 extends FirmwareUpdateBaseMethod<FirmwareU
462
503
  } else {
463
504
  const deviceDiff = await this.device.deviceConnector?.enumerate();
464
505
  const devicesDescriptor = deviceDiff?.descriptors ?? [];
506
+
507
+ const canPromptWebUsbBootloader =
508
+ DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
509
+ !this.payload.skipWebDevicePrompt &&
510
+ this.device.listenerCount(DEVICE.SELECT_DEVICE_IN_BOOTLOADER_FOR_WEB_DEVICE) > 0;
511
+
512
+ if (canPromptWebUsbBootloader) {
513
+ webUsbCheckCount += 1;
514
+ if (webUsbCheckCount > 4) {
515
+ this.postTipMessage(FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
516
+ try {
517
+ await this._promptDeviceInBootloaderForWebDevice();
518
+ } catch (e) {
519
+ Log.log('WebUSB re-authorization failed: ', e);
520
+ }
521
+ webUsbCheckCount = 0;
522
+ }
523
+ } else {
524
+ webUsbCheckCount = 0;
525
+ }
526
+
465
527
  const { deviceList } = await DevicePool.getDevices(devicesDescriptor, this.connectId);
466
528
 
467
529
  if (deviceList.length === 1) {
@@ -40,8 +40,6 @@ const isDeviceDisconnectedError = (error: unknown) => {
40
40
  export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
41
41
  checkPromise: Deferred<any> | null = null;
42
42
 
43
- protected hasPromptedWebUsbBootloaderReauth = false;
44
-
45
43
  init(): void {}
46
44
 
47
45
  run(): Promise<any> {
@@ -94,7 +92,7 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
94
92
  );
95
93
  };
96
94
 
97
- private async _promptDeviceInBootloaderForWebDevice() {
95
+ protected async _promptDeviceInBootloaderForWebDevice() {
98
96
  return new Promise((resolve, reject) => {
99
97
  if (this.device.listenerCount(DEVICE.SELECT_DEVICE_IN_BOOTLOADER_FOR_WEB_DEVICE) > 0) {
100
98
  this.device.emit(
@@ -195,29 +193,6 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
195
193
  }, 30000);
196
194
  }
197
195
 
198
- /**
199
- * 仅在 firmware 被抹掉、需要重新授权的 bootloader 场景下才提示 WebUSB
200
- * (比如 BTC-only 与通用固件互转后算作新设备)。
201
- */
202
- protected async ensureWebUsbBootloaderReauthPrompt() {
203
- if (
204
- this.hasPromptedWebUsbBootloaderReauth ||
205
- !DataManager.isBrowserWebUsb(DataManager.getSettings('env')) ||
206
- this.device.listenerCount(DEVICE.SELECT_DEVICE_IN_BOOTLOADER_FOR_WEB_DEVICE) === 0
207
- ) {
208
- return;
209
- }
210
-
211
- this.hasPromptedWebUsbBootloaderReauth = true;
212
- this.postTipMessage(FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
213
-
214
- try {
215
- await this._promptDeviceInBootloaderForWebDevice();
216
- } catch (error) {
217
- Log.log('WebUSB 设备重新授权失败: ', error);
218
- }
219
- }
220
-
221
196
  private async _checkDeviceInBootloaderMode(
222
197
  connectId: string | undefined,
223
198
  intervalTimer?: ReturnType<typeof setInterval>,
@@ -455,8 +430,8 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
455
430
  }
456
431
 
457
432
  /**
458
- * @description 设备重启(Bootloader 侧可用)
459
- * @param rebootType 重启类型,参考 RebootType 枚举
433
+ * @description Device reboot (available in bootloader mode)
434
+ * @param rebootType Reboot type, see the RebootType enum
460
435
  */
461
436
  async reboot(rebootType: RebootType) {
462
437
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());