@onekeyfe/hwk-ledger-adapter 1.1.26-alpha.15 → 1.1.26-alpha.21

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.js CHANGED
@@ -57,6 +57,12 @@ 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
+ }
60
66
  function ledgerFailure(code, error, appName, tag, params) {
61
67
  const payload = { error, code };
62
68
  if (appName !== void 0) payload.appName = appName;
@@ -341,6 +347,15 @@ function isAppNotInstalledError(err) {
341
347
  if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
342
348
  return false;
343
349
  }
350
+ function isOutOfMemoryError(err) {
351
+ if (!err || typeof err !== "object") return false;
352
+ const e = err;
353
+ if (e._tag === "OutOfMemoryDAError") return true;
354
+ if (typeof e.message === "string" && /out of memory|not enough.*space|insufficient.*memory/i.test(e.message)) {
355
+ return true;
356
+ }
357
+ return false;
358
+ }
344
359
  function isDeviceDisconnectedError(err) {
345
360
  if (!err || typeof err !== "object") return false;
346
361
  const e = err;
@@ -403,6 +418,8 @@ function mapLedgerError(err, opts) {
403
418
  code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
404
419
  } else if (isAppNotInstalledError(err)) {
405
420
  code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
421
+ } else if (isOutOfMemoryError(err)) {
422
+ code = import_hwk_adapter_core.HardwareErrorCode.DeviceOutOfMemory;
406
423
  } else if (isDeviceDisconnectedError(err)) {
407
424
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
408
425
  } else if (isTimeoutError(err)) {
@@ -488,7 +505,7 @@ function btcAccountIndexFromPath(path) {
488
505
  return Number.isFinite(accountIndex) ? accountIndex : null;
489
506
  }
490
507
  var _LedgerAdapter = class _LedgerAdapter {
491
- constructor(connector, options) {
508
+ constructor(connector) {
492
509
  this.vendor = "ledger";
493
510
  this.emitter = new import_hwk_adapter_core2.TypedEventEmitter();
494
511
  this._discoveredDevices = /* @__PURE__ */ new Map();
@@ -510,7 +527,7 @@ var _LedgerAdapter = class _LedgerAdapter {
510
527
  *
511
528
  * - If a session already exists for the given connectId, reuse it.
512
529
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
513
- * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
530
+ * - Otherwise: search → 1 USB device: auto-connect, multiple USB devices: throw, 0: throw.
514
531
  */
515
532
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
516
533
  this._connectingPromise = null;
@@ -519,16 +536,45 @@ var _LedgerAdapter = class _LedgerAdapter {
519
536
  // ---------------------------------------------------------------------------
520
537
  this.deviceConnectHandler = (data) => {
521
538
  const deviceInfo = this.connectorDeviceToDeviceInfo(data.device);
539
+ const hadSession = this._sessions.get(deviceInfo.connectId);
522
540
  this._discoveredDevices.set(deviceInfo.connectId, deviceInfo);
523
541
  this._sessions.delete(deviceInfo.connectId);
542
+ debugLog(
543
+ "[SESS-DBG] deviceConnectHandler EVENT connectId=",
544
+ deviceInfo.connectId,
545
+ "deviceId=",
546
+ deviceInfo.deviceId,
547
+ "label=",
548
+ deviceInfo.label,
549
+ "serial=",
550
+ deviceInfo.serialNumber,
551
+ "staleSessionDeleted=",
552
+ hadSession ?? "(none)",
553
+ "sessionsAfter=",
554
+ this._snapshotSessions()
555
+ );
524
556
  this.emitter.emit(import_hwk_adapter_core2.DEVICE.CONNECT, {
525
557
  type: import_hwk_adapter_core2.DEVICE.CONNECT,
526
558
  payload: deviceInfo
527
559
  });
528
560
  };
529
561
  this.deviceDisconnectHandler = (data) => {
562
+ const lostSession = this._sessions.get(data.connectId);
563
+ const lostDeviceInfo = this._discoveredDevices.get(data.connectId);
530
564
  this._discoveredDevices.delete(data.connectId);
531
565
  this._sessions.delete(data.connectId);
566
+ debugLog(
567
+ "[SESS-DBG] deviceDisconnectHandler EVENT connectId=",
568
+ data.connectId,
569
+ "removedSessionId=",
570
+ lostSession ?? "(none)",
571
+ "removedDeviceId=",
572
+ lostDeviceInfo?.deviceId,
573
+ "removedSerial=",
574
+ lostDeviceInfo?.serialNumber,
575
+ "sessionsAfter=",
576
+ this._snapshotSessions()
577
+ );
532
578
  this.emitter.emit(import_hwk_adapter_core2.DEVICE.DISCONNECT, {
533
579
  type: import_hwk_adapter_core2.DEVICE.DISCONNECT,
534
580
  payload: { connectId: data.connectId }
@@ -541,7 +587,6 @@ var _LedgerAdapter = class _LedgerAdapter {
541
587
  this.emitter.emit("ui-event", event);
542
588
  };
543
589
  this.connector = connector;
544
- this._handleSelectDevice = options?.handleSelectDevice ?? false;
545
590
  this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue();
546
591
  this.registerEventListeners();
547
592
  }
@@ -590,12 +635,21 @@ var _LedgerAdapter = class _LedgerAdapter {
590
635
  // Device management
591
636
  // ---------------------------------------------------------------------------
592
637
  async searchDevices(options) {
638
+ debugLog(
639
+ "[SESS-DBG] searchDevices ENTER resetSession=",
640
+ options?.resetSession ?? false,
641
+ "sessionsBefore=",
642
+ this._snapshotSessions(),
643
+ "discoveredBefore=",
644
+ this._snapshotDiscovered()
645
+ );
593
646
  if (options?.resetSession) {
594
647
  this._doConnectAbortController?.abort();
595
648
  this._sessions.clear();
596
649
  this._connectingPromise = null;
597
650
  this._doConnectAbortController = null;
598
651
  this._btcHighIndexConfirmedThisSession = false;
652
+ debugLog("[SESS-DBG] searchDevices cleared _sessions (resetSession=true)");
599
653
  }
600
654
  await this._ensureDevicePermission();
601
655
  const devices = await this.connector.searchDevices();
@@ -609,32 +663,110 @@ var _LedgerAdapter = class _LedgerAdapter {
609
663
  await this._ensureDevicePermission();
610
664
  }
611
665
  debugLog(
612
- `[LedgerAdapter] searchDevices() return count=${this._discoveredDevices.size} ids=[${[
613
- ...this._discoveredDevices.keys()
614
- ].join(",")}]`
666
+ "[SESS-DBG] searchDevices RETURN count=",
667
+ this._discoveredDevices.size,
668
+ "devices=",
669
+ devices.map((d) => ({
670
+ connectId: d.connectId,
671
+ deviceId: d.deviceId,
672
+ name: d.name,
673
+ serial: d.serialNumber
674
+ })),
675
+ "sessionsAfter=",
676
+ this._snapshotSessions()
615
677
  );
616
678
  return Array.from(this._discoveredDevices.values());
617
679
  }
680
+ _snapshotSessions() {
681
+ return [...this._sessions.entries()].map(([connectId, sessionId]) => ({
682
+ connectId,
683
+ sessionId
684
+ }));
685
+ }
686
+ _snapshotDiscovered() {
687
+ return [...this._discoveredDevices.entries()].map(([connectId, info]) => ({
688
+ connectId,
689
+ deviceId: info.deviceId,
690
+ label: info.label,
691
+ serial: info.serialNumber
692
+ }));
693
+ }
694
+ // Enforce USB single-session invariant — see connectDevice.
695
+ async _evictAllSessions(reason) {
696
+ if (this._sessions.size === 0) return;
697
+ const stale = [...this._sessions.entries()];
698
+ debugLog(
699
+ "[SESS-DBG] evictAllSessions reason=",
700
+ reason,
701
+ "evicting=",
702
+ stale.map(([cid, sid]) => ({ connectId: cid, sessionId: sid }))
703
+ );
704
+ this._sessions.clear();
705
+ for (const [, sid] of stale) {
706
+ try {
707
+ await this.connector.disconnect(sid);
708
+ } catch (err) {
709
+ debugLog(
710
+ "[SESS-DBG] evictAllSessions disconnect failed sessionId=",
711
+ sid,
712
+ "err=",
713
+ err?.message
714
+ );
715
+ }
716
+ }
717
+ }
618
718
  static _createDeviceBusyError(method) {
619
719
  return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
620
720
  code: import_hwk_adapter_core2.HardwareErrorCode.DeviceBusy
621
721
  });
622
722
  }
623
723
  async connectDevice(connectId) {
724
+ debugLog(
725
+ "[SESS-DBG] connectDevice ENTER connectId=",
726
+ connectId || "(empty)",
727
+ "sessionsBefore=",
728
+ this._snapshotSessions(),
729
+ "discoveredBefore=",
730
+ this._snapshotDiscovered()
731
+ );
624
732
  try {
625
733
  if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
626
734
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
627
735
  code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
628
736
  });
629
737
  }
738
+ if (!isLedgerBleConnectionType(this.connector.connectionType)) {
739
+ await this._evictAllSessions("connectDevice");
740
+ }
630
741
  await this._ensureDevicePermission(connectId);
631
742
  const session = await this.connector.connect(connectId);
743
+ const prev = this._sessions.get(connectId);
632
744
  this._sessions.set(connectId, session.sessionId);
745
+ debugLog(
746
+ "[SESS-DBG] connectDevice SET _sessions connectId=",
747
+ connectId,
748
+ "newSessionId=",
749
+ session.sessionId,
750
+ "replacedPrevSessionId=",
751
+ prev ?? "(none)",
752
+ "sessionInfoDeviceId=",
753
+ session.deviceInfo?.deviceId,
754
+ "sessionInfoSerial=",
755
+ session.deviceInfo?.serialNumber,
756
+ "sessionsAfter=",
757
+ this._snapshotSessions()
758
+ );
633
759
  if (session.deviceInfo) {
634
760
  this._discoveredDevices.set(connectId, session.deviceInfo);
635
761
  }
636
762
  return (0, import_hwk_adapter_core2.success)(connectId);
637
763
  } catch (err) {
764
+ debugLog(
765
+ "[SESS-DBG] connectDevice ERROR connectId=",
766
+ connectId,
767
+ "err=",
768
+ err?.message
769
+ );
638
770
  return this.errorToFailure(err);
639
771
  }
640
772
  }
@@ -740,6 +872,62 @@ var _LedgerAdapter = class _LedgerAdapter {
740
872
  tronSignMessage(connectId, deviceId, params) {
741
873
  return this.callChain(connectId, deviceId, "tron", "tronSignMessage", params);
742
874
  }
875
+ // ---------------------------------------------------------------------------
876
+ // App management — OS-level Ledger app install / list. Bypass fingerprint
877
+ // (no chain identity to verify) and chain handler dispatch. Progress for
878
+ // installApp is forwarded to the adapter emitter as 'app-install-progress'
879
+ // events so consumers can render progress bars without subscribing to a
880
+ // separate channel.
881
+ // ---------------------------------------------------------------------------
882
+ async installApp(connectId, appName) {
883
+ try {
884
+ const params = {
885
+ appName,
886
+ onProgress: (progress) => {
887
+ this.emitter.emit("app-install-progress", {
888
+ type: "app-install-progress",
889
+ payload: { connectId, appName, ...progress }
890
+ });
891
+ }
892
+ };
893
+ await this.connectorCall(connectId, "installApp", params);
894
+ return (0, import_hwk_adapter_core2.success)(void 0);
895
+ } catch (err) {
896
+ return this.errorToFailure(err);
897
+ }
898
+ }
899
+ async listInstalledApps(connectId) {
900
+ try {
901
+ const result = await this.connectorCall(connectId, "listInstalledApps", {});
902
+ return (0, import_hwk_adapter_core2.success)(result);
903
+ } catch (err) {
904
+ return this.errorToFailure(err);
905
+ }
906
+ }
907
+ async listAvailableApps(connectId) {
908
+ try {
909
+ const result = await this.connectorCall(connectId, "listAvailableApps", {});
910
+ return (0, import_hwk_adapter_core2.success)(result);
911
+ } catch (err) {
912
+ return this.errorToFailure(err);
913
+ }
914
+ }
915
+ async getLedgerFirmwareVersion(connectId) {
916
+ try {
917
+ const result = await this.connectorCall(connectId, "getFirmwareVersion", {});
918
+ return (0, import_hwk_adapter_core2.success)(result);
919
+ } catch (err) {
920
+ return this.errorToFailure(err);
921
+ }
922
+ }
923
+ async getLedgerDeviceInfo(connectId) {
924
+ try {
925
+ const result = await this.connectorCall(connectId, "getDeviceInfo", {});
926
+ return (0, import_hwk_adapter_core2.success)(result);
927
+ } catch (err) {
928
+ return this.errorToFailure(err);
929
+ }
930
+ }
743
931
  on(event, listener) {
744
932
  this.emitter.on(event, listener);
745
933
  }
@@ -953,10 +1141,50 @@ var _LedgerAdapter = class _LedgerAdapter {
953
1141
  code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
954
1142
  });
955
1143
  }
956
- if (connectId && this._sessions.has(connectId)) return connectId;
957
- if (this._sessions.size > 0) {
958
- return this._sessions.keys().next().value;
1144
+ debugLog(
1145
+ "[SESS-DBG] ensureConnected ENTER inputConnectId=",
1146
+ connectId || "(empty)",
1147
+ "sessions=",
1148
+ this._snapshotSessions(),
1149
+ "discovered=",
1150
+ this._snapshotDiscovered()
1151
+ );
1152
+ if (connectId && this._sessions.has(connectId)) {
1153
+ debugLog(
1154
+ "[SESS-DBG] ensureConnected RESOLVED path=fast-cache connectId=",
1155
+ connectId,
1156
+ "sessionId=",
1157
+ this._sessions.get(connectId)
1158
+ );
1159
+ return connectId;
1160
+ }
1161
+ if (!connectId && this._sessions.size > 0) {
1162
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
1163
+ debugLog(
1164
+ "[SESS-DBG] ensureConnected ABORT multiple USB sessions present sessions=",
1165
+ this._snapshotSessions()
1166
+ );
1167
+ throw Object.assign(
1168
+ new Error(
1169
+ "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
1170
+ ),
1171
+ { code: import_hwk_adapter_core2.HardwareErrorCode.DeviceOneDeviceOnly }
1172
+ );
1173
+ }
1174
+ const ambient = this._sessions.keys().next().value;
1175
+ debugLog(
1176
+ "[SESS-DBG] ensureConnected RESOLVED path=ambient-fallback chosenConnectId=",
1177
+ ambient,
1178
+ "sessionId=",
1179
+ this._sessions.get(ambient)
1180
+ );
1181
+ return ambient;
959
1182
  }
1183
+ debugLog(
1184
+ "[SESS-DBG] ensureConnected path=doConnect (no fast match)",
1185
+ "connectId=",
1186
+ connectId || "(empty)"
1187
+ );
960
1188
  if (!this._connectingPromise) {
961
1189
  this._doConnectAbortController = new AbortController();
962
1190
  const innerSignal = this._doConnectAbortController.signal;
@@ -1038,28 +1266,44 @@ var _LedgerAdapter = class _LedgerAdapter {
1038
1266
  throw new Error("_doConnect aborted");
1039
1267
  }
1040
1268
  async _connectFirstOrSelect(devices, targetConnectId) {
1269
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length > 1) {
1270
+ debugLog(
1271
+ `[DMK] Multiple Ledger USB devices found (${devices.length}); refusing to auto-select by ephemeral connectId.`
1272
+ );
1273
+ throw createMultipleUsbLedgerDevicesError();
1274
+ }
1041
1275
  if (targetConnectId) {
1042
1276
  const target = devices.find(
1043
1277
  (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
1044
1278
  );
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;
1279
+ if (target) {
1280
+ return this._connectDeviceOrThrow(target.connectId);
1281
+ }
1282
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) {
1283
+ debugLog(
1284
+ `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)`
1285
+ );
1286
+ return this._connectDeviceOrThrow(devices[0].connectId);
1053
1287
  }
1054
- return this._connectDeviceOrThrow(target.connectId);
1288
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1289
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1290
+ });
1291
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1292
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
1293
+ }
1294
+ throw err;
1055
1295
  }
1056
1296
  if (isLedgerBleConnectionType(this.connector.connectionType)) {
1057
1297
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1058
1298
  code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1059
1299
  });
1060
1300
  }
1061
- const chosenConnectId = devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices);
1062
- return this._connectDeviceOrThrow(chosenConnectId);
1301
+ if (devices.length !== 1) {
1302
+ throw Object.assign(new Error("Ledger device not found."), {
1303
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1304
+ });
1305
+ }
1306
+ return this._connectDeviceOrThrow(devices[0].connectId);
1063
1307
  }
1064
1308
  async _connectDeviceOrThrow(chosenConnectId) {
1065
1309
  const result = await this.connectDevice(chosenConnectId);
@@ -1075,46 +1319,6 @@ var _LedgerAdapter = class _LedgerAdapter {
1075
1319
  }
1076
1320
  return chosenConnectId;
1077
1321
  }
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
- }
1118
1322
  /**
1119
1323
  * Call the connector with automatic session resolution and disconnect retry.
1120
1324
  *
@@ -1124,7 +1328,18 @@ var _LedgerAdapter = class _LedgerAdapter {
1124
1328
  * 4. On disconnect error: clears stale session, re-connects, retries once
1125
1329
  */
1126
1330
  async connectorCall(connectId, method, params, fingerprint, permissionDeviceId) {
1127
- debugLog("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
1331
+ debugLog(
1332
+ "[SESS-DBG] connectorCall ENTER method=",
1333
+ method,
1334
+ "callerConnectId=",
1335
+ connectId || "(empty)",
1336
+ "callerDeviceId=",
1337
+ fingerprint?.deviceId ?? permissionDeviceId ?? "(none)",
1338
+ "sessions=",
1339
+ this._snapshotSessions(),
1340
+ "discovered=",
1341
+ this._snapshotDiscovered()
1342
+ );
1128
1343
  const queueKey = connectId || "__ledger_default__";
1129
1344
  return this._jobQueue.enqueue(
1130
1345
  queueKey,
@@ -1279,8 +1494,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1279
1494
  } catch {
1280
1495
  }
1281
1496
  }
1282
- const retryConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1283
- const reConnectId = await this.ensureConnected(retryConnectId, signal);
1497
+ const reConnectId = await this.ensureConnected(resolvedConnectId, signal);
1284
1498
  const reSessionId = this._sessions.get(reConnectId);
1285
1499
  if (!reSessionId) throw lastErr;
1286
1500
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
@@ -1348,8 +1562,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1348
1562
  */
1349
1563
  async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
1350
1564
  await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
1351
- const retryTargetConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1352
- const retryConnectId = await this.ensureConnected(retryTargetConnectId, signal);
1565
+ const retryConnectId = await this.ensureConnected(resolvedConnectId, signal);
1353
1566
  const retrySessionId = this._sessions.get(retryConnectId);
1354
1567
  if (!retrySessionId) throw originalErr;
1355
1568
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
@@ -1527,7 +1740,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1527
1740
  const tag = err && typeof err === "object" ? err._tag : void 0;
1528
1741
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
1529
1742
  const e = err;
1530
- const params = e.code === import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : void 0;
1743
+ const params = e.code === import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
1531
1744
  return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
1532
1745
  }
1533
1746
  const mapped = mapLedgerError(err);
@@ -1577,7 +1790,7 @@ _LedgerAdapter.MAX_DOCONNECT_CONFIRMS = 3;
1577
1790
  var LedgerAdapter = _LedgerAdapter;
1578
1791
 
1579
1792
  // src/connector/LedgerConnectorBase.ts
1580
- var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
1793
+ var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
1581
1794
 
1582
1795
  // src/device/LedgerDeviceManager.ts
1583
1796
  var LedgerDeviceManager = class {
@@ -1855,7 +2068,7 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
1855
2068
  // src/signer/deviceActionToPromise.ts
1856
2069
  var import_device_management_kit = require("@ledgerhq/device-management-kit");
1857
2070
  var IDLE_WATCHDOG_MS = 65e3;
1858
- function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller) {
2071
+ function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller, onIntermediate) {
1859
2072
  return new Promise((resolve, reject) => {
1860
2073
  let settled = false;
1861
2074
  let lastStep;
@@ -1937,14 +2150,22 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_
1937
2150
  onInteraction?.("interaction-complete");
1938
2151
  sub?.unsubscribe();
1939
2152
  rejectWithStepContext(state.error, lastStep, observedSteps, reject);
1940
- } else if (state.status === import_device_management_kit.DeviceActionStatus.Pending && onInteraction) {
1941
- const interaction = state.intermediateValue?.requiredUserInteraction;
1942
- if (interaction && interaction !== "none") {
1943
- if (interaction === "unlock-device" && timer) {
1944
- clearTimeout(timer);
1945
- timer = null;
2153
+ } else if (state.status === import_device_management_kit.DeviceActionStatus.Pending) {
2154
+ if (onIntermediate) {
2155
+ try {
2156
+ onIntermediate(state.intermediateValue);
2157
+ } catch {
2158
+ }
2159
+ }
2160
+ if (onInteraction) {
2161
+ const interaction = state.intermediateValue?.requiredUserInteraction;
2162
+ if (interaction && interaction !== "none") {
2163
+ if (interaction === "unlock-device" && timer) {
2164
+ clearTimeout(timer);
2165
+ timer = null;
2166
+ }
2167
+ onInteraction(String(interaction));
1946
2168
  }
1947
- onInteraction(String(interaction));
1948
2169
  }
1949
2170
  }
1950
2171
  },
@@ -2983,6 +3204,336 @@ async function _createTrx(ctx, sessionId) {
2983
3204
  return new import_hw_app_trx.default(new DmkTransport(dmk, sessionId));
2984
3205
  }
2985
3206
 
3207
+ // src/device-apps/DeviceApps.ts
3208
+ var import_device_management_kit3 = require("@ledgerhq/device-management-kit");
3209
+ var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
3210
+ var import_rxjs = require("rxjs");
3211
+ var INSTALL_TIMEOUT_MS = 5 * 6e4;
3212
+ var DeviceApps = class {
3213
+ constructor(_dmk, _sessionId, _ledgerKit) {
3214
+ this._dmk = _dmk;
3215
+ this._sessionId = _sessionId;
3216
+ this._ledgerKit = _ledgerKit;
3217
+ }
3218
+ async listInstalled(options) {
3219
+ const action = this._dmk.executeDeviceAction({
3220
+ sessionId: this._sessionId,
3221
+ deviceAction: new this._ledgerKit.ListAppsWithMetadataDeviceAction({
3222
+ input: { unlockTimeout: options?.unlockTimeout }
3223
+ })
3224
+ });
3225
+ const result = await deviceActionToPromise(
3226
+ action,
3227
+ this.onInteraction,
3228
+ void 0,
3229
+ this.onRegisterCanceller
3230
+ );
3231
+ return result.filter((a) => a !== null).map(applicationToMetadata);
3232
+ }
3233
+ // Catalog lookup via custom device action — DMK has no typed wrapper for this.
3234
+ async listAvailable() {
3235
+ const customAction = new ListAvailableAppsDeviceAction({
3236
+ GetOsVersionCommand: this._ledgerKit.GetOsVersionCommand,
3237
+ isSuccessCommandResult: this._ledgerKit.isSuccessCommandResult
3238
+ });
3239
+ const action = this._dmk.executeDeviceAction({
3240
+ sessionId: this._sessionId,
3241
+ deviceAction: customAction
3242
+ });
3243
+ const result = await deviceActionToPromise(
3244
+ action,
3245
+ this.onInteraction,
3246
+ void 0,
3247
+ this.onRegisterCanceller
3248
+ );
3249
+ return result.map(applicationToMetadata);
3250
+ }
3251
+ async getFirmwareVersion() {
3252
+ const v = await this._fetchOsVersion();
3253
+ return {
3254
+ seVersion: v.seVersion,
3255
+ mcuSephVersion: v.mcuSephVersion,
3256
+ mcuBootloaderVersion: v.mcuBootloaderVersion,
3257
+ hwVersion: v.hwVersion
3258
+ };
3259
+ }
3260
+ async getDeviceInfo() {
3261
+ const v = await this._fetchOsVersion();
3262
+ return {
3263
+ isBootloader: v.isBootloader,
3264
+ isOsu: v.isOsu,
3265
+ targetId: v.targetId,
3266
+ seTargetId: v.seTargetId,
3267
+ mcuTargetId: v.mcuTargetId,
3268
+ seVersion: v.seVersion,
3269
+ seFlagsHex: bytesToHex2(v.seFlags),
3270
+ mcuSephVersion: v.mcuSephVersion,
3271
+ mcuBootloaderVersion: v.mcuBootloaderVersion,
3272
+ hwVersion: v.hwVersion
3273
+ };
3274
+ }
3275
+ /**
3276
+ * Pre-flight space check. Mirrors DMK's PredictOutOfMemoryTask using the
3277
+ * data we already expose (listInstalled / listAvailable / getOsVersion).
3278
+ * Fails fast with HardwareErrorCode.DeviceOutOfMemory before issuing the
3279
+ * actual install — saves the user the time-to-fail of DMK's state machine.
3280
+ */
3281
+ async _assertEnoughSpace(appName) {
3282
+ let osVersion;
3283
+ let installed;
3284
+ let available;
3285
+ try {
3286
+ [osVersion, installed, available] = await Promise.all([
3287
+ this._fetchOsVersion(),
3288
+ this.listInstalled(),
3289
+ this.listAvailable()
3290
+ ]);
3291
+ } catch (err) {
3292
+ debugLog("[DeviceApps] precheck skipped:", err?.message);
3293
+ return;
3294
+ }
3295
+ if (installed.some((a) => a.versionName === appName)) return;
3296
+ const target = available.find((a) => a.versionName === appName);
3297
+ if (!target) return;
3298
+ const capacity = getDeviceCapacity(osVersion.targetId);
3299
+ if (!capacity) return;
3300
+ const blockSize = capacity.getBlockSize(osVersion.seVersion);
3301
+ const totalBlocks = Math.floor(capacity.memorySize / blockSize);
3302
+ const blocks = (b) => Math.ceil((b ?? 0) / blockSize);
3303
+ const usedBlocks = installed.reduce((sum, a) => sum + blocks(a.bytes), 0);
3304
+ const neededBlocks = blocks(target.bytes);
3305
+ debugLog(
3306
+ "[DeviceApps] space check",
3307
+ "targetId=",
3308
+ osVersion.targetId.toString(16),
3309
+ "memorySize=",
3310
+ capacity.memorySize,
3311
+ "blockSize=",
3312
+ blockSize,
3313
+ "usedBlocks=",
3314
+ usedBlocks,
3315
+ "neededBlocks=",
3316
+ neededBlocks,
3317
+ "totalBlocks=",
3318
+ totalBlocks
3319
+ );
3320
+ if (usedBlocks + neededBlocks > totalBlocks) {
3321
+ const usedBytes = usedBlocks * blockSize;
3322
+ const neededBytes = neededBlocks * blockSize;
3323
+ const freeBytes = Math.max(0, capacity.memorySize - usedBytes);
3324
+ throw Object.assign(
3325
+ new Error(
3326
+ `Not enough space to install "${appName}": needs ${neededBytes} bytes, ${freeBytes} bytes free of ${capacity.memorySize}.`
3327
+ ),
3328
+ {
3329
+ code: import_hwk_adapter_core12.HardwareErrorCode.DeviceOutOfMemory,
3330
+ _tag: "OutOfMemoryDAError",
3331
+ appName,
3332
+ params: {
3333
+ requiredBytes: neededBytes,
3334
+ availableBytes: freeBytes,
3335
+ totalBytes: capacity.memorySize
3336
+ }
3337
+ }
3338
+ );
3339
+ }
3340
+ }
3341
+ // Sole sendCommand wrapper — surfaces unlock-device interaction through
3342
+ // the same onInteraction pipeline as the device-action methods.
3343
+ async _fetchOsVersion() {
3344
+ const customAction = new GetOsVersionDeviceAction({
3345
+ GetOsVersionCommand: this._ledgerKit.GetOsVersionCommand,
3346
+ isSuccessCommandResult: this._ledgerKit.isSuccessCommandResult
3347
+ });
3348
+ const action = this._dmk.executeDeviceAction({
3349
+ sessionId: this._sessionId,
3350
+ deviceAction: customAction
3351
+ });
3352
+ return deviceActionToPromise(
3353
+ action,
3354
+ this.onInteraction,
3355
+ void 0,
3356
+ this.onRegisterCanceller
3357
+ );
3358
+ }
3359
+ async install(appName, onProgress, options) {
3360
+ if (!appName) throw new Error("DeviceApps.install: appName is required");
3361
+ debugLog("[DeviceApps] install:", appName);
3362
+ await this._assertEnoughSpace(appName);
3363
+ const action = this._dmk.executeDeviceAction({
3364
+ sessionId: this._sessionId,
3365
+ deviceAction: new this._ledgerKit.InstallAppDeviceAction({
3366
+ input: { appName, unlockTimeout: options?.unlockTimeout }
3367
+ })
3368
+ });
3369
+ await deviceActionToPromise(
3370
+ action,
3371
+ this.onInteraction,
3372
+ INSTALL_TIMEOUT_MS,
3373
+ this.onRegisterCanceller,
3374
+ onProgress ? (intermediateValue) => {
3375
+ const iv = intermediateValue;
3376
+ if (typeof iv?.progress === "number") {
3377
+ onProgress({
3378
+ progress: iv.progress,
3379
+ requiredUserInteraction: iv.requiredUserInteraction
3380
+ });
3381
+ }
3382
+ } : void 0
3383
+ );
3384
+ }
3385
+ };
3386
+ function bytesToHex2(bytes) {
3387
+ if (!bytes) return "";
3388
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
3389
+ }
3390
+ function applicationToMetadata(app) {
3391
+ return {
3392
+ versionName: app.versionName,
3393
+ versionId: app.versionId,
3394
+ version: app.version,
3395
+ versionDisplayName: app.versionDisplayName,
3396
+ description: app.description,
3397
+ icon: app.icon,
3398
+ bytes: app.bytes,
3399
+ currencyId: app.currencyId,
3400
+ isDevTools: app.isDevTools
3401
+ };
3402
+ }
3403
+ var DEVICE_CAPACITIES = [
3404
+ // Nano S — block size 4K on fw<2.0, 2K on >=2.0. Default 4K (conservative).
3405
+ {
3406
+ mask: 823132160,
3407
+ capacity: {
3408
+ memorySize: 320 * 1024,
3409
+ getBlockSize: (fw) => parseMajor(fw) >= 2 ? 2 * 1024 : 4 * 1024
3410
+ }
3411
+ },
3412
+ // Nano S Plus
3413
+ { mask: 856686592, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } },
3414
+ // Nano X
3415
+ { mask: 855638016, capacity: { memorySize: 2 * 1024 * 1024, getBlockSize: () => 4 * 1024 } },
3416
+ // Stax
3417
+ { mask: 857735168, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } },
3418
+ // Flex
3419
+ { mask: 858783744, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } },
3420
+ // Apex / Nano Gen5
3421
+ { mask: 859832320, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } }
3422
+ ];
3423
+ function getDeviceCapacity(targetId) {
3424
+ const masked = targetId & 4294901760;
3425
+ return DEVICE_CAPACITIES.find((d) => d.mask === masked)?.capacity;
3426
+ }
3427
+ function parseMajor(version) {
3428
+ const m = /^(\d+)/.exec(version);
3429
+ return m ? Number(m[1]) : 0;
3430
+ }
3431
+ var GetOsVersionDeviceAction = class {
3432
+ constructor(_deps) {
3433
+ this._deps = _deps;
3434
+ this.input = void 0;
3435
+ }
3436
+ _execute(internalApi) {
3437
+ const subject = new import_rxjs.Subject();
3438
+ let cancelled = false;
3439
+ (async () => {
3440
+ try {
3441
+ subject.next({
3442
+ status: import_device_management_kit3.DeviceActionStatus.Pending,
3443
+ intermediateValue: { requiredUserInteraction: "none" }
3444
+ });
3445
+ const result = await internalApi.sendCommand(new this._deps.GetOsVersionCommand());
3446
+ if (cancelled) return;
3447
+ if (!this._deps.isSuccessCommandResult(result)) {
3448
+ const errObj = result?.error;
3449
+ throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
3450
+ }
3451
+ subject.next({ status: import_device_management_kit3.DeviceActionStatus.Completed, output: result.data });
3452
+ subject.complete();
3453
+ } catch (err) {
3454
+ if (cancelled) return;
3455
+ subject.next({ status: import_device_management_kit3.DeviceActionStatus.Error, error: err });
3456
+ subject.complete();
3457
+ }
3458
+ })();
3459
+ return {
3460
+ observable: subject.asObservable(),
3461
+ cancel: () => {
3462
+ cancelled = true;
3463
+ subject.next({ status: import_device_management_kit3.DeviceActionStatus.Stopped });
3464
+ subject.complete();
3465
+ }
3466
+ };
3467
+ }
3468
+ };
3469
+ var ListAvailableAppsDeviceAction = class {
3470
+ constructor(deps) {
3471
+ this.input = void 0;
3472
+ this._GetOsVersionCommand = deps.GetOsVersionCommand;
3473
+ this._isSuccessCommandResult = deps.isSuccessCommandResult;
3474
+ }
3475
+ _execute(internalApi) {
3476
+ const subject = new import_rxjs.Subject();
3477
+ let cancelled = false;
3478
+ (async () => {
3479
+ try {
3480
+ subject.next({
3481
+ status: import_device_management_kit3.DeviceActionStatus.Pending,
3482
+ intermediateValue: { requiredUserInteraction: "none" }
3483
+ });
3484
+ const osVersionResult = await internalApi.sendCommand(new this._GetOsVersionCommand());
3485
+ if (cancelled) return;
3486
+ if (!this._isSuccessCommandResult(osVersionResult)) {
3487
+ const errObj = osVersionResult?.error;
3488
+ throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
3489
+ }
3490
+ const managerApi = internalApi.getManagerApiService();
3491
+ const either = await managerApi.getAppList(osVersionResult.data).run();
3492
+ if (cancelled) return;
3493
+ if (either.isLeft()) {
3494
+ const httpErr = either.extract();
3495
+ throw new Error(httpErr?.message ?? "Manager API getAppList failed");
3496
+ }
3497
+ const apps = either.extract();
3498
+ subject.next({ status: import_device_management_kit3.DeviceActionStatus.Completed, output: apps });
3499
+ subject.complete();
3500
+ } catch (err) {
3501
+ if (cancelled) return;
3502
+ subject.next({ status: import_device_management_kit3.DeviceActionStatus.Error, error: err });
3503
+ subject.complete();
3504
+ }
3505
+ })();
3506
+ return {
3507
+ observable: subject.asObservable(),
3508
+ cancel: () => {
3509
+ cancelled = true;
3510
+ subject.next({ status: import_device_management_kit3.DeviceActionStatus.Stopped });
3511
+ subject.complete();
3512
+ }
3513
+ };
3514
+ }
3515
+ };
3516
+
3517
+ // src/device-apps/DeviceAppsManager.ts
3518
+ var DeviceAppsManager = class {
3519
+ constructor(dmk, importLedgerKit) {
3520
+ this._dmk = dmk;
3521
+ this._importLedgerKit = importLedgerKit;
3522
+ }
3523
+ async getOrCreate(sessionId) {
3524
+ const ledgerKit = await this._importLedgerKit(
3525
+ "@ledgerhq/device-management-kit"
3526
+ );
3527
+ return new DeviceApps(this._dmk, sessionId, ledgerKit);
3528
+ }
3529
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this
3530
+ invalidate(_sessionId) {
3531
+ }
3532
+ // eslint-disable-next-line class-methods-use-this
3533
+ clearAll() {
3534
+ }
3535
+ };
3536
+
2986
3537
  // src/connector/LedgerConnectorBase.ts
2987
3538
  var METHOD_PREFIX_TO_APP_NAME = {
2988
3539
  evm: "Ethereum",
@@ -2991,7 +3542,7 @@ var METHOD_PREFIX_TO_APP_NAME = {
2991
3542
  tron: "Tron"
2992
3543
  };
2993
3544
  var HARDWARE_ERROR_CODE_VALUES = new Set(
2994
- Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
3545
+ Object.values(import_hwk_adapter_core13.HardwareErrorCode).filter((value) => typeof value === "number")
2995
3546
  );
2996
3547
  var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
2997
3548
  async function defaultLedgerKitImporter(pkg) {
@@ -3010,10 +3561,25 @@ async function defaultLedgerKitImporter(pkg) {
3010
3561
  throw new Error(`Unknown Ledger kit package: ${pkg}`);
3011
3562
  }
3012
3563
  }
3564
+ async function _getDeviceApps(ctx, sessionId) {
3565
+ const manager = await ctx.getDeviceAppsManager();
3566
+ const apps = await manager.getOrCreate(sessionId);
3567
+ apps.onInteraction = (interaction) => {
3568
+ ctx.emit("ui-event", {
3569
+ type: collapseSignerInteraction(interaction),
3570
+ payload: { sessionId }
3571
+ });
3572
+ };
3573
+ apps.onRegisterCanceller = (cancel) => {
3574
+ ctx.registerCanceller(sessionId, cancel);
3575
+ };
3576
+ return apps;
3577
+ }
3013
3578
  var LedgerConnectorBase = class {
3014
3579
  constructor(createTransport, options) {
3015
3580
  this._deviceManager = null;
3016
3581
  this._signerManager = null;
3582
+ this._deviceAppsManager = null;
3017
3583
  this._dmk = null;
3018
3584
  this._eventHandlers = /* @__PURE__ */ new Map();
3019
3585
  // ---------------------------------------------------------------------------
@@ -3052,6 +3618,7 @@ var LedgerConnectorBase = class {
3052
3618
  getOrCreateDmk: () => this._getOrCreateDmk(),
3053
3619
  getDeviceManager: () => this._getDeviceManager(),
3054
3620
  getSignerManager: () => this._getSignerManager(),
3621
+ getDeviceAppsManager: () => this._getDeviceAppsManager(),
3055
3622
  clearAllSigners: () => this._signerManager?.clearAll(),
3056
3623
  replaceSession: (oldSid, newSid) => this._replaceSession(oldSid, newSid),
3057
3624
  registerCanceller: (sid, cancel) => this._cancellers.set(sid, cancel),
@@ -3086,12 +3653,22 @@ var LedgerConnectorBase = class {
3086
3653
  }
3087
3654
  return descriptors;
3088
3655
  }
3656
+ _assertSingleUsbDescriptor(descriptors) {
3657
+ if (isLedgerBleConnectionType(this.connectionType) || descriptors.length <= 1) {
3658
+ return;
3659
+ }
3660
+ debugLog(
3661
+ `[DMK] Multiple Ledger USB devices found (${descriptors.length}); refusing to choose by ephemeral path. paths=[${descriptors.map((d) => d.path).join(",")}]`
3662
+ );
3663
+ throw createMultipleUsbLedgerDevicesError();
3664
+ }
3089
3665
  // ---------------------------------------------------------------------------
3090
3666
  // IConnector -- Device discovery
3091
3667
  // ---------------------------------------------------------------------------
3092
3668
  async searchDevices() {
3093
3669
  const dm = await this._getDeviceManager();
3094
3670
  const descriptors = await this._discoverDescriptors(dm);
3671
+ this._assertSingleUsbDescriptor(descriptors);
3095
3672
  const resolvedDescriptors = descriptors.map((d) => ({
3096
3673
  descriptor: d,
3097
3674
  connectId: this._resolveConnectId(d)
@@ -3117,6 +3694,10 @@ var LedgerConnectorBase = class {
3117
3694
  async connect(deviceId) {
3118
3695
  const callerSuppliedConnectId = Boolean(deviceId);
3119
3696
  let targetPath = deviceId;
3697
+ if (callerSuppliedConnectId && !isLedgerBleConnectionType(this.connectionType)) {
3698
+ const dm = await this._getDeviceManager();
3699
+ this._assertSingleUsbDescriptor(await this._discoverDescriptors(dm));
3700
+ }
3120
3701
  if (!targetPath) {
3121
3702
  const discovered = await this.searchDevices();
3122
3703
  if (discovered.length === 0) {
@@ -3140,7 +3721,7 @@ var LedgerConnectorBase = class {
3140
3721
  `Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
3141
3722
  );
3142
3723
  err._tag = ERROR_TAG.BlePairingTimeout;
3143
- err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
3724
+ err.code = import_hwk_adapter_core13.HardwareErrorCode.BlePairingTimeout;
3144
3725
  reject(err);
3145
3726
  }, HANG_CEILING_MS);
3146
3727
  });
@@ -3173,7 +3754,7 @@ var LedgerConnectorBase = class {
3173
3754
  "Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
3174
3755
  );
3175
3756
  err._tag = ERROR_TAG.DeviceNotAdvertising;
3176
- err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
3757
+ err.code = import_hwk_adapter_core13.HardwareErrorCode.DeviceNotFound;
3177
3758
  throw err;
3178
3759
  };
3179
3760
  const doConnect = async (path) => {
@@ -3193,7 +3774,19 @@ var LedgerConnectorBase = class {
3193
3774
  }
3194
3775
  throw err;
3195
3776
  }
3196
- this._watchSessionState(sessionId, externalConnectId);
3777
+ try {
3778
+ this._watchSessionState(sessionId, externalConnectId);
3779
+ } catch (subErr) {
3780
+ debugLog(
3781
+ "[DMK] state subscription failed during connect; disconnecting session:",
3782
+ subErr
3783
+ );
3784
+ try {
3785
+ await dm.disconnect(sessionId);
3786
+ } catch {
3787
+ }
3788
+ throw subErr;
3789
+ }
3197
3790
  const info = dm.getDiscoveredDeviceInfo(path);
3198
3791
  const session = {
3199
3792
  sessionId,
@@ -3210,6 +3803,18 @@ var LedgerConnectorBase = class {
3210
3803
  }
3211
3804
  };
3212
3805
  const name = dm.getDeviceName(path) ?? "Ledger";
3806
+ debugLog(
3807
+ "[SESS-DBG] connector.connect SUCCESS dmPath=",
3808
+ path,
3809
+ "externalConnectId=",
3810
+ externalConnectId,
3811
+ "newSessionId=",
3812
+ sessionId,
3813
+ "name=",
3814
+ name,
3815
+ "model=",
3816
+ info?.model
3817
+ );
3213
3818
  this._emit("device-connect", {
3214
3819
  device: { connectId: externalConnectId, deviceId: path, name }
3215
3820
  });
@@ -3228,7 +3833,7 @@ var LedgerConnectorBase = class {
3228
3833
  "Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
3229
3834
  );
3230
3835
  wrapped._tag = ERROR_TAG.BleGattBondingFailed;
3231
- wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
3836
+ wrapped.code = import_hwk_adapter_core13.HardwareErrorCode.BlePairingTimeout;
3232
3837
  wrapped.originalError = err;
3233
3838
  throw wrapped;
3234
3839
  }
@@ -3272,24 +3877,48 @@ var LedgerConnectorBase = class {
3272
3877
  } catch {
3273
3878
  }
3274
3879
  }
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: () => {
3880
+ debugLog(
3881
+ "[SESS-DBG] _watchSessionState SUBSCRIBE sessionId=",
3882
+ sessionId,
3883
+ "connectId=",
3884
+ externalConnectId
3885
+ );
3886
+ const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
3887
+ next: (state) => {
3888
+ debugLog(
3889
+ "[SESS-DBG] _watchSessionState NEXT sessionId=",
3890
+ sessionId,
3891
+ "connectId=",
3892
+ externalConnectId,
3893
+ "deviceStatus=",
3894
+ state?.deviceStatus
3895
+ );
3896
+ if (state?.deviceStatus === "NOT CONNECTED") {
3286
3897
  this._handleAutonomousDisconnect(sessionId, externalConnectId);
3287
3898
  }
3288
- });
3289
- this._sessionStateSubs.set(sessionId, sub);
3290
- } catch (err) {
3291
- debugLog("[DMK] _watchSessionState subscribe failed:", err);
3292
- }
3899
+ },
3900
+ error: (err) => {
3901
+ debugLog(
3902
+ "[SESS-DBG] _watchSessionState ERROR sessionId=",
3903
+ sessionId,
3904
+ "connectId=",
3905
+ externalConnectId,
3906
+ "err=",
3907
+ err?.message
3908
+ );
3909
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3910
+ },
3911
+ complete: () => {
3912
+ debugLog(
3913
+ "[SESS-DBG] _watchSessionState COMPLETE sessionId=",
3914
+ sessionId,
3915
+ "connectId=",
3916
+ externalConnectId
3917
+ );
3918
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3919
+ }
3920
+ });
3921
+ this._sessionStateSubs.set(sessionId, sub);
3293
3922
  }
3294
3923
  _unwatchSessionState(sessionId) {
3295
3924
  const sub = this._sessionStateSubs.get(sessionId);
@@ -3301,7 +3930,21 @@ var LedgerConnectorBase = class {
3301
3930
  this._sessionStateSubs.delete(sessionId);
3302
3931
  }
3303
3932
  _handleAutonomousDisconnect(sessionId, externalConnectId) {
3304
- if (!this._sessionStateSubs.has(sessionId)) return;
3933
+ if (!this._sessionStateSubs.has(sessionId)) {
3934
+ debugLog(
3935
+ "[SESS-DBG] _handleAutonomousDisconnect SKIP (already handled) sessionId=",
3936
+ sessionId,
3937
+ "connectId=",
3938
+ externalConnectId
3939
+ );
3940
+ return;
3941
+ }
3942
+ debugLog(
3943
+ "[SESS-DBG] _handleAutonomousDisconnect FIRE sessionId=",
3944
+ sessionId,
3945
+ "connectId=",
3946
+ externalConnectId
3947
+ );
3305
3948
  debugLog(
3306
3949
  "[DMK] autonomous disconnect detected \u2014 sessionId:",
3307
3950
  sessionId,
@@ -3311,7 +3954,7 @@ var LedgerConnectorBase = class {
3311
3954
  this._unwatchSessionState(sessionId);
3312
3955
  this._signerManager?.invalidate(sessionId);
3313
3956
  this._cancellers.get(sessionId)?.({
3314
- code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
3957
+ code: import_hwk_adapter_core13.HardwareErrorCode.DeviceDisconnected,
3315
3958
  tag: "DeviceDisconnected",
3316
3959
  message: "Device disconnected"
3317
3960
  });
@@ -3328,14 +3971,14 @@ var LedgerConnectorBase = class {
3328
3971
  } catch (err) {
3329
3972
  if (isAppStuckByApdu(err)) {
3330
3973
  throw Object.assign(new Error("Ledger app is unresponsive"), {
3331
- code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
3974
+ code: import_hwk_adapter_core13.HardwareErrorCode.DeviceAppStuck,
3332
3975
  _tag: ERROR_TAG.DeviceAppStuck,
3333
3976
  originalError: err
3334
3977
  });
3335
3978
  }
3336
3979
  if (isTransportStuck(err)) {
3337
3980
  throw Object.assign(new Error("Device communication interrupted, please retry"), {
3338
- code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
3981
+ code: import_hwk_adapter_core13.HardwareErrorCode.TransportError,
3339
3982
  _tag: ERROR_TAG.DeviceTransportStuck,
3340
3983
  originalError: err
3341
3984
  });
@@ -3392,6 +4035,49 @@ var LedgerConnectorBase = class {
3392
4035
  };
3393
4036
  return tronSignMessage(ctx, sessionId, internalParams);
3394
4037
  }
4038
+ // OS-level device management — symmetric to chain handlers.
4039
+ // _getDeviceApps wires onInteraction/onRegisterCanceller like _getEthSigner.
4040
+ case "installApp": {
4041
+ const p = params;
4042
+ const apps = await _getDeviceApps(ctx, sessionId);
4043
+ try {
4044
+ return await apps.install(p.appName, p.onProgress);
4045
+ } finally {
4046
+ ctx.clearCanceller(sessionId);
4047
+ }
4048
+ }
4049
+ case "listInstalledApps": {
4050
+ const apps = await _getDeviceApps(ctx, sessionId);
4051
+ try {
4052
+ return await apps.listInstalled(params);
4053
+ } finally {
4054
+ ctx.clearCanceller(sessionId);
4055
+ }
4056
+ }
4057
+ case "listAvailableApps": {
4058
+ const apps = await _getDeviceApps(ctx, sessionId);
4059
+ try {
4060
+ return await apps.listAvailable();
4061
+ } finally {
4062
+ ctx.clearCanceller(sessionId);
4063
+ }
4064
+ }
4065
+ case "getFirmwareVersion": {
4066
+ const apps = await _getDeviceApps(ctx, sessionId);
4067
+ try {
4068
+ return await apps.getFirmwareVersion();
4069
+ } finally {
4070
+ ctx.clearCanceller(sessionId);
4071
+ }
4072
+ }
4073
+ case "getDeviceInfo": {
4074
+ const apps = await _getDeviceApps(ctx, sessionId);
4075
+ try {
4076
+ return await apps.getDeviceInfo();
4077
+ } finally {
4078
+ ctx.clearCanceller(sessionId);
4079
+ }
4080
+ }
3395
4081
  default:
3396
4082
  throw new Error(`LedgerConnector: unknown method "${method}"`);
3397
4083
  }
@@ -3464,6 +4150,7 @@ var LedgerConnectorBase = class {
3464
4150
  const mod = await importKit("@ledgerhq/device-signer-kit-ethereum");
3465
4151
  return new mod.SignerEthBuilder(args);
3466
4152
  });
4153
+ this._deviceAppsManager = new DeviceAppsManager(dmk, importKit);
3467
4154
  }
3468
4155
  async _getDeviceManager() {
3469
4156
  if (this._deviceManager) return this._deviceManager;
@@ -3478,6 +4165,13 @@ var LedgerConnectorBase = class {
3478
4165
  }
3479
4166
  return this._signerManager;
3480
4167
  }
4168
+ async _getDeviceAppsManager() {
4169
+ if (!this._deviceAppsManager) {
4170
+ const dmk = await this._getOrCreateDmk();
4171
+ this._initManagers(dmk);
4172
+ }
4173
+ return this._deviceAppsManager;
4174
+ }
3481
4175
  _invalidateSession(sessionId) {
3482
4176
  this._signerManager?.invalidate(sessionId);
3483
4177
  }
@@ -3513,6 +4207,8 @@ var LedgerConnectorBase = class {
3513
4207
  debugLog("[DMK] _resetSignersAndSessions called");
3514
4208
  this._signerManager?.clearAll();
3515
4209
  this._signerManager = null;
4210
+ this._deviceAppsManager?.clearAll();
4211
+ this._deviceAppsManager = null;
3516
4212
  this._deviceManager?.disposeKeepingDmk();
3517
4213
  this._deviceManager = null;
3518
4214
  }
@@ -3533,9 +4229,11 @@ var LedgerConnectorBase = class {
3533
4229
  }
3534
4230
  this._sessionStateSubs.clear();
3535
4231
  this._signerManager?.clearAll();
4232
+ this._deviceAppsManager?.clearAll();
3536
4233
  this._deviceManager?.dispose();
3537
4234
  this._deviceManager = null;
3538
4235
  this._signerManager = null;
4236
+ this._deviceAppsManager = null;
3539
4237
  this._dmk = null;
3540
4238
  }
3541
4239
  // ---------------------------------------------------------------------------