@onekeyfe/hwk-ledger-adapter 1.1.26-patch.2 → 1.1.27-alpha.1

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.
package/dist/index.d.mts CHANGED
@@ -6,17 +6,26 @@ import { SignerBtc as SignerBtc$1 } from '@ledgerhq/device-signer-kit-bitcoin';
6
6
  import { SignerSolana } from '@ledgerhq/device-signer-kit-solana';
7
7
  import Transport from '@ledgerhq/hw-transport';
8
8
 
9
+ interface LedgerAdapterOptions {
10
+ /**
11
+ * `true` — emit `REQUEST_SELECT_DEVICE` on multi-device discovery and await
12
+ * `uiResponse({ type: RECEIVE_SELECT_DEVICE, payload: { sdkConnectId } })`.
13
+ * `false` (default) — silently pick the first device.
14
+ */
15
+ handleSelectDevice?: boolean;
16
+ }
9
17
  declare class LedgerAdapter implements IHardwareWallet {
10
18
  readonly vendor: "ledger";
11
19
  private readonly connector;
12
20
  private readonly emitter;
21
+ private readonly _handleSelectDevice;
13
22
  private _discoveredDevices;
14
23
  private _sessions;
15
24
  private readonly _uiRegistry;
16
25
  private _btcHighIndexConfirmedThisSession;
17
26
  private readonly _jobQueue;
18
27
  private _doConnectAbortController;
19
- constructor(connector: IConnector);
28
+ constructor(connector: IConnector, options?: LedgerAdapterOptions);
20
29
  get activeTransport(): TransportType | null;
21
30
  getAvailableTransports(): TransportType[];
22
31
  switchTransport(_type: TransportType): Promise<void>;
@@ -30,7 +39,6 @@ declare class LedgerAdapter implements IHardwareWallet {
30
39
  dispose(): Promise<void>;
31
40
  uiResponse(response: UiResponseEvent): void;
32
41
  searchDevices(options?: SearchDevicesOptions): Promise<DeviceInfo[]>;
33
- private _evictAllSessions;
34
42
  private static readonly MAX_BUSINESS_RETRY_BUDGET;
35
43
  private static readonly STUCK_APP_RETRY_DELAY_MS;
36
44
  private static readonly MAX_DOCONNECT_CONFIRMS;
@@ -90,7 +98,7 @@ declare class LedgerAdapter implements IHardwareWallet {
90
98
  *
91
99
  * - If a session already exists for the given connectId, reuse it.
92
100
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
93
- * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
101
+ * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
94
102
  */
95
103
  private _connectingPromise;
96
104
  private _waitForDeviceConnect;
@@ -107,6 +115,7 @@ declare class LedgerAdapter implements IHardwareWallet {
107
115
  private _doConnect;
108
116
  private _connectFirstOrSelect;
109
117
  private _connectDeviceOrThrow;
118
+ private _chooseDeviceFromList;
110
119
  /**
111
120
  * Call the connector with automatic session resolution and disconnect retry.
112
121
  *
@@ -364,7 +373,8 @@ declare class LedgerConnectorBase implements IConnector {
364
373
  * `LedgerAdapter._sessions` map would hold a dead session entry until
365
374
  * the next call hit `DeviceSessionNotFound`.
366
375
  *
367
- * Subscribe failure is fatal see the inline note at the subscribe call.
376
+ * Best-effort: any error subscribing is swallowed so a flaky DMK
377
+ * doesn't break the connect path.
368
378
  */
369
379
  private _watchSessionState;
370
380
  private _unwatchSessionState;
package/dist/index.d.ts CHANGED
@@ -6,17 +6,26 @@ import { SignerBtc as SignerBtc$1 } from '@ledgerhq/device-signer-kit-bitcoin';
6
6
  import { SignerSolana } from '@ledgerhq/device-signer-kit-solana';
7
7
  import Transport from '@ledgerhq/hw-transport';
8
8
 
9
+ interface LedgerAdapterOptions {
10
+ /**
11
+ * `true` — emit `REQUEST_SELECT_DEVICE` on multi-device discovery and await
12
+ * `uiResponse({ type: RECEIVE_SELECT_DEVICE, payload: { sdkConnectId } })`.
13
+ * `false` (default) — silently pick the first device.
14
+ */
15
+ handleSelectDevice?: boolean;
16
+ }
9
17
  declare class LedgerAdapter implements IHardwareWallet {
10
18
  readonly vendor: "ledger";
11
19
  private readonly connector;
12
20
  private readonly emitter;
21
+ private readonly _handleSelectDevice;
13
22
  private _discoveredDevices;
14
23
  private _sessions;
15
24
  private readonly _uiRegistry;
16
25
  private _btcHighIndexConfirmedThisSession;
17
26
  private readonly _jobQueue;
18
27
  private _doConnectAbortController;
19
- constructor(connector: IConnector);
28
+ constructor(connector: IConnector, options?: LedgerAdapterOptions);
20
29
  get activeTransport(): TransportType | null;
21
30
  getAvailableTransports(): TransportType[];
22
31
  switchTransport(_type: TransportType): Promise<void>;
@@ -30,7 +39,6 @@ declare class LedgerAdapter implements IHardwareWallet {
30
39
  dispose(): Promise<void>;
31
40
  uiResponse(response: UiResponseEvent): void;
32
41
  searchDevices(options?: SearchDevicesOptions): Promise<DeviceInfo[]>;
33
- private _evictAllSessions;
34
42
  private static readonly MAX_BUSINESS_RETRY_BUDGET;
35
43
  private static readonly STUCK_APP_RETRY_DELAY_MS;
36
44
  private static readonly MAX_DOCONNECT_CONFIRMS;
@@ -90,7 +98,7 @@ declare class LedgerAdapter implements IHardwareWallet {
90
98
  *
91
99
  * - If a session already exists for the given connectId, reuse it.
92
100
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
93
- * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
101
+ * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
94
102
  */
95
103
  private _connectingPromise;
96
104
  private _waitForDeviceConnect;
@@ -107,6 +115,7 @@ declare class LedgerAdapter implements IHardwareWallet {
107
115
  private _doConnect;
108
116
  private _connectFirstOrSelect;
109
117
  private _connectDeviceOrThrow;
118
+ private _chooseDeviceFromList;
110
119
  /**
111
120
  * Call the connector with automatic session resolution and disconnect retry.
112
121
  *
@@ -364,7 +373,8 @@ declare class LedgerConnectorBase implements IConnector {
364
373
  * `LedgerAdapter._sessions` map would hold a dead session entry until
365
374
  * the next call hit `DeviceSessionNotFound`.
366
375
  *
367
- * Subscribe failure is fatal see the inline note at the subscribe call.
376
+ * Best-effort: any error subscribing is swallowed so a flaky DMK
377
+ * doesn't break the connect path.
368
378
  */
369
379
  private _watchSessionState;
370
380
  private _unwatchSessionState;
package/dist/index.js CHANGED
@@ -57,12 +57,6 @@ var import_hwk_adapter_core2 = require("@onekeyfe/hwk-adapter-core");
57
57
 
58
58
  // src/errors.ts
59
59
  var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
60
- var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
61
- function createMultipleUsbLedgerDevicesError() {
62
- return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
63
- code: import_hwk_adapter_core.HardwareErrorCode.DeviceOneDeviceOnly
64
- });
65
- }
66
60
  function ledgerFailure(code, error, appName, tag, params) {
67
61
  const payload = { error, code };
68
62
  if (appName !== void 0) payload.appName = appName;
@@ -494,7 +488,7 @@ function btcAccountIndexFromPath(path) {
494
488
  return Number.isFinite(accountIndex) ? accountIndex : null;
495
489
  }
496
490
  var _LedgerAdapter = class _LedgerAdapter {
497
- constructor(connector) {
491
+ constructor(connector, options) {
498
492
  this.vendor = "ledger";
499
493
  this.emitter = new import_hwk_adapter_core2.TypedEventEmitter();
500
494
  this._discoveredDevices = /* @__PURE__ */ new Map();
@@ -516,7 +510,7 @@ var _LedgerAdapter = class _LedgerAdapter {
516
510
  *
517
511
  * - If a session already exists for the given connectId, reuse it.
518
512
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
519
- * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
513
+ * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
520
514
  */
521
515
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
522
516
  this._connectingPromise = null;
@@ -547,6 +541,7 @@ var _LedgerAdapter = class _LedgerAdapter {
547
541
  this.emitter.emit("ui-event", event);
548
542
  };
549
543
  this.connector = connector;
544
+ this._handleSelectDevice = options?.handleSelectDevice ?? false;
550
545
  this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue();
551
546
  this.registerEventListeners();
552
547
  }
@@ -620,18 +615,6 @@ var _LedgerAdapter = class _LedgerAdapter {
620
615
  );
621
616
  return Array.from(this._discoveredDevices.values());
622
617
  }
623
- // USB single-session invariant: evict all sessions, best-effort (see connectDevice).
624
- async _evictAllSessions() {
625
- if (this._sessions.size === 0) return;
626
- const stale = [...this._sessions.values()];
627
- this._sessions.clear();
628
- for (const sid of stale) {
629
- try {
630
- await this.connector.disconnect(sid);
631
- } catch {
632
- }
633
- }
634
- }
635
618
  static _createDeviceBusyError(method) {
636
619
  return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
637
620
  code: import_hwk_adapter_core2.HardwareErrorCode.DeviceBusy
@@ -644,9 +627,6 @@ var _LedgerAdapter = class _LedgerAdapter {
644
627
  code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
645
628
  });
646
629
  }
647
- if (!isLedgerBleConnectionType(this.connector.connectionType)) {
648
- await this._evictAllSessions();
649
- }
650
630
  await this._ensureDevicePermission(connectId);
651
631
  const session = await this.connector.connect(connectId);
652
632
  this._sessions.set(connectId, session.sessionId);
@@ -974,15 +954,7 @@ var _LedgerAdapter = class _LedgerAdapter {
974
954
  });
975
955
  }
976
956
  if (connectId && this._sessions.has(connectId)) return connectId;
977
- if (!connectId && this._sessions.size > 0) {
978
- if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
979
- throw Object.assign(
980
- new Error(
981
- "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
982
- ),
983
- { code: import_hwk_adapter_core2.HardwareErrorCode.DeviceOneDeviceOnly }
984
- );
985
- }
957
+ if (this._sessions.size > 0) {
986
958
  return this._sessions.keys().next().value;
987
959
  }
988
960
  if (!this._connectingPromise) {
@@ -1070,37 +1042,24 @@ var _LedgerAdapter = class _LedgerAdapter {
1070
1042
  const target = devices.find(
1071
1043
  (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
1072
1044
  );
1073
- if (target) {
1074
- return this._connectDeviceOrThrow(target.connectId);
1075
- }
1076
- if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) {
1077
- debugLog(
1078
- `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)`
1079
- );
1080
- return this._connectDeviceOrThrow(devices[0].connectId);
1081
- }
1082
- const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1083
- code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1084
- });
1085
- if (isLedgerBleConnectionType(this.connector.connectionType)) {
1086
- err._tag = ERROR_TAG.DeviceNotAdvertising;
1045
+ if (!target) {
1046
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1047
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1048
+ });
1049
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1050
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
1051
+ }
1052
+ throw err;
1087
1053
  }
1088
- throw err;
1054
+ return this._connectDeviceOrThrow(target.connectId);
1089
1055
  }
1090
1056
  if (isLedgerBleConnectionType(this.connector.connectionType)) {
1091
1057
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1092
1058
  code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1093
1059
  });
1094
1060
  }
1095
- if (devices.length > 1) {
1096
- throw createMultipleUsbLedgerDevicesError();
1097
- }
1098
- if (devices.length !== 1) {
1099
- throw Object.assign(new Error("Ledger device not found."), {
1100
- code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1101
- });
1102
- }
1103
- return this._connectDeviceOrThrow(devices[0].connectId);
1061
+ const chosenConnectId = devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices);
1062
+ return this._connectDeviceOrThrow(chosenConnectId);
1104
1063
  }
1105
1064
  async _connectDeviceOrThrow(chosenConnectId) {
1106
1065
  const result = await this.connectDevice(chosenConnectId);
@@ -1116,6 +1075,46 @@ var _LedgerAdapter = class _LedgerAdapter {
1116
1075
  }
1117
1076
  return chosenConnectId;
1118
1077
  }
1078
+ async _chooseDeviceFromList(devices) {
1079
+ if (!this._handleSelectDevice) {
1080
+ debugLog(
1081
+ `[DMK] Multiple Ledger devices found (${devices.length}); handleSelectDevice=false, picking first (${devices[0].connectId}).`
1082
+ );
1083
+ return devices[0].connectId;
1084
+ }
1085
+ let response;
1086
+ try {
1087
+ const waitPromise = this._uiRegistry.wait(
1088
+ import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE
1089
+ );
1090
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE, {
1091
+ type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE,
1092
+ payload: { devices }
1093
+ });
1094
+ response = await waitPromise;
1095
+ } catch (err) {
1096
+ if (err?._tag === import_hwk_adapter_core2.UI_REQUEST_PREEMPTED_TAG) {
1097
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
1098
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
1099
+ payload: {}
1100
+ });
1101
+ throw Object.assign(new Error("Device selection superseded"), {
1102
+ _tag: ERROR_TAG.UserAborted,
1103
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
1104
+ cause: err
1105
+ });
1106
+ }
1107
+ throw err;
1108
+ }
1109
+ const chosen = devices.find((d) => d.connectId === response?.sdkConnectId);
1110
+ if (!chosen) {
1111
+ throw Object.assign(
1112
+ new Error(`Selected sdkConnectId '${response?.sdkConnectId}' not in discovered list`),
1113
+ { _tag: ERROR_TAG.DeviceNotRecognized }
1114
+ );
1115
+ }
1116
+ return chosen.connectId;
1117
+ }
1119
1118
  /**
1120
1119
  * Call the connector with automatic session resolution and disconnect retry.
1121
1120
  *
@@ -1280,7 +1279,8 @@ var _LedgerAdapter = class _LedgerAdapter {
1280
1279
  } catch {
1281
1280
  }
1282
1281
  }
1283
- const reConnectId = await this.ensureConnected(resolvedConnectId, signal);
1282
+ const retryConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1283
+ const reConnectId = await this.ensureConnected(retryConnectId, signal);
1284
1284
  const reSessionId = this._sessions.get(reConnectId);
1285
1285
  if (!reSessionId) throw lastErr;
1286
1286
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
@@ -1348,7 +1348,8 @@ var _LedgerAdapter = class _LedgerAdapter {
1348
1348
  */
1349
1349
  async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
1350
1350
  await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
1351
- const retryConnectId = await this.ensureConnected(resolvedConnectId, signal);
1351
+ const retryTargetConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1352
+ const retryConnectId = await this.ensureConnected(retryTargetConnectId, signal);
1352
1353
  const retrySessionId = this._sessions.get(retryConnectId);
1353
1354
  if (!retrySessionId) throw originalErr;
1354
1355
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
@@ -3087,12 +3088,6 @@ var LedgerConnectorBase = class {
3087
3088
  }
3088
3089
  // ---------------------------------------------------------------------------
3089
3090
  // IConnector -- Device discovery
3090
- //
3091
- // Discovery never throws on multiple USB devices — it returns the full list.
3092
- // The "connect exactly one device" rule is enforced upstream: an explicit
3093
- // connectId connects that device (or fails as not-found), and only the
3094
- // no-connectId USB path rejects when more than one device is present
3095
- // (see LedgerAdapter._connectFirstOrSelect).
3096
3091
  // ---------------------------------------------------------------------------
3097
3092
  async searchDevices() {
3098
3093
  const dm = await this._getDeviceManager();
@@ -3198,16 +3193,7 @@ var LedgerConnectorBase = class {
3198
3193
  }
3199
3194
  throw err;
3200
3195
  }
3201
- try {
3202
- this._watchSessionState(sessionId, externalConnectId);
3203
- } catch (subErr) {
3204
- debugLog("[DMK] state subscription failed during connect; disconnecting session:", subErr);
3205
- try {
3206
- await dm.disconnect(sessionId);
3207
- } catch {
3208
- }
3209
- throw subErr;
3210
- }
3196
+ this._watchSessionState(sessionId, externalConnectId);
3211
3197
  const info = dm.getDiscoveredDeviceInfo(path);
3212
3198
  const session = {
3213
3199
  sessionId,
@@ -3273,7 +3259,8 @@ var LedgerConnectorBase = class {
3273
3259
  * `LedgerAdapter._sessions` map would hold a dead session entry until
3274
3260
  * the next call hit `DeviceSessionNotFound`.
3275
3261
  *
3276
- * Subscribe failure is fatal see the inline note at the subscribe call.
3262
+ * Best-effort: any error subscribing is swallowed so a flaky DMK
3263
+ * doesn't break the connect path.
3277
3264
  */
3278
3265
  _watchSessionState(sessionId, externalConnectId) {
3279
3266
  const dmk = this._dmk;
@@ -3285,20 +3272,24 @@ var LedgerConnectorBase = class {
3285
3272
  } catch {
3286
3273
  }
3287
3274
  }
3288
- const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
3289
- next: (state) => {
3290
- if (state?.deviceStatus === "NOT CONNECTED") {
3275
+ try {
3276
+ const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
3277
+ next: (state) => {
3278
+ if (state?.deviceStatus === "NOT CONNECTED") {
3279
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3280
+ }
3281
+ },
3282
+ error: () => {
3283
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3284
+ },
3285
+ complete: () => {
3291
3286
  this._handleAutonomousDisconnect(sessionId, externalConnectId);
3292
3287
  }
3293
- },
3294
- error: () => {
3295
- this._handleAutonomousDisconnect(sessionId, externalConnectId);
3296
- },
3297
- complete: () => {
3298
- this._handleAutonomousDisconnect(sessionId, externalConnectId);
3299
- }
3300
- });
3301
- this._sessionStateSubs.set(sessionId, sub);
3288
+ });
3289
+ this._sessionStateSubs.set(sessionId, sub);
3290
+ } catch (err) {
3291
+ debugLog("[DMK] _watchSessionState subscribe failed:", err);
3292
+ }
3302
3293
  }
3303
3294
  _unwatchSessionState(sessionId) {
3304
3295
  const sub = this._sessionStateSubs.get(sessionId);