@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.mjs CHANGED
@@ -16,6 +16,12 @@ import {
16
16
 
17
17
  // src/errors.ts
18
18
  import { HardwareErrorCode, enrichErrorMessage } from "@onekeyfe/hwk-adapter-core";
19
+ var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
20
+ function createMultipleUsbLedgerDevicesError() {
21
+ return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
22
+ code: HardwareErrorCode.DeviceOneDeviceOnly
23
+ });
24
+ }
19
25
  function ledgerFailure(code, error, appName, tag, params) {
20
26
  const payload = { error, code };
21
27
  if (appName !== void 0) payload.appName = appName;
@@ -300,6 +306,15 @@ function isAppNotInstalledError(err) {
300
306
  if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
301
307
  return false;
302
308
  }
309
+ function isOutOfMemoryError(err) {
310
+ if (!err || typeof err !== "object") return false;
311
+ const e = err;
312
+ if (e._tag === "OutOfMemoryDAError") return true;
313
+ if (typeof e.message === "string" && /out of memory|not enough.*space|insufficient.*memory/i.test(e.message)) {
314
+ return true;
315
+ }
316
+ return false;
317
+ }
303
318
  function isDeviceDisconnectedError(err) {
304
319
  if (!err || typeof err !== "object") return false;
305
320
  const e = err;
@@ -362,6 +377,8 @@ function mapLedgerError(err, opts) {
362
377
  code = HardwareErrorCode.WrongApp;
363
378
  } else if (isAppNotInstalledError(err)) {
364
379
  code = HardwareErrorCode.AppNotInstalled;
380
+ } else if (isOutOfMemoryError(err)) {
381
+ code = HardwareErrorCode.DeviceOutOfMemory;
365
382
  } else if (isDeviceDisconnectedError(err)) {
366
383
  code = HardwareErrorCode.DeviceDisconnected;
367
384
  } else if (isTimeoutError(err)) {
@@ -447,7 +464,7 @@ function btcAccountIndexFromPath(path) {
447
464
  return Number.isFinite(accountIndex) ? accountIndex : null;
448
465
  }
449
466
  var _LedgerAdapter = class _LedgerAdapter {
450
- constructor(connector, options) {
467
+ constructor(connector) {
451
468
  this.vendor = "ledger";
452
469
  this.emitter = new TypedEventEmitter();
453
470
  this._discoveredDevices = /* @__PURE__ */ new Map();
@@ -469,7 +486,7 @@ var _LedgerAdapter = class _LedgerAdapter {
469
486
  *
470
487
  * - If a session already exists for the given connectId, reuse it.
471
488
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
472
- * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
489
+ * - Otherwise: search → 1 USB device: auto-connect, multiple USB devices: throw, 0: throw.
473
490
  */
474
491
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
475
492
  this._connectingPromise = null;
@@ -478,16 +495,45 @@ var _LedgerAdapter = class _LedgerAdapter {
478
495
  // ---------------------------------------------------------------------------
479
496
  this.deviceConnectHandler = (data) => {
480
497
  const deviceInfo = this.connectorDeviceToDeviceInfo(data.device);
498
+ const hadSession = this._sessions.get(deviceInfo.connectId);
481
499
  this._discoveredDevices.set(deviceInfo.connectId, deviceInfo);
482
500
  this._sessions.delete(deviceInfo.connectId);
501
+ debugLog(
502
+ "[SESS-DBG] deviceConnectHandler EVENT connectId=",
503
+ deviceInfo.connectId,
504
+ "deviceId=",
505
+ deviceInfo.deviceId,
506
+ "label=",
507
+ deviceInfo.label,
508
+ "serial=",
509
+ deviceInfo.serialNumber,
510
+ "staleSessionDeleted=",
511
+ hadSession ?? "(none)",
512
+ "sessionsAfter=",
513
+ this._snapshotSessions()
514
+ );
483
515
  this.emitter.emit(DEVICE.CONNECT, {
484
516
  type: DEVICE.CONNECT,
485
517
  payload: deviceInfo
486
518
  });
487
519
  };
488
520
  this.deviceDisconnectHandler = (data) => {
521
+ const lostSession = this._sessions.get(data.connectId);
522
+ const lostDeviceInfo = this._discoveredDevices.get(data.connectId);
489
523
  this._discoveredDevices.delete(data.connectId);
490
524
  this._sessions.delete(data.connectId);
525
+ debugLog(
526
+ "[SESS-DBG] deviceDisconnectHandler EVENT connectId=",
527
+ data.connectId,
528
+ "removedSessionId=",
529
+ lostSession ?? "(none)",
530
+ "removedDeviceId=",
531
+ lostDeviceInfo?.deviceId,
532
+ "removedSerial=",
533
+ lostDeviceInfo?.serialNumber,
534
+ "sessionsAfter=",
535
+ this._snapshotSessions()
536
+ );
491
537
  this.emitter.emit(DEVICE.DISCONNECT, {
492
538
  type: DEVICE.DISCONNECT,
493
539
  payload: { connectId: data.connectId }
@@ -500,7 +546,6 @@ var _LedgerAdapter = class _LedgerAdapter {
500
546
  this.emitter.emit("ui-event", event);
501
547
  };
502
548
  this.connector = connector;
503
- this._handleSelectDevice = options?.handleSelectDevice ?? false;
504
549
  this._jobQueue = new DeviceJobQueue();
505
550
  this.registerEventListeners();
506
551
  }
@@ -549,12 +594,21 @@ var _LedgerAdapter = class _LedgerAdapter {
549
594
  // Device management
550
595
  // ---------------------------------------------------------------------------
551
596
  async searchDevices(options) {
597
+ debugLog(
598
+ "[SESS-DBG] searchDevices ENTER resetSession=",
599
+ options?.resetSession ?? false,
600
+ "sessionsBefore=",
601
+ this._snapshotSessions(),
602
+ "discoveredBefore=",
603
+ this._snapshotDiscovered()
604
+ );
552
605
  if (options?.resetSession) {
553
606
  this._doConnectAbortController?.abort();
554
607
  this._sessions.clear();
555
608
  this._connectingPromise = null;
556
609
  this._doConnectAbortController = null;
557
610
  this._btcHighIndexConfirmedThisSession = false;
611
+ debugLog("[SESS-DBG] searchDevices cleared _sessions (resetSession=true)");
558
612
  }
559
613
  await this._ensureDevicePermission();
560
614
  const devices = await this.connector.searchDevices();
@@ -568,32 +622,110 @@ var _LedgerAdapter = class _LedgerAdapter {
568
622
  await this._ensureDevicePermission();
569
623
  }
570
624
  debugLog(
571
- `[LedgerAdapter] searchDevices() return count=${this._discoveredDevices.size} ids=[${[
572
- ...this._discoveredDevices.keys()
573
- ].join(",")}]`
625
+ "[SESS-DBG] searchDevices RETURN count=",
626
+ this._discoveredDevices.size,
627
+ "devices=",
628
+ devices.map((d) => ({
629
+ connectId: d.connectId,
630
+ deviceId: d.deviceId,
631
+ name: d.name,
632
+ serial: d.serialNumber
633
+ })),
634
+ "sessionsAfter=",
635
+ this._snapshotSessions()
574
636
  );
575
637
  return Array.from(this._discoveredDevices.values());
576
638
  }
639
+ _snapshotSessions() {
640
+ return [...this._sessions.entries()].map(([connectId, sessionId]) => ({
641
+ connectId,
642
+ sessionId
643
+ }));
644
+ }
645
+ _snapshotDiscovered() {
646
+ return [...this._discoveredDevices.entries()].map(([connectId, info]) => ({
647
+ connectId,
648
+ deviceId: info.deviceId,
649
+ label: info.label,
650
+ serial: info.serialNumber
651
+ }));
652
+ }
653
+ // Enforce USB single-session invariant — see connectDevice.
654
+ async _evictAllSessions(reason) {
655
+ if (this._sessions.size === 0) return;
656
+ const stale = [...this._sessions.entries()];
657
+ debugLog(
658
+ "[SESS-DBG] evictAllSessions reason=",
659
+ reason,
660
+ "evicting=",
661
+ stale.map(([cid, sid]) => ({ connectId: cid, sessionId: sid }))
662
+ );
663
+ this._sessions.clear();
664
+ for (const [, sid] of stale) {
665
+ try {
666
+ await this.connector.disconnect(sid);
667
+ } catch (err) {
668
+ debugLog(
669
+ "[SESS-DBG] evictAllSessions disconnect failed sessionId=",
670
+ sid,
671
+ "err=",
672
+ err?.message
673
+ );
674
+ }
675
+ }
676
+ }
577
677
  static _createDeviceBusyError(method) {
578
678
  return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
579
679
  code: HardwareErrorCode2.DeviceBusy
580
680
  });
581
681
  }
582
682
  async connectDevice(connectId) {
683
+ debugLog(
684
+ "[SESS-DBG] connectDevice ENTER connectId=",
685
+ connectId || "(empty)",
686
+ "sessionsBefore=",
687
+ this._snapshotSessions(),
688
+ "discoveredBefore=",
689
+ this._snapshotDiscovered()
690
+ );
583
691
  try {
584
692
  if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
585
693
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
586
694
  code: HardwareErrorCode2.DeviceNotFound
587
695
  });
588
696
  }
697
+ if (!isLedgerBleConnectionType(this.connector.connectionType)) {
698
+ await this._evictAllSessions("connectDevice");
699
+ }
589
700
  await this._ensureDevicePermission(connectId);
590
701
  const session = await this.connector.connect(connectId);
702
+ const prev = this._sessions.get(connectId);
591
703
  this._sessions.set(connectId, session.sessionId);
704
+ debugLog(
705
+ "[SESS-DBG] connectDevice SET _sessions connectId=",
706
+ connectId,
707
+ "newSessionId=",
708
+ session.sessionId,
709
+ "replacedPrevSessionId=",
710
+ prev ?? "(none)",
711
+ "sessionInfoDeviceId=",
712
+ session.deviceInfo?.deviceId,
713
+ "sessionInfoSerial=",
714
+ session.deviceInfo?.serialNumber,
715
+ "sessionsAfter=",
716
+ this._snapshotSessions()
717
+ );
592
718
  if (session.deviceInfo) {
593
719
  this._discoveredDevices.set(connectId, session.deviceInfo);
594
720
  }
595
721
  return success(connectId);
596
722
  } catch (err) {
723
+ debugLog(
724
+ "[SESS-DBG] connectDevice ERROR connectId=",
725
+ connectId,
726
+ "err=",
727
+ err?.message
728
+ );
597
729
  return this.errorToFailure(err);
598
730
  }
599
731
  }
@@ -699,6 +831,62 @@ var _LedgerAdapter = class _LedgerAdapter {
699
831
  tronSignMessage(connectId, deviceId, params) {
700
832
  return this.callChain(connectId, deviceId, "tron", "tronSignMessage", params);
701
833
  }
834
+ // ---------------------------------------------------------------------------
835
+ // App management — OS-level Ledger app install / list. Bypass fingerprint
836
+ // (no chain identity to verify) and chain handler dispatch. Progress for
837
+ // installApp is forwarded to the adapter emitter as 'app-install-progress'
838
+ // events so consumers can render progress bars without subscribing to a
839
+ // separate channel.
840
+ // ---------------------------------------------------------------------------
841
+ async installApp(connectId, appName) {
842
+ try {
843
+ const params = {
844
+ appName,
845
+ onProgress: (progress) => {
846
+ this.emitter.emit("app-install-progress", {
847
+ type: "app-install-progress",
848
+ payload: { connectId, appName, ...progress }
849
+ });
850
+ }
851
+ };
852
+ await this.connectorCall(connectId, "installApp", params);
853
+ return success(void 0);
854
+ } catch (err) {
855
+ return this.errorToFailure(err);
856
+ }
857
+ }
858
+ async listInstalledApps(connectId) {
859
+ try {
860
+ const result = await this.connectorCall(connectId, "listInstalledApps", {});
861
+ return success(result);
862
+ } catch (err) {
863
+ return this.errorToFailure(err);
864
+ }
865
+ }
866
+ async listAvailableApps(connectId) {
867
+ try {
868
+ const result = await this.connectorCall(connectId, "listAvailableApps", {});
869
+ return success(result);
870
+ } catch (err) {
871
+ return this.errorToFailure(err);
872
+ }
873
+ }
874
+ async getLedgerFirmwareVersion(connectId) {
875
+ try {
876
+ const result = await this.connectorCall(connectId, "getFirmwareVersion", {});
877
+ return success(result);
878
+ } catch (err) {
879
+ return this.errorToFailure(err);
880
+ }
881
+ }
882
+ async getLedgerDeviceInfo(connectId) {
883
+ try {
884
+ const result = await this.connectorCall(connectId, "getDeviceInfo", {});
885
+ return success(result);
886
+ } catch (err) {
887
+ return this.errorToFailure(err);
888
+ }
889
+ }
702
890
  on(event, listener) {
703
891
  this.emitter.on(event, listener);
704
892
  }
@@ -912,10 +1100,50 @@ var _LedgerAdapter = class _LedgerAdapter {
912
1100
  code: HardwareErrorCode2.DeviceNotFound
913
1101
  });
914
1102
  }
915
- if (connectId && this._sessions.has(connectId)) return connectId;
916
- if (this._sessions.size > 0) {
917
- return this._sessions.keys().next().value;
1103
+ debugLog(
1104
+ "[SESS-DBG] ensureConnected ENTER inputConnectId=",
1105
+ connectId || "(empty)",
1106
+ "sessions=",
1107
+ this._snapshotSessions(),
1108
+ "discovered=",
1109
+ this._snapshotDiscovered()
1110
+ );
1111
+ if (connectId && this._sessions.has(connectId)) {
1112
+ debugLog(
1113
+ "[SESS-DBG] ensureConnected RESOLVED path=fast-cache connectId=",
1114
+ connectId,
1115
+ "sessionId=",
1116
+ this._sessions.get(connectId)
1117
+ );
1118
+ return connectId;
1119
+ }
1120
+ if (!connectId && this._sessions.size > 0) {
1121
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
1122
+ debugLog(
1123
+ "[SESS-DBG] ensureConnected ABORT multiple USB sessions present sessions=",
1124
+ this._snapshotSessions()
1125
+ );
1126
+ throw Object.assign(
1127
+ new Error(
1128
+ "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
1129
+ ),
1130
+ { code: HardwareErrorCode2.DeviceOneDeviceOnly }
1131
+ );
1132
+ }
1133
+ const ambient = this._sessions.keys().next().value;
1134
+ debugLog(
1135
+ "[SESS-DBG] ensureConnected RESOLVED path=ambient-fallback chosenConnectId=",
1136
+ ambient,
1137
+ "sessionId=",
1138
+ this._sessions.get(ambient)
1139
+ );
1140
+ return ambient;
918
1141
  }
1142
+ debugLog(
1143
+ "[SESS-DBG] ensureConnected path=doConnect (no fast match)",
1144
+ "connectId=",
1145
+ connectId || "(empty)"
1146
+ );
919
1147
  if (!this._connectingPromise) {
920
1148
  this._doConnectAbortController = new AbortController();
921
1149
  const innerSignal = this._doConnectAbortController.signal;
@@ -997,28 +1225,44 @@ var _LedgerAdapter = class _LedgerAdapter {
997
1225
  throw new Error("_doConnect aborted");
998
1226
  }
999
1227
  async _connectFirstOrSelect(devices, targetConnectId) {
1228
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length > 1) {
1229
+ debugLog(
1230
+ `[DMK] Multiple Ledger USB devices found (${devices.length}); refusing to auto-select by ephemeral connectId.`
1231
+ );
1232
+ throw createMultipleUsbLedgerDevicesError();
1233
+ }
1000
1234
  if (targetConnectId) {
1001
1235
  const target = devices.find(
1002
1236
  (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
1003
1237
  );
1004
- if (!target) {
1005
- const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1006
- code: HardwareErrorCode2.DeviceNotFound
1007
- });
1008
- if (isLedgerBleConnectionType(this.connector.connectionType)) {
1009
- err._tag = ERROR_TAG.DeviceNotAdvertising;
1010
- }
1011
- throw err;
1238
+ if (target) {
1239
+ return this._connectDeviceOrThrow(target.connectId);
1240
+ }
1241
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) {
1242
+ debugLog(
1243
+ `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)`
1244
+ );
1245
+ return this._connectDeviceOrThrow(devices[0].connectId);
1012
1246
  }
1013
- return this._connectDeviceOrThrow(target.connectId);
1247
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1248
+ code: HardwareErrorCode2.DeviceNotFound
1249
+ });
1250
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1251
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
1252
+ }
1253
+ throw err;
1014
1254
  }
1015
1255
  if (isLedgerBleConnectionType(this.connector.connectionType)) {
1016
1256
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1017
1257
  code: HardwareErrorCode2.DeviceNotFound
1018
1258
  });
1019
1259
  }
1020
- const chosenConnectId = devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices);
1021
- return this._connectDeviceOrThrow(chosenConnectId);
1260
+ if (devices.length !== 1) {
1261
+ throw Object.assign(new Error("Ledger device not found."), {
1262
+ code: HardwareErrorCode2.DeviceNotFound
1263
+ });
1264
+ }
1265
+ return this._connectDeviceOrThrow(devices[0].connectId);
1022
1266
  }
1023
1267
  async _connectDeviceOrThrow(chosenConnectId) {
1024
1268
  const result = await this.connectDevice(chosenConnectId);
@@ -1034,46 +1278,6 @@ var _LedgerAdapter = class _LedgerAdapter {
1034
1278
  }
1035
1279
  return chosenConnectId;
1036
1280
  }
1037
- async _chooseDeviceFromList(devices) {
1038
- if (!this._handleSelectDevice) {
1039
- debugLog(
1040
- `[DMK] Multiple Ledger devices found (${devices.length}); handleSelectDevice=false, picking first (${devices[0].connectId}).`
1041
- );
1042
- return devices[0].connectId;
1043
- }
1044
- let response;
1045
- try {
1046
- const waitPromise = this._uiRegistry.wait(
1047
- UI_REQUEST.REQUEST_SELECT_DEVICE
1048
- );
1049
- this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
1050
- type: UI_REQUEST.REQUEST_SELECT_DEVICE,
1051
- payload: { devices }
1052
- });
1053
- response = await waitPromise;
1054
- } catch (err) {
1055
- if (err?._tag === UI_REQUEST_PREEMPTED_TAG) {
1056
- this.emitter.emit(UI_REQUEST.CLOSE_UI_WINDOW, {
1057
- type: UI_REQUEST.CLOSE_UI_WINDOW,
1058
- payload: {}
1059
- });
1060
- throw Object.assign(new Error("Device selection superseded"), {
1061
- _tag: ERROR_TAG.UserAborted,
1062
- code: HardwareErrorCode2.UserAborted,
1063
- cause: err
1064
- });
1065
- }
1066
- throw err;
1067
- }
1068
- const chosen = devices.find((d) => d.connectId === response?.sdkConnectId);
1069
- if (!chosen) {
1070
- throw Object.assign(
1071
- new Error(`Selected sdkConnectId '${response?.sdkConnectId}' not in discovered list`),
1072
- { _tag: ERROR_TAG.DeviceNotRecognized }
1073
- );
1074
- }
1075
- return chosen.connectId;
1076
- }
1077
1281
  /**
1078
1282
  * Call the connector with automatic session resolution and disconnect retry.
1079
1283
  *
@@ -1083,7 +1287,18 @@ var _LedgerAdapter = class _LedgerAdapter {
1083
1287
  * 4. On disconnect error: clears stale session, re-connects, retries once
1084
1288
  */
1085
1289
  async connectorCall(connectId, method, params, fingerprint, permissionDeviceId) {
1086
- debugLog("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
1290
+ debugLog(
1291
+ "[SESS-DBG] connectorCall ENTER method=",
1292
+ method,
1293
+ "callerConnectId=",
1294
+ connectId || "(empty)",
1295
+ "callerDeviceId=",
1296
+ fingerprint?.deviceId ?? permissionDeviceId ?? "(none)",
1297
+ "sessions=",
1298
+ this._snapshotSessions(),
1299
+ "discovered=",
1300
+ this._snapshotDiscovered()
1301
+ );
1087
1302
  const queueKey = connectId || "__ledger_default__";
1088
1303
  return this._jobQueue.enqueue(
1089
1304
  queueKey,
@@ -1238,8 +1453,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1238
1453
  } catch {
1239
1454
  }
1240
1455
  }
1241
- const retryConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1242
- const reConnectId = await this.ensureConnected(retryConnectId, signal);
1456
+ const reConnectId = await this.ensureConnected(resolvedConnectId, signal);
1243
1457
  const reSessionId = this._sessions.get(reConnectId);
1244
1458
  if (!reSessionId) throw lastErr;
1245
1459
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
@@ -1307,8 +1521,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1307
1521
  */
1308
1522
  async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
1309
1523
  await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
1310
- const retryTargetConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1311
- const retryConnectId = await this.ensureConnected(retryTargetConnectId, signal);
1524
+ const retryConnectId = await this.ensureConnected(resolvedConnectId, signal);
1312
1525
  const retrySessionId = this._sessions.get(retryConnectId);
1313
1526
  if (!retrySessionId) throw originalErr;
1314
1527
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
@@ -1486,7 +1699,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1486
1699
  const tag = err && typeof err === "object" ? err._tag : void 0;
1487
1700
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
1488
1701
  const e = err;
1489
- const params = e.code === HardwareErrorCode2.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : void 0;
1702
+ const params = e.code === HardwareErrorCode2.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
1490
1703
  return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
1491
1704
  }
1492
1705
  const mapped = mapLedgerError(err);
@@ -1536,7 +1749,7 @@ _LedgerAdapter.MAX_DOCONNECT_CONFIRMS = 3;
1536
1749
  var LedgerAdapter = _LedgerAdapter;
1537
1750
 
1538
1751
  // src/connector/LedgerConnectorBase.ts
1539
- import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
1752
+ import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
1540
1753
 
1541
1754
  // src/device/LedgerDeviceManager.ts
1542
1755
  var LedgerDeviceManager = class {
@@ -1814,7 +2027,7 @@ import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
1814
2027
  // src/signer/deviceActionToPromise.ts
1815
2028
  import { DeviceActionStatus } from "@ledgerhq/device-management-kit";
1816
2029
  var IDLE_WATCHDOG_MS = 65e3;
1817
- function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller) {
2030
+ function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller, onIntermediate) {
1818
2031
  return new Promise((resolve, reject) => {
1819
2032
  let settled = false;
1820
2033
  let lastStep;
@@ -1896,14 +2109,22 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_
1896
2109
  onInteraction?.("interaction-complete");
1897
2110
  sub?.unsubscribe();
1898
2111
  rejectWithStepContext(state.error, lastStep, observedSteps, reject);
1899
- } else if (state.status === DeviceActionStatus.Pending && onInteraction) {
1900
- const interaction = state.intermediateValue?.requiredUserInteraction;
1901
- if (interaction && interaction !== "none") {
1902
- if (interaction === "unlock-device" && timer) {
1903
- clearTimeout(timer);
1904
- timer = null;
2112
+ } else if (state.status === DeviceActionStatus.Pending) {
2113
+ if (onIntermediate) {
2114
+ try {
2115
+ onIntermediate(state.intermediateValue);
2116
+ } catch {
2117
+ }
2118
+ }
2119
+ if (onInteraction) {
2120
+ const interaction = state.intermediateValue?.requiredUserInteraction;
2121
+ if (interaction && interaction !== "none") {
2122
+ if (interaction === "unlock-device" && timer) {
2123
+ clearTimeout(timer);
2124
+ timer = null;
2125
+ }
2126
+ onInteraction(String(interaction));
1905
2127
  }
1906
- onInteraction(String(interaction));
1907
2128
  }
1908
2129
  }
1909
2130
  },
@@ -2947,6 +3168,336 @@ async function _createTrx(ctx, sessionId) {
2947
3168
  return new Trx(new DmkTransport(dmk, sessionId));
2948
3169
  }
2949
3170
 
3171
+ // src/device-apps/DeviceApps.ts
3172
+ import { DeviceActionStatus as DeviceActionStatus2 } from "@ledgerhq/device-management-kit";
3173
+ import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
3174
+ import { Subject } from "rxjs";
3175
+ var INSTALL_TIMEOUT_MS = 5 * 6e4;
3176
+ var DeviceApps = class {
3177
+ constructor(_dmk, _sessionId, _ledgerKit) {
3178
+ this._dmk = _dmk;
3179
+ this._sessionId = _sessionId;
3180
+ this._ledgerKit = _ledgerKit;
3181
+ }
3182
+ async listInstalled(options) {
3183
+ const action = this._dmk.executeDeviceAction({
3184
+ sessionId: this._sessionId,
3185
+ deviceAction: new this._ledgerKit.ListAppsWithMetadataDeviceAction({
3186
+ input: { unlockTimeout: options?.unlockTimeout }
3187
+ })
3188
+ });
3189
+ const result = await deviceActionToPromise(
3190
+ action,
3191
+ this.onInteraction,
3192
+ void 0,
3193
+ this.onRegisterCanceller
3194
+ );
3195
+ return result.filter((a) => a !== null).map(applicationToMetadata);
3196
+ }
3197
+ // Catalog lookup via custom device action — DMK has no typed wrapper for this.
3198
+ async listAvailable() {
3199
+ const customAction = new ListAvailableAppsDeviceAction({
3200
+ GetOsVersionCommand: this._ledgerKit.GetOsVersionCommand,
3201
+ isSuccessCommandResult: this._ledgerKit.isSuccessCommandResult
3202
+ });
3203
+ const action = this._dmk.executeDeviceAction({
3204
+ sessionId: this._sessionId,
3205
+ deviceAction: customAction
3206
+ });
3207
+ const result = await deviceActionToPromise(
3208
+ action,
3209
+ this.onInteraction,
3210
+ void 0,
3211
+ this.onRegisterCanceller
3212
+ );
3213
+ return result.map(applicationToMetadata);
3214
+ }
3215
+ async getFirmwareVersion() {
3216
+ const v = await this._fetchOsVersion();
3217
+ return {
3218
+ seVersion: v.seVersion,
3219
+ mcuSephVersion: v.mcuSephVersion,
3220
+ mcuBootloaderVersion: v.mcuBootloaderVersion,
3221
+ hwVersion: v.hwVersion
3222
+ };
3223
+ }
3224
+ async getDeviceInfo() {
3225
+ const v = await this._fetchOsVersion();
3226
+ return {
3227
+ isBootloader: v.isBootloader,
3228
+ isOsu: v.isOsu,
3229
+ targetId: v.targetId,
3230
+ seTargetId: v.seTargetId,
3231
+ mcuTargetId: v.mcuTargetId,
3232
+ seVersion: v.seVersion,
3233
+ seFlagsHex: bytesToHex2(v.seFlags),
3234
+ mcuSephVersion: v.mcuSephVersion,
3235
+ mcuBootloaderVersion: v.mcuBootloaderVersion,
3236
+ hwVersion: v.hwVersion
3237
+ };
3238
+ }
3239
+ /**
3240
+ * Pre-flight space check. Mirrors DMK's PredictOutOfMemoryTask using the
3241
+ * data we already expose (listInstalled / listAvailable / getOsVersion).
3242
+ * Fails fast with HardwareErrorCode.DeviceOutOfMemory before issuing the
3243
+ * actual install — saves the user the time-to-fail of DMK's state machine.
3244
+ */
3245
+ async _assertEnoughSpace(appName) {
3246
+ let osVersion;
3247
+ let installed;
3248
+ let available;
3249
+ try {
3250
+ [osVersion, installed, available] = await Promise.all([
3251
+ this._fetchOsVersion(),
3252
+ this.listInstalled(),
3253
+ this.listAvailable()
3254
+ ]);
3255
+ } catch (err) {
3256
+ debugLog("[DeviceApps] precheck skipped:", err?.message);
3257
+ return;
3258
+ }
3259
+ if (installed.some((a) => a.versionName === appName)) return;
3260
+ const target = available.find((a) => a.versionName === appName);
3261
+ if (!target) return;
3262
+ const capacity = getDeviceCapacity(osVersion.targetId);
3263
+ if (!capacity) return;
3264
+ const blockSize = capacity.getBlockSize(osVersion.seVersion);
3265
+ const totalBlocks = Math.floor(capacity.memorySize / blockSize);
3266
+ const blocks = (b) => Math.ceil((b ?? 0) / blockSize);
3267
+ const usedBlocks = installed.reduce((sum, a) => sum + blocks(a.bytes), 0);
3268
+ const neededBlocks = blocks(target.bytes);
3269
+ debugLog(
3270
+ "[DeviceApps] space check",
3271
+ "targetId=",
3272
+ osVersion.targetId.toString(16),
3273
+ "memorySize=",
3274
+ capacity.memorySize,
3275
+ "blockSize=",
3276
+ blockSize,
3277
+ "usedBlocks=",
3278
+ usedBlocks,
3279
+ "neededBlocks=",
3280
+ neededBlocks,
3281
+ "totalBlocks=",
3282
+ totalBlocks
3283
+ );
3284
+ if (usedBlocks + neededBlocks > totalBlocks) {
3285
+ const usedBytes = usedBlocks * blockSize;
3286
+ const neededBytes = neededBlocks * blockSize;
3287
+ const freeBytes = Math.max(0, capacity.memorySize - usedBytes);
3288
+ throw Object.assign(
3289
+ new Error(
3290
+ `Not enough space to install "${appName}": needs ${neededBytes} bytes, ${freeBytes} bytes free of ${capacity.memorySize}.`
3291
+ ),
3292
+ {
3293
+ code: HardwareErrorCode7.DeviceOutOfMemory,
3294
+ _tag: "OutOfMemoryDAError",
3295
+ appName,
3296
+ params: {
3297
+ requiredBytes: neededBytes,
3298
+ availableBytes: freeBytes,
3299
+ totalBytes: capacity.memorySize
3300
+ }
3301
+ }
3302
+ );
3303
+ }
3304
+ }
3305
+ // Sole sendCommand wrapper — surfaces unlock-device interaction through
3306
+ // the same onInteraction pipeline as the device-action methods.
3307
+ async _fetchOsVersion() {
3308
+ const customAction = new GetOsVersionDeviceAction({
3309
+ GetOsVersionCommand: this._ledgerKit.GetOsVersionCommand,
3310
+ isSuccessCommandResult: this._ledgerKit.isSuccessCommandResult
3311
+ });
3312
+ const action = this._dmk.executeDeviceAction({
3313
+ sessionId: this._sessionId,
3314
+ deviceAction: customAction
3315
+ });
3316
+ return deviceActionToPromise(
3317
+ action,
3318
+ this.onInteraction,
3319
+ void 0,
3320
+ this.onRegisterCanceller
3321
+ );
3322
+ }
3323
+ async install(appName, onProgress, options) {
3324
+ if (!appName) throw new Error("DeviceApps.install: appName is required");
3325
+ debugLog("[DeviceApps] install:", appName);
3326
+ await this._assertEnoughSpace(appName);
3327
+ const action = this._dmk.executeDeviceAction({
3328
+ sessionId: this._sessionId,
3329
+ deviceAction: new this._ledgerKit.InstallAppDeviceAction({
3330
+ input: { appName, unlockTimeout: options?.unlockTimeout }
3331
+ })
3332
+ });
3333
+ await deviceActionToPromise(
3334
+ action,
3335
+ this.onInteraction,
3336
+ INSTALL_TIMEOUT_MS,
3337
+ this.onRegisterCanceller,
3338
+ onProgress ? (intermediateValue) => {
3339
+ const iv = intermediateValue;
3340
+ if (typeof iv?.progress === "number") {
3341
+ onProgress({
3342
+ progress: iv.progress,
3343
+ requiredUserInteraction: iv.requiredUserInteraction
3344
+ });
3345
+ }
3346
+ } : void 0
3347
+ );
3348
+ }
3349
+ };
3350
+ function bytesToHex2(bytes) {
3351
+ if (!bytes) return "";
3352
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
3353
+ }
3354
+ function applicationToMetadata(app) {
3355
+ return {
3356
+ versionName: app.versionName,
3357
+ versionId: app.versionId,
3358
+ version: app.version,
3359
+ versionDisplayName: app.versionDisplayName,
3360
+ description: app.description,
3361
+ icon: app.icon,
3362
+ bytes: app.bytes,
3363
+ currencyId: app.currencyId,
3364
+ isDevTools: app.isDevTools
3365
+ };
3366
+ }
3367
+ var DEVICE_CAPACITIES = [
3368
+ // Nano S — block size 4K on fw<2.0, 2K on >=2.0. Default 4K (conservative).
3369
+ {
3370
+ mask: 823132160,
3371
+ capacity: {
3372
+ memorySize: 320 * 1024,
3373
+ getBlockSize: (fw) => parseMajor(fw) >= 2 ? 2 * 1024 : 4 * 1024
3374
+ }
3375
+ },
3376
+ // Nano S Plus
3377
+ { mask: 856686592, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } },
3378
+ // Nano X
3379
+ { mask: 855638016, capacity: { memorySize: 2 * 1024 * 1024, getBlockSize: () => 4 * 1024 } },
3380
+ // Stax
3381
+ { mask: 857735168, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } },
3382
+ // Flex
3383
+ { mask: 858783744, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } },
3384
+ // Apex / Nano Gen5
3385
+ { mask: 859832320, capacity: { memorySize: 1533 * 1024, getBlockSize: () => 32 } }
3386
+ ];
3387
+ function getDeviceCapacity(targetId) {
3388
+ const masked = targetId & 4294901760;
3389
+ return DEVICE_CAPACITIES.find((d) => d.mask === masked)?.capacity;
3390
+ }
3391
+ function parseMajor(version) {
3392
+ const m = /^(\d+)/.exec(version);
3393
+ return m ? Number(m[1]) : 0;
3394
+ }
3395
+ var GetOsVersionDeviceAction = class {
3396
+ constructor(_deps) {
3397
+ this._deps = _deps;
3398
+ this.input = void 0;
3399
+ }
3400
+ _execute(internalApi) {
3401
+ const subject = new Subject();
3402
+ let cancelled = false;
3403
+ (async () => {
3404
+ try {
3405
+ subject.next({
3406
+ status: DeviceActionStatus2.Pending,
3407
+ intermediateValue: { requiredUserInteraction: "none" }
3408
+ });
3409
+ const result = await internalApi.sendCommand(new this._deps.GetOsVersionCommand());
3410
+ if (cancelled) return;
3411
+ if (!this._deps.isSuccessCommandResult(result)) {
3412
+ const errObj = result?.error;
3413
+ throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
3414
+ }
3415
+ subject.next({ status: DeviceActionStatus2.Completed, output: result.data });
3416
+ subject.complete();
3417
+ } catch (err) {
3418
+ if (cancelled) return;
3419
+ subject.next({ status: DeviceActionStatus2.Error, error: err });
3420
+ subject.complete();
3421
+ }
3422
+ })();
3423
+ return {
3424
+ observable: subject.asObservable(),
3425
+ cancel: () => {
3426
+ cancelled = true;
3427
+ subject.next({ status: DeviceActionStatus2.Stopped });
3428
+ subject.complete();
3429
+ }
3430
+ };
3431
+ }
3432
+ };
3433
+ var ListAvailableAppsDeviceAction = class {
3434
+ constructor(deps) {
3435
+ this.input = void 0;
3436
+ this._GetOsVersionCommand = deps.GetOsVersionCommand;
3437
+ this._isSuccessCommandResult = deps.isSuccessCommandResult;
3438
+ }
3439
+ _execute(internalApi) {
3440
+ const subject = new Subject();
3441
+ let cancelled = false;
3442
+ (async () => {
3443
+ try {
3444
+ subject.next({
3445
+ status: DeviceActionStatus2.Pending,
3446
+ intermediateValue: { requiredUserInteraction: "none" }
3447
+ });
3448
+ const osVersionResult = await internalApi.sendCommand(new this._GetOsVersionCommand());
3449
+ if (cancelled) return;
3450
+ if (!this._isSuccessCommandResult(osVersionResult)) {
3451
+ const errObj = osVersionResult?.error;
3452
+ throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
3453
+ }
3454
+ const managerApi = internalApi.getManagerApiService();
3455
+ const either = await managerApi.getAppList(osVersionResult.data).run();
3456
+ if (cancelled) return;
3457
+ if (either.isLeft()) {
3458
+ const httpErr = either.extract();
3459
+ throw new Error(httpErr?.message ?? "Manager API getAppList failed");
3460
+ }
3461
+ const apps = either.extract();
3462
+ subject.next({ status: DeviceActionStatus2.Completed, output: apps });
3463
+ subject.complete();
3464
+ } catch (err) {
3465
+ if (cancelled) return;
3466
+ subject.next({ status: DeviceActionStatus2.Error, error: err });
3467
+ subject.complete();
3468
+ }
3469
+ })();
3470
+ return {
3471
+ observable: subject.asObservable(),
3472
+ cancel: () => {
3473
+ cancelled = true;
3474
+ subject.next({ status: DeviceActionStatus2.Stopped });
3475
+ subject.complete();
3476
+ }
3477
+ };
3478
+ }
3479
+ };
3480
+
3481
+ // src/device-apps/DeviceAppsManager.ts
3482
+ var DeviceAppsManager = class {
3483
+ constructor(dmk, importLedgerKit) {
3484
+ this._dmk = dmk;
3485
+ this._importLedgerKit = importLedgerKit;
3486
+ }
3487
+ async getOrCreate(sessionId) {
3488
+ const ledgerKit = await this._importLedgerKit(
3489
+ "@ledgerhq/device-management-kit"
3490
+ );
3491
+ return new DeviceApps(this._dmk, sessionId, ledgerKit);
3492
+ }
3493
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this
3494
+ invalidate(_sessionId) {
3495
+ }
3496
+ // eslint-disable-next-line class-methods-use-this
3497
+ clearAll() {
3498
+ }
3499
+ };
3500
+
2950
3501
  // src/connector/LedgerConnectorBase.ts
2951
3502
  var METHOD_PREFIX_TO_APP_NAME = {
2952
3503
  evm: "Ethereum",
@@ -2955,7 +3506,7 @@ var METHOD_PREFIX_TO_APP_NAME = {
2955
3506
  tron: "Tron"
2956
3507
  };
2957
3508
  var HARDWARE_ERROR_CODE_VALUES = new Set(
2958
- Object.values(HardwareErrorCode7).filter((value) => typeof value === "number")
3509
+ Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
2959
3510
  );
2960
3511
  var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
2961
3512
  async function defaultLedgerKitImporter(pkg) {
@@ -2974,10 +3525,25 @@ async function defaultLedgerKitImporter(pkg) {
2974
3525
  throw new Error(`Unknown Ledger kit package: ${pkg}`);
2975
3526
  }
2976
3527
  }
3528
+ async function _getDeviceApps(ctx, sessionId) {
3529
+ const manager = await ctx.getDeviceAppsManager();
3530
+ const apps = await manager.getOrCreate(sessionId);
3531
+ apps.onInteraction = (interaction) => {
3532
+ ctx.emit("ui-event", {
3533
+ type: collapseSignerInteraction(interaction),
3534
+ payload: { sessionId }
3535
+ });
3536
+ };
3537
+ apps.onRegisterCanceller = (cancel) => {
3538
+ ctx.registerCanceller(sessionId, cancel);
3539
+ };
3540
+ return apps;
3541
+ }
2977
3542
  var LedgerConnectorBase = class {
2978
3543
  constructor(createTransport, options) {
2979
3544
  this._deviceManager = null;
2980
3545
  this._signerManager = null;
3546
+ this._deviceAppsManager = null;
2981
3547
  this._dmk = null;
2982
3548
  this._eventHandlers = /* @__PURE__ */ new Map();
2983
3549
  // ---------------------------------------------------------------------------
@@ -3016,6 +3582,7 @@ var LedgerConnectorBase = class {
3016
3582
  getOrCreateDmk: () => this._getOrCreateDmk(),
3017
3583
  getDeviceManager: () => this._getDeviceManager(),
3018
3584
  getSignerManager: () => this._getSignerManager(),
3585
+ getDeviceAppsManager: () => this._getDeviceAppsManager(),
3019
3586
  clearAllSigners: () => this._signerManager?.clearAll(),
3020
3587
  replaceSession: (oldSid, newSid) => this._replaceSession(oldSid, newSid),
3021
3588
  registerCanceller: (sid, cancel) => this._cancellers.set(sid, cancel),
@@ -3050,12 +3617,22 @@ var LedgerConnectorBase = class {
3050
3617
  }
3051
3618
  return descriptors;
3052
3619
  }
3620
+ _assertSingleUsbDescriptor(descriptors) {
3621
+ if (isLedgerBleConnectionType(this.connectionType) || descriptors.length <= 1) {
3622
+ return;
3623
+ }
3624
+ debugLog(
3625
+ `[DMK] Multiple Ledger USB devices found (${descriptors.length}); refusing to choose by ephemeral path. paths=[${descriptors.map((d) => d.path).join(",")}]`
3626
+ );
3627
+ throw createMultipleUsbLedgerDevicesError();
3628
+ }
3053
3629
  // ---------------------------------------------------------------------------
3054
3630
  // IConnector -- Device discovery
3055
3631
  // ---------------------------------------------------------------------------
3056
3632
  async searchDevices() {
3057
3633
  const dm = await this._getDeviceManager();
3058
3634
  const descriptors = await this._discoverDescriptors(dm);
3635
+ this._assertSingleUsbDescriptor(descriptors);
3059
3636
  const resolvedDescriptors = descriptors.map((d) => ({
3060
3637
  descriptor: d,
3061
3638
  connectId: this._resolveConnectId(d)
@@ -3081,6 +3658,10 @@ var LedgerConnectorBase = class {
3081
3658
  async connect(deviceId) {
3082
3659
  const callerSuppliedConnectId = Boolean(deviceId);
3083
3660
  let targetPath = deviceId;
3661
+ if (callerSuppliedConnectId && !isLedgerBleConnectionType(this.connectionType)) {
3662
+ const dm = await this._getDeviceManager();
3663
+ this._assertSingleUsbDescriptor(await this._discoverDescriptors(dm));
3664
+ }
3084
3665
  if (!targetPath) {
3085
3666
  const discovered = await this.searchDevices();
3086
3667
  if (discovered.length === 0) {
@@ -3104,7 +3685,7 @@ var LedgerConnectorBase = class {
3104
3685
  `Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
3105
3686
  );
3106
3687
  err._tag = ERROR_TAG.BlePairingTimeout;
3107
- err.code = HardwareErrorCode7.BlePairingTimeout;
3688
+ err.code = HardwareErrorCode8.BlePairingTimeout;
3108
3689
  reject(err);
3109
3690
  }, HANG_CEILING_MS);
3110
3691
  });
@@ -3137,7 +3718,7 @@ var LedgerConnectorBase = class {
3137
3718
  "Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
3138
3719
  );
3139
3720
  err._tag = ERROR_TAG.DeviceNotAdvertising;
3140
- err.code = HardwareErrorCode7.DeviceNotFound;
3721
+ err.code = HardwareErrorCode8.DeviceNotFound;
3141
3722
  throw err;
3142
3723
  };
3143
3724
  const doConnect = async (path) => {
@@ -3157,7 +3738,19 @@ var LedgerConnectorBase = class {
3157
3738
  }
3158
3739
  throw err;
3159
3740
  }
3160
- this._watchSessionState(sessionId, externalConnectId);
3741
+ try {
3742
+ this._watchSessionState(sessionId, externalConnectId);
3743
+ } catch (subErr) {
3744
+ debugLog(
3745
+ "[DMK] state subscription failed during connect; disconnecting session:",
3746
+ subErr
3747
+ );
3748
+ try {
3749
+ await dm.disconnect(sessionId);
3750
+ } catch {
3751
+ }
3752
+ throw subErr;
3753
+ }
3161
3754
  const info = dm.getDiscoveredDeviceInfo(path);
3162
3755
  const session = {
3163
3756
  sessionId,
@@ -3174,6 +3767,18 @@ var LedgerConnectorBase = class {
3174
3767
  }
3175
3768
  };
3176
3769
  const name = dm.getDeviceName(path) ?? "Ledger";
3770
+ debugLog(
3771
+ "[SESS-DBG] connector.connect SUCCESS dmPath=",
3772
+ path,
3773
+ "externalConnectId=",
3774
+ externalConnectId,
3775
+ "newSessionId=",
3776
+ sessionId,
3777
+ "name=",
3778
+ name,
3779
+ "model=",
3780
+ info?.model
3781
+ );
3177
3782
  this._emit("device-connect", {
3178
3783
  device: { connectId: externalConnectId, deviceId: path, name }
3179
3784
  });
@@ -3192,7 +3797,7 @@ var LedgerConnectorBase = class {
3192
3797
  "Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
3193
3798
  );
3194
3799
  wrapped._tag = ERROR_TAG.BleGattBondingFailed;
3195
- wrapped.code = HardwareErrorCode7.BlePairingTimeout;
3800
+ wrapped.code = HardwareErrorCode8.BlePairingTimeout;
3196
3801
  wrapped.originalError = err;
3197
3802
  throw wrapped;
3198
3803
  }
@@ -3236,24 +3841,48 @@ var LedgerConnectorBase = class {
3236
3841
  } catch {
3237
3842
  }
3238
3843
  }
3239
- try {
3240
- const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
3241
- next: (state) => {
3242
- if (state?.deviceStatus === "NOT CONNECTED") {
3243
- this._handleAutonomousDisconnect(sessionId, externalConnectId);
3244
- }
3245
- },
3246
- error: () => {
3247
- this._handleAutonomousDisconnect(sessionId, externalConnectId);
3248
- },
3249
- complete: () => {
3844
+ debugLog(
3845
+ "[SESS-DBG] _watchSessionState SUBSCRIBE sessionId=",
3846
+ sessionId,
3847
+ "connectId=",
3848
+ externalConnectId
3849
+ );
3850
+ const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
3851
+ next: (state) => {
3852
+ debugLog(
3853
+ "[SESS-DBG] _watchSessionState NEXT sessionId=",
3854
+ sessionId,
3855
+ "connectId=",
3856
+ externalConnectId,
3857
+ "deviceStatus=",
3858
+ state?.deviceStatus
3859
+ );
3860
+ if (state?.deviceStatus === "NOT CONNECTED") {
3250
3861
  this._handleAutonomousDisconnect(sessionId, externalConnectId);
3251
3862
  }
3252
- });
3253
- this._sessionStateSubs.set(sessionId, sub);
3254
- } catch (err) {
3255
- debugLog("[DMK] _watchSessionState subscribe failed:", err);
3256
- }
3863
+ },
3864
+ error: (err) => {
3865
+ debugLog(
3866
+ "[SESS-DBG] _watchSessionState ERROR sessionId=",
3867
+ sessionId,
3868
+ "connectId=",
3869
+ externalConnectId,
3870
+ "err=",
3871
+ err?.message
3872
+ );
3873
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3874
+ },
3875
+ complete: () => {
3876
+ debugLog(
3877
+ "[SESS-DBG] _watchSessionState COMPLETE sessionId=",
3878
+ sessionId,
3879
+ "connectId=",
3880
+ externalConnectId
3881
+ );
3882
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3883
+ }
3884
+ });
3885
+ this._sessionStateSubs.set(sessionId, sub);
3257
3886
  }
3258
3887
  _unwatchSessionState(sessionId) {
3259
3888
  const sub = this._sessionStateSubs.get(sessionId);
@@ -3265,7 +3894,21 @@ var LedgerConnectorBase = class {
3265
3894
  this._sessionStateSubs.delete(sessionId);
3266
3895
  }
3267
3896
  _handleAutonomousDisconnect(sessionId, externalConnectId) {
3268
- if (!this._sessionStateSubs.has(sessionId)) return;
3897
+ if (!this._sessionStateSubs.has(sessionId)) {
3898
+ debugLog(
3899
+ "[SESS-DBG] _handleAutonomousDisconnect SKIP (already handled) sessionId=",
3900
+ sessionId,
3901
+ "connectId=",
3902
+ externalConnectId
3903
+ );
3904
+ return;
3905
+ }
3906
+ debugLog(
3907
+ "[SESS-DBG] _handleAutonomousDisconnect FIRE sessionId=",
3908
+ sessionId,
3909
+ "connectId=",
3910
+ externalConnectId
3911
+ );
3269
3912
  debugLog(
3270
3913
  "[DMK] autonomous disconnect detected \u2014 sessionId:",
3271
3914
  sessionId,
@@ -3275,7 +3918,7 @@ var LedgerConnectorBase = class {
3275
3918
  this._unwatchSessionState(sessionId);
3276
3919
  this._signerManager?.invalidate(sessionId);
3277
3920
  this._cancellers.get(sessionId)?.({
3278
- code: HardwareErrorCode7.DeviceDisconnected,
3921
+ code: HardwareErrorCode8.DeviceDisconnected,
3279
3922
  tag: "DeviceDisconnected",
3280
3923
  message: "Device disconnected"
3281
3924
  });
@@ -3292,14 +3935,14 @@ var LedgerConnectorBase = class {
3292
3935
  } catch (err) {
3293
3936
  if (isAppStuckByApdu(err)) {
3294
3937
  throw Object.assign(new Error("Ledger app is unresponsive"), {
3295
- code: HardwareErrorCode7.DeviceAppStuck,
3938
+ code: HardwareErrorCode8.DeviceAppStuck,
3296
3939
  _tag: ERROR_TAG.DeviceAppStuck,
3297
3940
  originalError: err
3298
3941
  });
3299
3942
  }
3300
3943
  if (isTransportStuck(err)) {
3301
3944
  throw Object.assign(new Error("Device communication interrupted, please retry"), {
3302
- code: HardwareErrorCode7.TransportError,
3945
+ code: HardwareErrorCode8.TransportError,
3303
3946
  _tag: ERROR_TAG.DeviceTransportStuck,
3304
3947
  originalError: err
3305
3948
  });
@@ -3356,6 +3999,49 @@ var LedgerConnectorBase = class {
3356
3999
  };
3357
4000
  return tronSignMessage(ctx, sessionId, internalParams);
3358
4001
  }
4002
+ // OS-level device management — symmetric to chain handlers.
4003
+ // _getDeviceApps wires onInteraction/onRegisterCanceller like _getEthSigner.
4004
+ case "installApp": {
4005
+ const p = params;
4006
+ const apps = await _getDeviceApps(ctx, sessionId);
4007
+ try {
4008
+ return await apps.install(p.appName, p.onProgress);
4009
+ } finally {
4010
+ ctx.clearCanceller(sessionId);
4011
+ }
4012
+ }
4013
+ case "listInstalledApps": {
4014
+ const apps = await _getDeviceApps(ctx, sessionId);
4015
+ try {
4016
+ return await apps.listInstalled(params);
4017
+ } finally {
4018
+ ctx.clearCanceller(sessionId);
4019
+ }
4020
+ }
4021
+ case "listAvailableApps": {
4022
+ const apps = await _getDeviceApps(ctx, sessionId);
4023
+ try {
4024
+ return await apps.listAvailable();
4025
+ } finally {
4026
+ ctx.clearCanceller(sessionId);
4027
+ }
4028
+ }
4029
+ case "getFirmwareVersion": {
4030
+ const apps = await _getDeviceApps(ctx, sessionId);
4031
+ try {
4032
+ return await apps.getFirmwareVersion();
4033
+ } finally {
4034
+ ctx.clearCanceller(sessionId);
4035
+ }
4036
+ }
4037
+ case "getDeviceInfo": {
4038
+ const apps = await _getDeviceApps(ctx, sessionId);
4039
+ try {
4040
+ return await apps.getDeviceInfo();
4041
+ } finally {
4042
+ ctx.clearCanceller(sessionId);
4043
+ }
4044
+ }
3359
4045
  default:
3360
4046
  throw new Error(`LedgerConnector: unknown method "${method}"`);
3361
4047
  }
@@ -3428,6 +4114,7 @@ var LedgerConnectorBase = class {
3428
4114
  const mod = await importKit("@ledgerhq/device-signer-kit-ethereum");
3429
4115
  return new mod.SignerEthBuilder(args);
3430
4116
  });
4117
+ this._deviceAppsManager = new DeviceAppsManager(dmk, importKit);
3431
4118
  }
3432
4119
  async _getDeviceManager() {
3433
4120
  if (this._deviceManager) return this._deviceManager;
@@ -3442,6 +4129,13 @@ var LedgerConnectorBase = class {
3442
4129
  }
3443
4130
  return this._signerManager;
3444
4131
  }
4132
+ async _getDeviceAppsManager() {
4133
+ if (!this._deviceAppsManager) {
4134
+ const dmk = await this._getOrCreateDmk();
4135
+ this._initManagers(dmk);
4136
+ }
4137
+ return this._deviceAppsManager;
4138
+ }
3445
4139
  _invalidateSession(sessionId) {
3446
4140
  this._signerManager?.invalidate(sessionId);
3447
4141
  }
@@ -3477,6 +4171,8 @@ var LedgerConnectorBase = class {
3477
4171
  debugLog("[DMK] _resetSignersAndSessions called");
3478
4172
  this._signerManager?.clearAll();
3479
4173
  this._signerManager = null;
4174
+ this._deviceAppsManager?.clearAll();
4175
+ this._deviceAppsManager = null;
3480
4176
  this._deviceManager?.disposeKeepingDmk();
3481
4177
  this._deviceManager = null;
3482
4178
  }
@@ -3497,9 +4193,11 @@ var LedgerConnectorBase = class {
3497
4193
  }
3498
4194
  this._sessionStateSubs.clear();
3499
4195
  this._signerManager?.clearAll();
4196
+ this._deviceAppsManager?.clearAll();
3500
4197
  this._deviceManager?.dispose();
3501
4198
  this._deviceManager = null;
3502
4199
  this._signerManager = null;
4200
+ this._deviceAppsManager = null;
3503
4201
  this._dmk = null;
3504
4202
  }
3505
4203
  // ---------------------------------------------------------------------------