@onekeyfe/hwk-ledger-adapter 1.2.2-alpha.9 → 1.2.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +65 -29
- package/dist/index.d.ts +65 -29
- package/dist/index.js +1551 -778
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1570 -779
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -9
package/dist/index.mjs
CHANGED
|
@@ -6,30 +6,41 @@ import {
|
|
|
6
6
|
DeviceJobQueue,
|
|
7
7
|
EConnectorInteraction,
|
|
8
8
|
HardwareErrorCode as HardwareErrorCode3,
|
|
9
|
+
OperationRegistry,
|
|
10
|
+
SDK,
|
|
9
11
|
TypedEventEmitter,
|
|
10
12
|
UI_REQUEST,
|
|
11
13
|
UI_REQUEST_PREEMPTED_TAG,
|
|
12
14
|
UiRequestRegistry,
|
|
15
|
+
canReplayHardwareMethodAfterTransportFailure,
|
|
13
16
|
createHwkError,
|
|
14
17
|
deriveDeviceFingerprint,
|
|
15
18
|
failure as failure2,
|
|
19
|
+
isHardwareOperationId,
|
|
20
|
+
isHwkRecoveryHint,
|
|
21
|
+
operationMayHaveCompletedParams,
|
|
16
22
|
rehydrateConnectorError,
|
|
23
|
+
requestBleDeviceSelection,
|
|
24
|
+
requestSaveDeviceBinding,
|
|
25
|
+
resolveSearchTargetReusePolicy,
|
|
17
26
|
success
|
|
18
27
|
} from "@onekeyfe/hwk-adapter-core";
|
|
19
28
|
|
|
20
29
|
// src/errors.ts
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function ledgerFailure(code, error, appName, tag, params) {
|
|
30
|
+
import {
|
|
31
|
+
HardwareErrorCode,
|
|
32
|
+
defaultOriginForCode,
|
|
33
|
+
defaultRecoveryForCode,
|
|
34
|
+
enrichErrorMessage
|
|
35
|
+
} from "@onekeyfe/hwk-adapter-core";
|
|
36
|
+
function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
|
|
29
37
|
const payload = { error, code };
|
|
30
38
|
if (appName !== void 0) payload.appName = appName;
|
|
31
39
|
if (tag !== void 0) payload._tag = tag;
|
|
32
40
|
if (params !== void 0) payload.params = params;
|
|
41
|
+
const resolvedOrigin = origin ?? defaultOriginForCode(code);
|
|
42
|
+
if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
|
|
43
|
+
payload.recovery = recovery ?? defaultRecoveryForCode(code);
|
|
33
44
|
return { success: false, payload };
|
|
34
45
|
}
|
|
35
46
|
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
@@ -213,7 +224,6 @@ function isBlePairingFailureError(err) {
|
|
|
213
224
|
return false;
|
|
214
225
|
}
|
|
215
226
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
216
|
-
ERROR_TAG.DeviceLocked,
|
|
217
227
|
ERROR_TAG.DeviceNotAdvertising,
|
|
218
228
|
ERROR_TAG.BlePairingTimeout,
|
|
219
229
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -448,11 +458,21 @@ function mapLedgerError(err, opts) {
|
|
|
448
458
|
}
|
|
449
459
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
450
460
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
451
|
-
return {
|
|
461
|
+
return {
|
|
462
|
+
code,
|
|
463
|
+
message: enrichErrorMessage(code, originalMessage),
|
|
464
|
+
origin: defaultOriginForCode(code),
|
|
465
|
+
appName
|
|
466
|
+
};
|
|
452
467
|
}
|
|
453
468
|
|
|
454
469
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
455
|
-
import {
|
|
470
|
+
import {
|
|
471
|
+
HardwareErrorCode as HardwareErrorCode2,
|
|
472
|
+
failure,
|
|
473
|
+
resolveHardwareOperationTarget,
|
|
474
|
+
runAllNetworkGetAddress
|
|
475
|
+
} from "@onekeyfe/hwk-adapter-core";
|
|
456
476
|
|
|
457
477
|
// src/utils/sdkEventBus.ts
|
|
458
478
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -509,57 +529,81 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
509
529
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
510
530
|
function createAllNetworkGetAddress({
|
|
511
531
|
callChain,
|
|
512
|
-
getChainFingerprint
|
|
532
|
+
getChainFingerprint,
|
|
533
|
+
retainOperation,
|
|
534
|
+
errorToFailure
|
|
513
535
|
}) {
|
|
514
536
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
515
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
537
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
538
|
+
method: "allNetworkGetAddress",
|
|
539
|
+
connectId,
|
|
540
|
+
itemCount: params.bundle.length
|
|
541
|
+
});
|
|
542
|
+
const target = resolveHardwareOperationTarget(connectId, params.operationId, "ledger");
|
|
543
|
+
if (!target.success) return target;
|
|
544
|
+
const effectiveTargetId = target.payload.targetId ?? "";
|
|
545
|
+
let releaseOperationRetention;
|
|
546
|
+
try {
|
|
547
|
+
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
return errorToFailure(error);
|
|
550
|
+
}
|
|
516
551
|
const installContext = {};
|
|
517
552
|
const commonParams = {
|
|
518
|
-
autoInstallApp: params.autoInstallApp
|
|
553
|
+
autoInstallApp: params.autoInstallApp,
|
|
554
|
+
operationId: target.payload.operationId,
|
|
555
|
+
knownConnections: params.knownConnections,
|
|
556
|
+
extra: params.extra,
|
|
557
|
+
allowDeviceSelection: params.allowDeviceSelection
|
|
519
558
|
};
|
|
520
559
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
560
|
+
try {
|
|
561
|
+
const result = await runAllNetworkGetAddress({
|
|
562
|
+
connectId: effectiveTargetId,
|
|
563
|
+
deviceId: _deviceId,
|
|
564
|
+
params,
|
|
565
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
566
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
567
|
+
callItem: async ({ method, chain, item }) => {
|
|
568
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
569
|
+
return callAllNetworkMethod(
|
|
570
|
+
callChain,
|
|
571
|
+
effectiveTargetId,
|
|
572
|
+
itemDeviceId,
|
|
573
|
+
method,
|
|
574
|
+
item,
|
|
575
|
+
commonParams,
|
|
576
|
+
installContext
|
|
577
|
+
);
|
|
578
|
+
},
|
|
579
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
580
|
+
getChainFingerprint,
|
|
581
|
+
effectiveTargetId,
|
|
534
582
|
item,
|
|
535
|
-
|
|
583
|
+
chain,
|
|
584
|
+
payload,
|
|
585
|
+
chainFingerprints,
|
|
536
586
|
installContext
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
558
|
-
method: "allNetworkGetAddress",
|
|
559
|
-
success: result.success,
|
|
560
|
-
payload: result
|
|
561
|
-
});
|
|
562
|
-
return result;
|
|
587
|
+
),
|
|
588
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
589
|
+
buildTopLevelFailure: (response) => {
|
|
590
|
+
const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
|
|
591
|
+
return failure(
|
|
592
|
+
code,
|
|
593
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
594
|
+
response.payload?.params
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
599
|
+
method: "allNetworkGetAddress",
|
|
600
|
+
success: result.success,
|
|
601
|
+
payload: result
|
|
602
|
+
});
|
|
603
|
+
return result;
|
|
604
|
+
} finally {
|
|
605
|
+
releaseOperationRetention?.();
|
|
606
|
+
}
|
|
563
607
|
};
|
|
564
608
|
}
|
|
565
609
|
function isTopLevelAllNetworkFailure(response) {
|
|
@@ -567,7 +611,7 @@ function isTopLevelAllNetworkFailure(response) {
|
|
|
567
611
|
return false;
|
|
568
612
|
}
|
|
569
613
|
const code = response.payload?.code;
|
|
570
|
-
return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
|
|
614
|
+
return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.DeviceDisconnected || code === HardwareErrorCode2.OperationTimeout || code === HardwareErrorCode2.TransportError || code === HardwareErrorCode2.OperationEnded || code === HardwareErrorCode2.OperationNotFound || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
|
|
571
615
|
}
|
|
572
616
|
function getItemDeviceId(item) {
|
|
573
617
|
const { deviceId } = item;
|
|
@@ -597,8 +641,13 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
597
641
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
598
642
|
return coin ? { ...item, coin } : item;
|
|
599
643
|
}
|
|
600
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
601
|
-
const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(
|
|
644
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context) {
|
|
645
|
+
const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(
|
|
646
|
+
getChainFingerprint,
|
|
647
|
+
context.connection?.connectId ?? connectId,
|
|
648
|
+
chain,
|
|
649
|
+
context
|
|
650
|
+
);
|
|
602
651
|
if (!fingerprint) {
|
|
603
652
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
604
653
|
}
|
|
@@ -619,8 +668,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
619
668
|
}
|
|
620
669
|
};
|
|
621
670
|
}
|
|
622
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
623
|
-
const response = await getChainFingerprint(connectId, chain);
|
|
671
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
|
|
672
|
+
const response = await getChainFingerprint(connectId, chain, context);
|
|
624
673
|
return response.success ? response.payload : "";
|
|
625
674
|
}
|
|
626
675
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -725,8 +774,27 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
725
774
|
constructor(connector, options) {
|
|
726
775
|
this.vendor = "ledger";
|
|
727
776
|
this.emitter = new TypedEventEmitter();
|
|
777
|
+
this._operations = new OperationRegistry({
|
|
778
|
+
vendor: "ledger",
|
|
779
|
+
onEnded: (operation, reason) => {
|
|
780
|
+
const binding = this._pendingOperationBindings.get(operation.operationId);
|
|
781
|
+
if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
|
|
782
|
+
this._finishBleBinding("cancelled");
|
|
783
|
+
}
|
|
784
|
+
this._pendingOperationBindings.delete(operation.operationId);
|
|
785
|
+
this.emitter.emit(SDK.OPERATION_ENDED, {
|
|
786
|
+
type: SDK.OPERATION_ENDED,
|
|
787
|
+
payload: { operationId: operation.operationId, reason }
|
|
788
|
+
});
|
|
789
|
+
if (reason === "timeout") {
|
|
790
|
+
void this._releaseOperationConnection(operation);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
});
|
|
728
794
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
729
795
|
this._sessions = /* @__PURE__ */ new Map();
|
|
796
|
+
this._pendingOperationBindings = /* @__PURE__ */ new Map();
|
|
797
|
+
this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
730
798
|
this._uiRegistry = new UiRequestRegistry();
|
|
731
799
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
732
800
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -740,10 +808,30 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
740
808
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
741
809
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
742
810
|
this._doConnectAbortController = null;
|
|
811
|
+
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
812
|
+
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
813
|
+
this._resetPromise = null;
|
|
814
|
+
this._stateGeneration = 0;
|
|
815
|
+
this._connectorTeardownTail = Promise.resolve();
|
|
816
|
+
this._pendingConnectorTeardowns = 0;
|
|
817
|
+
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
818
|
+
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
743
819
|
this._installProgressLastEmittedValue = -Infinity;
|
|
744
820
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
745
821
|
callChain: this.callChain.bind(this),
|
|
746
|
-
getChainFingerprint: (connectId, chain) =>
|
|
822
|
+
getChainFingerprint: async (connectId, chain, context) => {
|
|
823
|
+
try {
|
|
824
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
825
|
+
chain,
|
|
826
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
827
|
+
);
|
|
828
|
+
return success(fingerprint);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
return this.errorToFailure(error);
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
834
|
+
errorToFailure: (error) => this.errorToFailure(error)
|
|
747
835
|
});
|
|
748
836
|
// ---------------------------------------------------------------------------
|
|
749
837
|
// Private helpers
|
|
@@ -753,7 +841,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
753
841
|
*
|
|
754
842
|
* - If a session already exists for the given connectId, reuse it.
|
|
755
843
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
756
|
-
* - Otherwise: search →
|
|
844
|
+
* - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
|
|
757
845
|
*/
|
|
758
846
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
759
847
|
this._connectingPromise = null;
|
|
@@ -770,6 +858,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
770
858
|
});
|
|
771
859
|
};
|
|
772
860
|
this.deviceDisconnectHandler = (data) => {
|
|
861
|
+
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
862
|
+
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
863
|
+
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
864
|
+
} else {
|
|
865
|
+
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
866
|
+
}
|
|
773
867
|
this._discoveredDevices.delete(data.connectId);
|
|
774
868
|
this._sessions.delete(data.connectId);
|
|
775
869
|
this.emitter.emit(DEVICE.DISCONNECT, {
|
|
@@ -826,11 +920,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
826
920
|
this._jobQueue = new DeviceJobQueue();
|
|
827
921
|
this.registerEventListeners();
|
|
828
922
|
}
|
|
923
|
+
_finishBleBinding(status) {
|
|
924
|
+
const selectionRequestId = this._bindingSelectionRequestId;
|
|
925
|
+
this._bindingSelectionRequestId = void 0;
|
|
926
|
+
if (selectionRequestId)
|
|
927
|
+
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
928
|
+
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
929
|
+
payload: { selectionRequestId, status }
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
_isBleConnection() {
|
|
933
|
+
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
934
|
+
}
|
|
829
935
|
// Transport
|
|
830
936
|
get activeTransport() {
|
|
831
|
-
return
|
|
937
|
+
return this._isBleConnection() ? "ble" : "hid";
|
|
832
938
|
}
|
|
833
939
|
getAvailableTransports() {
|
|
940
|
+
if (this.connector.availableTransports) {
|
|
941
|
+
return this.connector.availableTransports.map(
|
|
942
|
+
(transport) => transport === "ble" ? "ble" : "hid"
|
|
943
|
+
);
|
|
944
|
+
}
|
|
834
945
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
835
946
|
}
|
|
836
947
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -847,21 +958,39 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
847
958
|
* The next operation will re-discover and re-connect automatically.
|
|
848
959
|
*/
|
|
849
960
|
resetState() {
|
|
961
|
+
void this._resetStateAndDisconnectSessions();
|
|
962
|
+
}
|
|
963
|
+
_resetStateAndDisconnectSessions() {
|
|
964
|
+
if (this._resetPromise) return this._resetPromise;
|
|
965
|
+
const sessionIds = new Set(this._sessions.values());
|
|
966
|
+
this._stateGeneration += 1;
|
|
967
|
+
this._finishBleBinding("cancelled");
|
|
968
|
+
this._operations.endAll("runtime-reset");
|
|
969
|
+
this._doConnectAbortController?.abort();
|
|
850
970
|
this._discoveredDevices.clear();
|
|
851
971
|
this._sessions.clear();
|
|
972
|
+
this._verifiedBleReconnectTargets.clear();
|
|
852
973
|
this._connectingPromise = null;
|
|
974
|
+
this._doConnectAbortController = null;
|
|
853
975
|
this._uiRegistry.reset();
|
|
854
976
|
this._jobQueue.clear();
|
|
855
977
|
this._btcHighIndexConfirmedThisSession = false;
|
|
978
|
+
const resetPromise = this._runConnectorTeardown(async () => {
|
|
979
|
+
for (const sessionId of sessionIds) {
|
|
980
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
this._resetPromise = resetPromise;
|
|
984
|
+
return resetPromise.finally(() => {
|
|
985
|
+
if (this._resetPromise === resetPromise) {
|
|
986
|
+
this._resetPromise = null;
|
|
987
|
+
}
|
|
988
|
+
});
|
|
856
989
|
}
|
|
857
990
|
async dispose() {
|
|
858
|
-
this.
|
|
859
|
-
this._jobQueue.clear();
|
|
991
|
+
await this._resetStateAndDisconnectSessions();
|
|
860
992
|
this.unregisterEventListeners();
|
|
861
993
|
this.connector.reset();
|
|
862
|
-
this._discoveredDevices.clear();
|
|
863
|
-
this._sessions.clear();
|
|
864
|
-
this._btcHighIndexConfirmedThisSession = false;
|
|
865
994
|
this.emitter.removeAllListeners();
|
|
866
995
|
}
|
|
867
996
|
uiResponse(response) {
|
|
@@ -871,17 +1000,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
871
1000
|
// Device management
|
|
872
1001
|
// ---------------------------------------------------------------------------
|
|
873
1002
|
async searchDevices(options) {
|
|
1003
|
+
return this._searchDevices(options);
|
|
1004
|
+
}
|
|
1005
|
+
async _searchDevices(options, signal) {
|
|
874
1006
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
875
1007
|
try {
|
|
876
1008
|
if (options?.resetSession) {
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
this.
|
|
880
|
-
|
|
881
|
-
|
|
1009
|
+
await this._resetStateAndDisconnectSessions();
|
|
1010
|
+
} else {
|
|
1011
|
+
await this._connectorTeardownTail;
|
|
1012
|
+
}
|
|
1013
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1014
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1015
|
+
const stateGeneration = this._stateGeneration;
|
|
1016
|
+
const devices = await this.connector.searchDevices(
|
|
1017
|
+
options?.transportType ? {
|
|
1018
|
+
transportType: options.transportType,
|
|
1019
|
+
waitForAll: options.waitForAllTransports
|
|
1020
|
+
} : void 0
|
|
1021
|
+
);
|
|
1022
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1023
|
+
if (stateGeneration !== this._stateGeneration) {
|
|
1024
|
+
throw createHwkError({
|
|
1025
|
+
code: HardwareErrorCode3.UserAborted,
|
|
1026
|
+
message: "Ledger discovery was reset"
|
|
1027
|
+
});
|
|
882
1028
|
}
|
|
883
|
-
await this._ensureDevicePermission();
|
|
884
|
-
const devices = await this.connector.searchDevices();
|
|
885
1029
|
this._discoveredDevices.clear();
|
|
886
1030
|
for (const d of devices) {
|
|
887
1031
|
if (d.connectId) {
|
|
@@ -889,7 +1033,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
889
1033
|
}
|
|
890
1034
|
}
|
|
891
1035
|
if (this._discoveredDevices.size === 0) {
|
|
892
|
-
await this._ensureDevicePermission();
|
|
1036
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
893
1037
|
}
|
|
894
1038
|
const result = Array.from(this._discoveredDevices.values());
|
|
895
1039
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -908,41 +1052,285 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
908
1052
|
throw err;
|
|
909
1053
|
}
|
|
910
1054
|
}
|
|
1055
|
+
async searchDeviceTargets(options) {
|
|
1056
|
+
const devices = await this.searchDevices(options);
|
|
1057
|
+
return devices.map((device) => ({
|
|
1058
|
+
searchTargetId: device.connectId,
|
|
1059
|
+
searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
|
|
1060
|
+
vendor: "ledger",
|
|
1061
|
+
connectionType: device.connectionType,
|
|
1062
|
+
kind: "physical",
|
|
1063
|
+
label: device.label,
|
|
1064
|
+
model: device.model,
|
|
1065
|
+
modelName: device.modelName,
|
|
1066
|
+
serialNumber: device.serialNumber
|
|
1067
|
+
}));
|
|
1068
|
+
}
|
|
1069
|
+
async listConnectionTargets(options) {
|
|
1070
|
+
const targets = await this.searchDeviceTargets(options);
|
|
1071
|
+
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1072
|
+
...target,
|
|
1073
|
+
targetId: searchTargetId
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
911
1076
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
912
|
-
async _evictAllSessions() {
|
|
1077
|
+
async _evictAllSessions(preserveOperationId) {
|
|
1078
|
+
this._operations.endAll("explicit", preserveOperationId);
|
|
913
1079
|
if (this._sessions.size === 0) return;
|
|
914
1080
|
const stale = [...this._sessions.values()];
|
|
915
1081
|
this._sessions.clear();
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1082
|
+
await this._runConnectorTeardown(async () => {
|
|
1083
|
+
for (const sid of stale) {
|
|
1084
|
+
try {
|
|
1085
|
+
await this.connector.disconnect(sid);
|
|
1086
|
+
} catch {
|
|
1087
|
+
}
|
|
920
1088
|
}
|
|
921
|
-
}
|
|
1089
|
+
});
|
|
922
1090
|
}
|
|
923
1091
|
static _createDeviceBusyError(method) {
|
|
924
1092
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
925
1093
|
code: HardwareErrorCode3.DeviceBusy
|
|
926
1094
|
});
|
|
927
1095
|
}
|
|
928
|
-
async connectDevice(
|
|
1096
|
+
async connectDevice(searchTargetId) {
|
|
1097
|
+
try {
|
|
1098
|
+
return await this._jobQueue.enqueue(
|
|
1099
|
+
searchTargetId || "__ledger_connect__",
|
|
1100
|
+
async () => {
|
|
1101
|
+
const connected = await this._connectTarget(searchTargetId);
|
|
1102
|
+
if (!connected.success) return connected;
|
|
1103
|
+
return this._createOperation(searchTargetId, connected.payload);
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
label: "connectDevice",
|
|
1107
|
+
rejectIfBusy: true,
|
|
1108
|
+
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1109
|
+
}
|
|
1110
|
+
);
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
return this.errorToFailure(error);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async bindBleDevice(params) {
|
|
1116
|
+
if (params.identity.vendor !== "ledger" || !params.identity.value) {
|
|
1117
|
+
return failure2(HardwareErrorCode3.InvalidParams, "Ledger wallet identity is required");
|
|
1118
|
+
}
|
|
1119
|
+
const { chain, value: expectedFingerprint } = params.identity;
|
|
1120
|
+
try {
|
|
1121
|
+
return await this._jobQueue.enqueue(
|
|
1122
|
+
expectedFingerprint,
|
|
1123
|
+
async (signal) => {
|
|
1124
|
+
if (!this.getAvailableTransports().includes("ble")) {
|
|
1125
|
+
throw createHwkError({
|
|
1126
|
+
code: HardwareErrorCode3.TransportNotAvailable,
|
|
1127
|
+
message: "Ledger Bluetooth transport is not available"
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
this._activeConnectionType = "ble";
|
|
1131
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1132
|
+
const attempt = {
|
|
1133
|
+
extra: params.extra,
|
|
1134
|
+
bindingReason: "manual-rebind"
|
|
1135
|
+
};
|
|
1136
|
+
try {
|
|
1137
|
+
for (; ; ) {
|
|
1138
|
+
const connectId = await this._connectFirstOrSelect(
|
|
1139
|
+
[],
|
|
1140
|
+
void 0,
|
|
1141
|
+
true,
|
|
1142
|
+
void 0,
|
|
1143
|
+
attempt,
|
|
1144
|
+
signal
|
|
1145
|
+
);
|
|
1146
|
+
const sessionId = this._sessions.get(connectId);
|
|
1147
|
+
if (!sessionId) {
|
|
1148
|
+
throw createHwkError({
|
|
1149
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
1150
|
+
message: "Selected Ledger connection ended"
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
let saved = false;
|
|
1154
|
+
try {
|
|
1155
|
+
const installContext = {
|
|
1156
|
+
connection: { connectId, sessionId }
|
|
1157
|
+
};
|
|
1158
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1159
|
+
chain,
|
|
1160
|
+
(method, callParams) => this._runConnectorCall(
|
|
1161
|
+
connectId,
|
|
1162
|
+
method,
|
|
1163
|
+
callParams,
|
|
1164
|
+
signal,
|
|
1165
|
+
void 0,
|
|
1166
|
+
void 0,
|
|
1167
|
+
{ autoInstallApp: true },
|
|
1168
|
+
installContext
|
|
1169
|
+
)
|
|
1170
|
+
);
|
|
1171
|
+
if (fingerprint !== expectedFingerprint) {
|
|
1172
|
+
attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
1173
|
+
attempt.rejectedConnectIds.add(connectId);
|
|
1174
|
+
attempt.rejectedConnectId = connectId;
|
|
1175
|
+
} else {
|
|
1176
|
+
const persisted = await this._publishVerifiedBleBinding(
|
|
1177
|
+
connectId,
|
|
1178
|
+
chain,
|
|
1179
|
+
fingerprint,
|
|
1180
|
+
attempt,
|
|
1181
|
+
void 0,
|
|
1182
|
+
signal
|
|
1183
|
+
);
|
|
1184
|
+
if (!persisted) {
|
|
1185
|
+
throw createHwkError({
|
|
1186
|
+
code: HardwareErrorCode3.UnknownError,
|
|
1187
|
+
message: "Bluetooth binding could not be saved",
|
|
1188
|
+
origin: "host"
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
saved = true;
|
|
1192
|
+
return success(connectId);
|
|
1193
|
+
}
|
|
1194
|
+
} finally {
|
|
1195
|
+
if (!saved && this._sessions.get(connectId) === sessionId) {
|
|
1196
|
+
this._sessions.delete(connectId);
|
|
1197
|
+
const teardown = this._runConnectorTeardown(
|
|
1198
|
+
() => this.connector.disconnect(sessionId)
|
|
1199
|
+
).catch(() => void 0);
|
|
1200
|
+
if (!signal.aborted) await teardown;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1206
|
+
throw error;
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
label: "bindBleDevice",
|
|
1211
|
+
rejectIfBusy: true,
|
|
1212
|
+
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1213
|
+
}
|
|
1214
|
+
);
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
return this.errorToFailure(error);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
async acquireOperation(connectId, context) {
|
|
1220
|
+
try {
|
|
1221
|
+
return await this._jobQueue.enqueue(
|
|
1222
|
+
connectId || "__ledger_acquire__",
|
|
1223
|
+
async (signal) => {
|
|
1224
|
+
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1225
|
+
const hint = context.knownConnections?.find(
|
|
1226
|
+
(connection) => connection.transport === transport
|
|
1227
|
+
);
|
|
1228
|
+
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1229
|
+
await this._ensureDevicePermission(target, void 0, signal);
|
|
1230
|
+
const attempt = {
|
|
1231
|
+
...context,
|
|
1232
|
+
extra: context.extra ? { ...context.extra } : void 0
|
|
1233
|
+
};
|
|
1234
|
+
try {
|
|
1235
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1236
|
+
target,
|
|
1237
|
+
signal,
|
|
1238
|
+
true,
|
|
1239
|
+
void 0,
|
|
1240
|
+
attempt
|
|
1241
|
+
);
|
|
1242
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1243
|
+
const result = this._createOperation(connectId, resolvedConnectId);
|
|
1244
|
+
if (result.success && attempt.selectedConnection) {
|
|
1245
|
+
this._pendingOperationBindings.set(result.payload, attempt);
|
|
1246
|
+
}
|
|
1247
|
+
return result;
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1250
|
+
throw error;
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
label: "acquireOperation",
|
|
1255
|
+
rejectIfBusy: true,
|
|
1256
|
+
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1257
|
+
}
|
|
1258
|
+
);
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
return this.errorToFailure(error);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1264
|
+
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1265
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1266
|
+
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1267
|
+
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1268
|
+
vendor: "ledger",
|
|
1269
|
+
model: "unknown",
|
|
1270
|
+
firmwareVersion: "",
|
|
1271
|
+
deviceId: "",
|
|
1272
|
+
connectId: resolvedConnectId,
|
|
1273
|
+
connectionType: this.connector.connectionType
|
|
1274
|
+
};
|
|
1275
|
+
const operation = this._operations.create({
|
|
1276
|
+
searchTargetId,
|
|
1277
|
+
connectId: resolvedConnectId,
|
|
1278
|
+
device,
|
|
1279
|
+
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1280
|
+
});
|
|
1281
|
+
return success(operation.operationId);
|
|
1282
|
+
}
|
|
1283
|
+
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
929
1284
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
930
1285
|
try {
|
|
931
|
-
|
|
1286
|
+
this._assertConnectorReady("connectDevice");
|
|
1287
|
+
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1288
|
+
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1289
|
+
this._activeConnectionType = discoveredType;
|
|
1290
|
+
}
|
|
1291
|
+
if (this._isBleConnection() && !connectId) {
|
|
932
1292
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
933
1293
|
code: HardwareErrorCode3.DeviceNotFound
|
|
934
1294
|
});
|
|
935
1295
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1296
|
+
await this._ensureDevicePermission(connectId, void 0, signal);
|
|
1297
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1298
|
+
if (this.connector.availableTransports && this.connector.availableTransports.length > 1) {
|
|
1299
|
+
await this._evictAllSessions(preserveOperationId);
|
|
1300
|
+
} else if (this._isBleConnection()) {
|
|
1301
|
+
const previousSessionId = this._sessions.get(connectId);
|
|
1302
|
+
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1303
|
+
if (previousSessionId) {
|
|
1304
|
+
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1305
|
+
this._sessions.delete(connectId);
|
|
1306
|
+
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1307
|
+
}
|
|
1308
|
+
} else {
|
|
1309
|
+
await this._evictAllSessions(preserveOperationId);
|
|
1310
|
+
}
|
|
1311
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1312
|
+
const stateGeneration = this._stateGeneration;
|
|
1313
|
+
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1314
|
+
let session;
|
|
1315
|
+
try {
|
|
1316
|
+
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1317
|
+
transportType: this._activeConnectionType ?? "usb"
|
|
1318
|
+
}) : await this.connector.connect(connectId);
|
|
1319
|
+
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1320
|
+
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1321
|
+
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1322
|
+
code: HardwareErrorCode3.UserAborted
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
} finally {
|
|
1326
|
+
releaseOperation();
|
|
938
1327
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
this._sessions.set(connectId, session.sessionId);
|
|
1328
|
+
const resolvedConnectId = session.deviceInfo?.connectId || connectId;
|
|
1329
|
+
this._sessions.set(resolvedConnectId, session.sessionId);
|
|
942
1330
|
if (session.deviceInfo) {
|
|
943
|
-
this._discoveredDevices.set(
|
|
1331
|
+
this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
|
|
944
1332
|
}
|
|
945
|
-
const result = success(
|
|
1333
|
+
const result = success(resolvedConnectId);
|
|
946
1334
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
947
1335
|
return result;
|
|
948
1336
|
} catch (err) {
|
|
@@ -955,30 +1343,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
955
1343
|
return failureResult;
|
|
956
1344
|
}
|
|
957
1345
|
}
|
|
958
|
-
async
|
|
1346
|
+
async releaseOperation(operationId) {
|
|
1347
|
+
const operation = this._operations.find(operationId);
|
|
1348
|
+
if (!operation) {
|
|
1349
|
+
this._operations.resolve(operationId);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1353
|
+
if (!endedOperation) return;
|
|
1354
|
+
const { connectId } = operation;
|
|
959
1355
|
debugLog("[LedgerAdapter][REQ]", {
|
|
960
|
-
method: "
|
|
1356
|
+
method: "releaseOperation",
|
|
961
1357
|
connectId,
|
|
962
1358
|
params: { connectId }
|
|
963
1359
|
});
|
|
964
1360
|
try {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
await this.connector.disconnect(sessionId);
|
|
968
|
-
this._sessions.delete(connectId);
|
|
969
|
-
}
|
|
970
|
-
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1361
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1362
|
+
debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
|
|
971
1363
|
} catch (err) {
|
|
972
1364
|
const e = err;
|
|
973
1365
|
debugLog("[LedgerAdapter][RES]", {
|
|
974
|
-
method: "
|
|
1366
|
+
method: "releaseOperation",
|
|
975
1367
|
success: false,
|
|
976
1368
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
977
1369
|
});
|
|
978
1370
|
throw err;
|
|
979
1371
|
}
|
|
980
1372
|
}
|
|
981
|
-
async
|
|
1373
|
+
async _releaseOperationConnection(operation) {
|
|
1374
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
1375
|
+
for (const [connectId, sessionId] of this._sessions) {
|
|
1376
|
+
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1377
|
+
this._sessions.delete(connectId);
|
|
1378
|
+
sessionIds.add(sessionId);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
for (const sessionId of sessionIds) {
|
|
1382
|
+
await this._runConnectorTeardown(
|
|
1383
|
+
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async _releaseLostOperationConnection(operationId) {
|
|
1388
|
+
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1389
|
+
if (!endedOperation) return;
|
|
1390
|
+
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1391
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1392
|
+
}
|
|
1393
|
+
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1394
|
+
let connectId;
|
|
1395
|
+
try {
|
|
1396
|
+
connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
return this.errorToFailure(error);
|
|
1399
|
+
}
|
|
982
1400
|
debugLog("[LedgerAdapter][REQ]", {
|
|
983
1401
|
method: "getDeviceInfo",
|
|
984
1402
|
connectId,
|
|
@@ -1016,9 +1434,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1016
1434
|
throw err;
|
|
1017
1435
|
}
|
|
1018
1436
|
}
|
|
1019
|
-
getSupportedChains() {
|
|
1020
|
-
return ["evm", "btc", "sol", "tron"];
|
|
1021
|
-
}
|
|
1022
1437
|
// ---------------------------------------------------------------------------
|
|
1023
1438
|
// Chain call helper
|
|
1024
1439
|
// ---------------------------------------------------------------------------
|
|
@@ -1053,13 +1468,21 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1053
1468
|
if (params && typeof params === "object") {
|
|
1054
1469
|
const {
|
|
1055
1470
|
autoInstallApp,
|
|
1471
|
+
operationId,
|
|
1056
1472
|
passphraseState: _passphraseState,
|
|
1057
1473
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1474
|
+
knownConnections,
|
|
1475
|
+
extra,
|
|
1476
|
+
allowDeviceSelection,
|
|
1058
1477
|
...rest
|
|
1059
1478
|
} = params;
|
|
1060
1479
|
return {
|
|
1061
1480
|
commonParams: {
|
|
1062
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1481
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
|
|
1482
|
+
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1483
|
+
knownConnections,
|
|
1484
|
+
extra,
|
|
1485
|
+
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0
|
|
1063
1486
|
},
|
|
1064
1487
|
rest
|
|
1065
1488
|
};
|
|
@@ -1235,6 +1658,27 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1235
1658
|
);
|
|
1236
1659
|
}
|
|
1237
1660
|
// ---------------------------------------------------------------------------
|
|
1661
|
+
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1662
|
+
// ---------------------------------------------------------------------------
|
|
1663
|
+
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
1664
|
+
return this.callChainWithMergedParams(
|
|
1665
|
+
connectId,
|
|
1666
|
+
deviceId,
|
|
1667
|
+
"zcash",
|
|
1668
|
+
"zcashGetFullViewingKey",
|
|
1669
|
+
params
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1673
|
+
return this.callChainWithMergedParams(
|
|
1674
|
+
connectId,
|
|
1675
|
+
deviceId,
|
|
1676
|
+
"zcash",
|
|
1677
|
+
"zcashGetShieldedAddress",
|
|
1678
|
+
params
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
// ---------------------------------------------------------------------------
|
|
1238
1682
|
// App management — OS-level Ledger app install / list. Bypasses fingerprint
|
|
1239
1683
|
// and chain-handler dispatch; installApp progress is forwarded to the adapter
|
|
1240
1684
|
// emitter via 'ui-event' AppInstallProgress events.
|
|
@@ -1323,7 +1767,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1323
1767
|
);
|
|
1324
1768
|
}
|
|
1325
1769
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1326
|
-
this.
|
|
1770
|
+
await this._resetStateAndDisconnectSessions();
|
|
1327
1771
|
}
|
|
1328
1772
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1329
1773
|
if (!result.isGenuine) {
|
|
@@ -1349,10 +1793,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1349
1793
|
if (relayUrl) {
|
|
1350
1794
|
try {
|
|
1351
1795
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1352
|
-
this.
|
|
1796
|
+
await this._resetStateAndDisconnectSessions();
|
|
1353
1797
|
} catch {
|
|
1354
1798
|
this.connector.reset();
|
|
1355
|
-
this.
|
|
1799
|
+
await this._resetStateAndDisconnectSessions();
|
|
1356
1800
|
}
|
|
1357
1801
|
}
|
|
1358
1802
|
}
|
|
@@ -1364,6 +1808,20 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1364
1808
|
this.emitter.off(event, listener);
|
|
1365
1809
|
}
|
|
1366
1810
|
cancel(connectId) {
|
|
1811
|
+
const namedOperationIsLive = (id) => {
|
|
1812
|
+
try {
|
|
1813
|
+
this._operations.resolve(id);
|
|
1814
|
+
return true;
|
|
1815
|
+
} catch {
|
|
1816
|
+
return false;
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
if (isHardwareOperationId(connectId) && !namedOperationIsLive(connectId)) {
|
|
1820
|
+
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1821
|
+
connectId
|
|
1822
|
+
});
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1367
1825
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1368
1826
|
code: HardwareErrorCode3.UserAborted,
|
|
1369
1827
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1374,14 +1832,37 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1374
1832
|
this._lastCancelReason = void 0;
|
|
1375
1833
|
}
|
|
1376
1834
|
}, 2e3);
|
|
1835
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1836
|
+
let operationId;
|
|
1837
|
+
if (isHardwareOperationId(connectId)) {
|
|
1838
|
+
operationId = connectId;
|
|
1839
|
+
} else if (!connectId && isHardwareOperationId(activeJobId)) {
|
|
1840
|
+
operationId = activeJobId;
|
|
1841
|
+
}
|
|
1842
|
+
let resolvedConnectId = connectId;
|
|
1843
|
+
if (operationId) {
|
|
1844
|
+
try {
|
|
1845
|
+
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1846
|
+
} catch {
|
|
1847
|
+
resolvedConnectId = void 0;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1377
1850
|
this._uiRegistry.cancel();
|
|
1378
|
-
|
|
1379
|
-
|
|
1851
|
+
this._finishBleBinding("cancelled");
|
|
1852
|
+
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1853
|
+
const queueKey = operationId ?? (activeJobId === connectId ? connectId : interactionForPhysicalId?.operationId) ?? connectId;
|
|
1854
|
+
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1855
|
+
if (!connectId) this._pendingOperationBindings.clear();
|
|
1856
|
+
else if (pendingOperationId) {
|
|
1857
|
+
this._pendingOperationBindings.delete(pendingOperationId);
|
|
1858
|
+
}
|
|
1859
|
+
if (queueKey) {
|
|
1860
|
+
this._jobQueue.cancelActiveAndPending(queueKey, userAbortReason);
|
|
1380
1861
|
} else {
|
|
1381
1862
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1382
1863
|
}
|
|
1383
|
-
if (
|
|
1384
|
-
const sessionId = this._sessions.get(
|
|
1864
|
+
if (resolvedConnectId) {
|
|
1865
|
+
const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
|
|
1385
1866
|
void this.connector.cancel(sessionId);
|
|
1386
1867
|
} else {
|
|
1387
1868
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1393,41 +1874,98 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1393
1874
|
// ---------------------------------------------------------------------------
|
|
1394
1875
|
// Chain fingerprint
|
|
1395
1876
|
// ---------------------------------------------------------------------------
|
|
1877
|
+
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1396
1878
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1397
1879
|
try {
|
|
1398
1880
|
const fingerprint = await this._computeChainFingerprint(
|
|
1399
1881
|
chain,
|
|
1400
1882
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1401
1883
|
);
|
|
1884
|
+
if (deviceId) {
|
|
1885
|
+
if (fingerprint !== deviceId) {
|
|
1886
|
+
return failure2(
|
|
1887
|
+
HardwareErrorCode3.DeviceMismatch,
|
|
1888
|
+
formatDeviceMismatchError(deviceId, fingerprint)
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
if (isHardwareOperationId(connectId)) {
|
|
1892
|
+
const operation = this._operations.resolve(connectId);
|
|
1893
|
+
await this._publishVerifiedBleBinding(
|
|
1894
|
+
operation.connectId,
|
|
1895
|
+
chain,
|
|
1896
|
+
fingerprint,
|
|
1897
|
+
void 0,
|
|
1898
|
+
connectId
|
|
1899
|
+
);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1402
1902
|
return success(fingerprint);
|
|
1403
1903
|
} catch (err) {
|
|
1404
1904
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
1405
1905
|
return this.errorToFailure(err);
|
|
1406
1906
|
}
|
|
1407
1907
|
}
|
|
1408
|
-
/**
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1908
|
+
/** Discovery may select a BLE target before a later call verifies its wallet. */
|
|
1909
|
+
async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, operationId, signal) {
|
|
1910
|
+
const binding = operationId ? this._pendingOperationBindings.get(operationId) : attempt;
|
|
1911
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) {
|
|
1912
|
+
return false;
|
|
1913
|
+
}
|
|
1414
1914
|
try {
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
|
|
1915
|
+
const outcome = await requestSaveDeviceBinding(
|
|
1916
|
+
this.emitter,
|
|
1917
|
+
this._uiRegistry,
|
|
1918
|
+
{
|
|
1919
|
+
selectionRequestId: binding.selectedConnection.requestId,
|
|
1920
|
+
connection: { transport: "ble", connectId },
|
|
1921
|
+
identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
|
|
1922
|
+
extra: binding.extra
|
|
1923
|
+
},
|
|
1924
|
+
signal
|
|
1418
1925
|
);
|
|
1419
|
-
if (
|
|
1420
|
-
|
|
1926
|
+
if (operationId) this._operations.resolve(operationId);
|
|
1927
|
+
if (!outcome.saved) {
|
|
1928
|
+
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
1929
|
+
connectId,
|
|
1930
|
+
reason: outcome.reason
|
|
1931
|
+
});
|
|
1421
1932
|
}
|
|
1422
|
-
return
|
|
1423
|
-
} catch (
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1933
|
+
return outcome.saved;
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
if (operationId) {
|
|
1936
|
+
this._pendingOperationBindings.delete(operationId);
|
|
1937
|
+
this._operations.end(operationId, "explicit");
|
|
1938
|
+
}
|
|
1939
|
+
const sessionId = this._sessions.get(connectId);
|
|
1940
|
+
this._sessions.delete(connectId);
|
|
1941
|
+
if (sessionId) {
|
|
1942
|
+
const teardown = this._runConnectorTeardown(
|
|
1943
|
+
() => this.connector.disconnect(sessionId)
|
|
1944
|
+
).catch(() => void 0);
|
|
1945
|
+
if (!signal?.aborted) await teardown;
|
|
1946
|
+
}
|
|
1947
|
+
throw error;
|
|
1948
|
+
} finally {
|
|
1949
|
+
if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
|
|
1950
|
+
this._bindingSelectionRequestId = void 0;
|
|
1951
|
+
}
|
|
1952
|
+
if (operationId) {
|
|
1953
|
+
this._pendingOperationBindings.delete(operationId);
|
|
1427
1954
|
}
|
|
1428
|
-
throw err;
|
|
1429
1955
|
}
|
|
1430
1956
|
}
|
|
1957
|
+
/** Verify on the acquired session without re-entering the job queue. */
|
|
1958
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
1959
|
+
if (!deviceId) return { success: true };
|
|
1960
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1961
|
+
chain,
|
|
1962
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
1963
|
+
);
|
|
1964
|
+
if (fingerprint === deviceId) {
|
|
1965
|
+
return { success: true };
|
|
1966
|
+
}
|
|
1967
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
1968
|
+
}
|
|
1431
1969
|
/**
|
|
1432
1970
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
1433
1971
|
*
|
|
@@ -1455,6 +1993,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1455
1993
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
1456
1994
|
} else if (chain === "tron") {
|
|
1457
1995
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
1996
|
+
} else if (chain === "zcash") {
|
|
1997
|
+
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
1458
1998
|
} else {
|
|
1459
1999
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
1460
2000
|
}
|
|
@@ -1584,16 +2124,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1584
2124
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
1585
2125
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
1586
2126
|
// doesn't kill caller B's await.
|
|
1587
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2127
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
1588
2128
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
1589
|
-
|
|
2129
|
+
this._assertConnectorReady("connectDevice");
|
|
2130
|
+
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1590
2131
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1591
2132
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1592
2133
|
});
|
|
1593
2134
|
}
|
|
1594
2135
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
1595
2136
|
if (!connectId && this._sessions.size > 0) {
|
|
1596
|
-
if (!
|
|
2137
|
+
if (!this._isBleConnection() && this._sessions.size > 1) {
|
|
1597
2138
|
throw Object.assign(
|
|
1598
2139
|
new Error(
|
|
1599
2140
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -1603,15 +2144,24 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1603
2144
|
}
|
|
1604
2145
|
return this._sessions.keys().next().value;
|
|
1605
2146
|
}
|
|
1606
|
-
if (!this._connectingPromise) {
|
|
1607
|
-
|
|
1608
|
-
|
|
2147
|
+
if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
|
|
2148
|
+
const controller = new AbortController();
|
|
2149
|
+
this._doConnectAbortController = controller;
|
|
2150
|
+
const innerSignal = controller.signal;
|
|
1609
2151
|
this._connectingPromise = (async () => {
|
|
1610
2152
|
try {
|
|
1611
|
-
return await this._doConnect(
|
|
2153
|
+
return await this._doConnect(
|
|
2154
|
+
innerSignal,
|
|
2155
|
+
connectId,
|
|
2156
|
+
allowUsbEphemeralFallback,
|
|
2157
|
+
preserveOperationId,
|
|
2158
|
+
context
|
|
2159
|
+
);
|
|
1612
2160
|
} finally {
|
|
1613
|
-
this.
|
|
1614
|
-
|
|
2161
|
+
if (this._doConnectAbortController === controller) {
|
|
2162
|
+
this._connectingPromise = null;
|
|
2163
|
+
this._doConnectAbortController = null;
|
|
2164
|
+
}
|
|
1615
2165
|
}
|
|
1616
2166
|
})();
|
|
1617
2167
|
}
|
|
@@ -1620,22 +2170,51 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1620
2170
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1621
2171
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1622
2172
|
// throw DeviceNotFound so the user is kicked out of the loop.
|
|
1623
|
-
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
2173
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
2174
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2175
|
+
if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
|
|
2176
|
+
this._activeConnectionType = "usb";
|
|
2177
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
|
|
2178
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2179
|
+
if (usbDevices.length > 0) {
|
|
2180
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2181
|
+
(connection) => connection.transport === "usb"
|
|
2182
|
+
);
|
|
2183
|
+
const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
|
|
2184
|
+
return this._connectFirstOrSelect(
|
|
2185
|
+
usbDevices,
|
|
2186
|
+
usbTarget,
|
|
2187
|
+
allowUsbEphemeralFallback,
|
|
2188
|
+
preserveOperationId,
|
|
2189
|
+
context,
|
|
2190
|
+
internalSignal
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
this._activeConnectionType = "ble";
|
|
2194
|
+
const knownBle = context?.knownConnections?.find(
|
|
2195
|
+
(connection) => connection.transport === "ble"
|
|
2196
|
+
);
|
|
2197
|
+
if (knownBle?.transport === "ble") {
|
|
2198
|
+
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2199
|
+
}
|
|
2200
|
+
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2201
|
+
throw createHwkError({
|
|
2202
|
+
code: HardwareErrorCode3.DeviceNotFound,
|
|
2203
|
+
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2204
|
+
});
|
|
1638
2205
|
}
|
|
2206
|
+
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2207
|
+
return this._connectFirstOrSelect(
|
|
2208
|
+
bleDevices,
|
|
2209
|
+
void 0,
|
|
2210
|
+
allowUsbEphemeralFallback,
|
|
2211
|
+
preserveOperationId,
|
|
2212
|
+
context,
|
|
2213
|
+
internalSignal
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
if (this._isBleConnection() && targetConnectId) {
|
|
2217
|
+
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
1639
2218
|
}
|
|
1640
2219
|
let confirms = 0;
|
|
1641
2220
|
while (!internalSignal.aborted) {
|
|
@@ -1643,22 +2222,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1643
2222
|
type: EConnectorInteraction.Searching,
|
|
1644
2223
|
payload: { sessionId: "" }
|
|
1645
2224
|
});
|
|
1646
|
-
let devices = await this.
|
|
2225
|
+
let devices = await this._searchDevices(void 0, internalSignal);
|
|
2226
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1647
2227
|
if (devices.length === 0) {
|
|
1648
2228
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1649
2229
|
await new Promise((resolve) => {
|
|
1650
2230
|
setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
1651
2231
|
});
|
|
1652
|
-
|
|
2232
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2233
|
+
devices = await this._searchDevices(void 0, internalSignal);
|
|
2234
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1653
2235
|
if (devices.length > 0) break;
|
|
1654
2236
|
}
|
|
1655
2237
|
}
|
|
1656
|
-
if (devices.length > 0) {
|
|
2238
|
+
if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
|
|
1657
2239
|
try {
|
|
1658
2240
|
return await this._connectFirstOrSelect(
|
|
1659
2241
|
devices,
|
|
1660
2242
|
targetConnectId,
|
|
1661
|
-
allowUsbEphemeralFallback
|
|
2243
|
+
allowUsbEphemeralFallback,
|
|
2244
|
+
preserveOperationId,
|
|
2245
|
+
context,
|
|
2246
|
+
internalSignal
|
|
1662
2247
|
);
|
|
1663
2248
|
} catch (err) {
|
|
1664
2249
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -1687,45 +2272,133 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1687
2272
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1688
2273
|
throw new Error("_doConnect aborted");
|
|
1689
2274
|
}
|
|
1690
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2275
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
|
|
2276
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1691
2277
|
if (targetConnectId) {
|
|
1692
2278
|
const target = devices.find(
|
|
1693
2279
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1694
2280
|
);
|
|
1695
2281
|
if (target) {
|
|
1696
|
-
return this._connectDeviceOrThrow(target.connectId);
|
|
2282
|
+
return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
|
|
1697
2283
|
}
|
|
1698
|
-
if (!
|
|
2284
|
+
if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
1699
2285
|
debugLog(
|
|
1700
2286
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
1701
2287
|
);
|
|
1702
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
1703
|
-
}
|
|
1704
|
-
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
1705
|
-
code: HardwareErrorCode3.DeviceNotFound
|
|
1706
|
-
});
|
|
1707
|
-
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
1708
|
-
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2288
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1709
2289
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2290
|
+
if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
|
|
2291
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
2292
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
2293
|
+
});
|
|
2294
|
+
if (this._isBleConnection()) {
|
|
2295
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2296
|
+
}
|
|
2297
|
+
throw err;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
const requiresBleSelection = this._isBleConnection();
|
|
2301
|
+
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1713
2302
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1714
2303
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1715
2304
|
});
|
|
1716
2305
|
}
|
|
1717
|
-
if (
|
|
1718
|
-
|
|
2306
|
+
if (requiresBleSelection && context?.allowDeviceSelection !== false) {
|
|
2307
|
+
const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
|
|
2308
|
+
if (context) context.bindingSessionId = bindingSessionId;
|
|
2309
|
+
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2310
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2311
|
+
(connection) => connection.transport === "usb"
|
|
2312
|
+
);
|
|
2313
|
+
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2314
|
+
const { device, requestId } = await requestBleDeviceSelection({
|
|
2315
|
+
emitter: this.emitter,
|
|
2316
|
+
registry: this._uiRegistry,
|
|
2317
|
+
signal,
|
|
2318
|
+
allowUsbFallback,
|
|
2319
|
+
scan: async () => {
|
|
2320
|
+
if (allowUsbFallback) {
|
|
2321
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2322
|
+
const candidate = usbDevices.find(
|
|
2323
|
+
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2324
|
+
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2325
|
+
if (candidate) return [candidate];
|
|
2326
|
+
}
|
|
2327
|
+
return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
|
|
2328
|
+
},
|
|
2329
|
+
request: {
|
|
2330
|
+
devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
|
|
2331
|
+
bindingSessionId,
|
|
2332
|
+
rejectedConnectId: context?.rejectedConnectId,
|
|
2333
|
+
context: {
|
|
2334
|
+
kind: "bind-connection",
|
|
2335
|
+
transport: "ble",
|
|
2336
|
+
reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
|
|
2337
|
+
},
|
|
2338
|
+
extra: context?.extra
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2341
|
+
if (device.connectionType === "usb") {
|
|
2342
|
+
this._activeConnectionType = "usb";
|
|
2343
|
+
if (context) context.selectedConnection = void 0;
|
|
2344
|
+
if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
|
|
2345
|
+
this._bindingSelectionRequestId = void 0;
|
|
2346
|
+
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
2347
|
+
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
2348
|
+
payload: { selectionRequestId: requestId, status: "cancelled" }
|
|
2349
|
+
});
|
|
2350
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2351
|
+
}
|
|
2352
|
+
if (context) context.selectedConnection = { connectId: device.connectId, requestId };
|
|
2353
|
+
this._bindingSelectionRequestId = requestId;
|
|
2354
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2355
|
+
}
|
|
2356
|
+
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2357
|
+
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2358
|
+
throw createHwkError({
|
|
2359
|
+
code: HardwareErrorCode3.DeviceNotFound,
|
|
2360
|
+
message: "Select a Ledger device before continuing"
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
const requestId = this._uiRegistry.createRequestId();
|
|
2364
|
+
const waitPromise = this._uiRegistry.wait(
|
|
2365
|
+
UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2366
|
+
{ requestId }
|
|
2367
|
+
);
|
|
2368
|
+
this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2369
|
+
type: UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2370
|
+
payload: {
|
|
2371
|
+
devices,
|
|
2372
|
+
requestId,
|
|
2373
|
+
context: requiresBleSelection ? {
|
|
2374
|
+
kind: "bind-connection",
|
|
2375
|
+
transport: "ble",
|
|
2376
|
+
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2377
|
+
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2378
|
+
extra: context?.extra
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
const { sdkConnectId } = await (signal ? this._abortable(signal, waitPromise) : waitPromise);
|
|
2382
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
2383
|
+
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2384
|
+
if (!selected) {
|
|
2385
|
+
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2386
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
if (context && requiresBleSelection)
|
|
2390
|
+
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2391
|
+
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1719
2392
|
}
|
|
1720
2393
|
if (devices.length !== 1) {
|
|
1721
2394
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
1722
2395
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1723
2396
|
});
|
|
1724
2397
|
}
|
|
1725
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2398
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1726
2399
|
}
|
|
1727
|
-
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1728
|
-
const result = await this.
|
|
2400
|
+
async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
|
|
2401
|
+
const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
|
|
1729
2402
|
if (!result.success) {
|
|
1730
2403
|
const payload = result.payload;
|
|
1731
2404
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -1736,7 +2409,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1736
2409
|
}
|
|
1737
2410
|
throw rethrow;
|
|
1738
2411
|
}
|
|
1739
|
-
return
|
|
2412
|
+
return result.payload;
|
|
1740
2413
|
}
|
|
1741
2414
|
/**
|
|
1742
2415
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -1766,26 +2439,122 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1766
2439
|
* lives in one place.
|
|
1767
2440
|
*/
|
|
1768
2441
|
async _callConnector(sessionId, method, params, signal) {
|
|
1769
|
-
|
|
2442
|
+
this._assertConnectorReady(method);
|
|
2443
|
+
if (signal?.aborted) throw this._abortReason(signal);
|
|
2444
|
+
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2445
|
+
let promise;
|
|
2446
|
+
try {
|
|
2447
|
+
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2448
|
+
} catch (error) {
|
|
2449
|
+
releaseOperation();
|
|
2450
|
+
throw error;
|
|
2451
|
+
}
|
|
1770
2452
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
1771
2453
|
return this._unwrapConnectorResult(result);
|
|
1772
2454
|
}
|
|
2455
|
+
_assertConnectorReady(method) {
|
|
2456
|
+
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2457
|
+
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
_runConnectorTeardown(task) {
|
|
2461
|
+
const previous = this._connectorTeardownTail;
|
|
2462
|
+
let releaseTail = () => void 0;
|
|
2463
|
+
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2464
|
+
releaseTail = resolve;
|
|
2465
|
+
});
|
|
2466
|
+
this._pendingConnectorTeardowns += 1;
|
|
2467
|
+
return (async () => {
|
|
2468
|
+
try {
|
|
2469
|
+
await previous;
|
|
2470
|
+
await this._waitForConnectorOperationsToDrain();
|
|
2471
|
+
await task();
|
|
2472
|
+
} finally {
|
|
2473
|
+
this._pendingConnectorTeardowns -= 1;
|
|
2474
|
+
releaseTail();
|
|
2475
|
+
}
|
|
2476
|
+
})();
|
|
2477
|
+
}
|
|
2478
|
+
_waitForConnectorOperationsToDrain() {
|
|
2479
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2480
|
+
return Promise.resolve();
|
|
2481
|
+
}
|
|
2482
|
+
return new Promise((resolve) => {
|
|
2483
|
+
this._connectorIdleWaiters.add(resolve);
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
_retainConnectorOperation(key) {
|
|
2487
|
+
this._unsettledConnectorOperations.set(
|
|
2488
|
+
key,
|
|
2489
|
+
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2490
|
+
);
|
|
2491
|
+
let released = false;
|
|
2492
|
+
return () => {
|
|
2493
|
+
if (released) return;
|
|
2494
|
+
released = true;
|
|
2495
|
+
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2496
|
+
if (remaining > 0) {
|
|
2497
|
+
this._unsettledConnectorOperations.set(key, remaining);
|
|
2498
|
+
} else {
|
|
2499
|
+
this._unsettledConnectorOperations.delete(key);
|
|
2500
|
+
}
|
|
2501
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2502
|
+
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2503
|
+
this._connectorIdleWaiters.clear();
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
1773
2507
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
1774
|
-
|
|
1775
|
-
|
|
2508
|
+
const positionalOperationId = isHardwareOperationId(connectId) ? connectId : void 0;
|
|
2509
|
+
if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
|
|
2510
|
+
throw createHwkError({
|
|
2511
|
+
code: HardwareErrorCode3.InvalidParams,
|
|
2512
|
+
message: "Conflicting Ledger operation ids",
|
|
2513
|
+
params: {
|
|
2514
|
+
positionalOperationId,
|
|
2515
|
+
commonOperationId: commonParams.operationId
|
|
2516
|
+
}
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2520
|
+
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2521
|
+
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2522
|
+
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2523
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
2524
|
+
method,
|
|
2525
|
+
connectId: effectiveConnectId || "(empty)",
|
|
2526
|
+
params
|
|
2527
|
+
});
|
|
2528
|
+
const queueKey = (operationId ?? effectiveConnectId) || "__ledger_default__";
|
|
1776
2529
|
try {
|
|
1777
2530
|
const result = await this._jobQueue.enqueue(
|
|
1778
2531
|
queueKey,
|
|
1779
|
-
async (signal) =>
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2532
|
+
async (signal) => {
|
|
2533
|
+
if (operationId) this._activeOperationJobs.add(operationId);
|
|
2534
|
+
try {
|
|
2535
|
+
return await this._runConnectorCall(
|
|
2536
|
+
effectiveConnectId,
|
|
2537
|
+
method,
|
|
2538
|
+
params,
|
|
2539
|
+
signal,
|
|
2540
|
+
fingerprint,
|
|
2541
|
+
permissionDeviceId,
|
|
2542
|
+
commonParams,
|
|
2543
|
+
installContext ?? {},
|
|
2544
|
+
operationId
|
|
2545
|
+
);
|
|
2546
|
+
} catch (error) {
|
|
2547
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
2548
|
+
throw error;
|
|
2549
|
+
} finally {
|
|
2550
|
+
if (operationId) {
|
|
2551
|
+
this._activeOperationJobs.delete(operationId);
|
|
2552
|
+
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2553
|
+
await this._releaseLostOperationConnection(operationId);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
},
|
|
1789
2558
|
{
|
|
1790
2559
|
label: method,
|
|
1791
2560
|
rejectIfBusy: true,
|
|
@@ -1806,15 +2575,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1806
2575
|
}
|
|
1807
2576
|
});
|
|
1808
2577
|
throw err;
|
|
2578
|
+
} finally {
|
|
2579
|
+
releaseOperationRetention?.();
|
|
1809
2580
|
}
|
|
1810
2581
|
}
|
|
1811
2582
|
/**
|
|
1812
2583
|
* Race a promise against an abort signal. On abort, rejects with
|
|
1813
2584
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
1814
2585
|
*/
|
|
2586
|
+
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2587
|
+
_abortReason(signal) {
|
|
2588
|
+
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2589
|
+
}
|
|
1815
2590
|
_abortable(signal, promise) {
|
|
1816
|
-
const getAbortReason = () =>
|
|
2591
|
+
const getAbortReason = () => this._abortReason(signal);
|
|
1817
2592
|
if (signal.aborted) {
|
|
2593
|
+
void promise.catch(() => void 0);
|
|
1818
2594
|
return Promise.reject(getAbortReason());
|
|
1819
2595
|
}
|
|
1820
2596
|
return new Promise((resolve, reject) => {
|
|
@@ -1841,13 +2617,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1841
2617
|
}
|
|
1842
2618
|
}
|
|
1843
2619
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
1844
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2620
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
|
|
1845
2621
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2622
|
+
if (!operationId) {
|
|
2623
|
+
await this._ensureDevicePermission(
|
|
2624
|
+
connectId,
|
|
2625
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
2626
|
+
signal
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
1851
2629
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1852
2630
|
let effectiveParams = params;
|
|
1853
2631
|
if (method === "btcGetPublicKey") {
|
|
@@ -1861,36 +2639,132 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1861
2639
|
effectiveParams = gatedParams;
|
|
1862
2640
|
}
|
|
1863
2641
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
1864
|
-
|
|
1865
|
-
|
|
2642
|
+
let businessCallStarted = false;
|
|
2643
|
+
const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
|
|
2644
|
+
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2645
|
+
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2646
|
+
(connection) => connection.transport === knownTransport
|
|
2647
|
+
);
|
|
2648
|
+
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2649
|
+
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
|
|
2650
|
+
const connectionAttempt = { ...commonParams };
|
|
2651
|
+
const bundleConnection = installContext?.connection;
|
|
2652
|
+
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2653
|
+
throw createHwkError({
|
|
2654
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2655
|
+
message: "Ledger all-network connection ended"
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2659
|
+
preferredConnectId,
|
|
1866
2660
|
signal,
|
|
1867
|
-
allowUsbEphemeralFallback
|
|
2661
|
+
allowUsbEphemeralFallback,
|
|
2662
|
+
void 0,
|
|
2663
|
+
connectionAttempt
|
|
1868
2664
|
);
|
|
1869
|
-
|
|
2665
|
+
let sessionId = this._sessions.get(resolvedConnectId);
|
|
2666
|
+
if (sessionId && installContext && !installContext.connection) {
|
|
2667
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2668
|
+
}
|
|
1870
2669
|
if (!sessionId) {
|
|
2670
|
+
if (operationId) {
|
|
2671
|
+
this._operations.end(operationId, "disconnect");
|
|
2672
|
+
throw createHwkError({
|
|
2673
|
+
code: HardwareErrorCode3.OperationEnded,
|
|
2674
|
+
message: "Ledger operation connection is no longer active",
|
|
2675
|
+
params: { operationId, reason: "disconnect" }
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
1871
2678
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
1872
2679
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
1873
2680
|
});
|
|
1874
2681
|
}
|
|
1875
2682
|
try {
|
|
1876
2683
|
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2684
|
+
for (; ; ) {
|
|
2685
|
+
const fp = await this._abortable(
|
|
2686
|
+
signal,
|
|
2687
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2688
|
+
sessionId,
|
|
2689
|
+
fingerprint.deviceId,
|
|
2690
|
+
fingerprint.chain
|
|
2691
|
+
)
|
|
2692
|
+
);
|
|
2693
|
+
if (fp.success) break;
|
|
2694
|
+
const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
|
|
2695
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
|
|
2696
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2697
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
2701
|
+
binding.rejectedConnectIds.add(resolvedConnectId);
|
|
2702
|
+
binding.rejectedConnectId = resolvedConnectId;
|
|
2703
|
+
this._sessions.delete(resolvedConnectId);
|
|
2704
|
+
await this.connector.disconnect(sessionId);
|
|
2705
|
+
if (operationId) this._pendingOperationDisconnects.delete(operationId);
|
|
2706
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2707
|
+
resolvedConnectId = await this._connectFirstOrSelect(
|
|
2708
|
+
[],
|
|
2709
|
+
void 0,
|
|
2710
|
+
true,
|
|
2711
|
+
operationId,
|
|
2712
|
+
binding,
|
|
2713
|
+
signal
|
|
2714
|
+
);
|
|
2715
|
+
const selectedSession = this._sessions.get(resolvedConnectId);
|
|
2716
|
+
const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
|
|
2717
|
+
if (!selectedSession || !selectedDevice)
|
|
2718
|
+
throw createHwkError({
|
|
2719
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2720
|
+
message: "Selected Ledger connection ended"
|
|
2721
|
+
});
|
|
2722
|
+
sessionId = selectedSession;
|
|
2723
|
+
if (operationId)
|
|
2724
|
+
this._operations.rebind(operationId, {
|
|
2725
|
+
connectId: resolvedConnectId,
|
|
2726
|
+
device: selectedDevice,
|
|
2727
|
+
connectionKeys: [sessionId]
|
|
2728
|
+
});
|
|
2729
|
+
if (installContext)
|
|
2730
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2731
|
+
}
|
|
2732
|
+
await this._publishVerifiedBleBinding(
|
|
2733
|
+
resolvedConnectId,
|
|
2734
|
+
fingerprint.chain,
|
|
2735
|
+
fingerprint.deviceId,
|
|
2736
|
+
connectionAttempt,
|
|
2737
|
+
operationId,
|
|
2738
|
+
signal
|
|
1884
2739
|
);
|
|
1885
|
-
if (!
|
|
1886
|
-
|
|
1887
|
-
|
|
2740
|
+
if (!operationId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
|
|
2741
|
+
this._verifiedBleReconnectTargets.set(connectId, {
|
|
2742
|
+
connectId: resolvedConnectId,
|
|
2743
|
+
chain: fingerprint.chain,
|
|
2744
|
+
fingerprint: fingerprint.deviceId
|
|
1888
2745
|
});
|
|
1889
2746
|
}
|
|
1890
2747
|
}
|
|
2748
|
+
businessCallStarted = true;
|
|
1891
2749
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
1892
2750
|
} catch (err) {
|
|
1893
2751
|
if (signal.aborted) throw err;
|
|
2752
|
+
if (isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err)) {
|
|
2753
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2754
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2755
|
+
else {
|
|
2756
|
+
this._sessions.delete(resolvedConnectId);
|
|
2757
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2758
|
+
}
|
|
2759
|
+
const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2760
|
+
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2761
|
+
throw createHwkError({
|
|
2762
|
+
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(err).code,
|
|
2763
|
+
message: "Ledger operation connection was lost; start a new operation",
|
|
2764
|
+
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId }) : interactionParams,
|
|
2765
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
1894
2768
|
const errObj = err;
|
|
1895
2769
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1896
2770
|
message: errObj?.message,
|
|
@@ -1902,6 +2776,65 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1902
2776
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1903
2777
|
isStuckApp: isStuckAppStateError(err)
|
|
1904
2778
|
});
|
|
2779
|
+
if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2780
|
+
await this._waitForDeviceConnect(signal);
|
|
2781
|
+
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2782
|
+
throw createHwkError({
|
|
2783
|
+
code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2784
|
+
message: "Ledger connection ended while waiting for unlock"
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
return this._runConnectorCall(
|
|
2788
|
+
resolvedConnectId,
|
|
2789
|
+
method,
|
|
2790
|
+
effectiveParams,
|
|
2791
|
+
signal,
|
|
2792
|
+
fingerprint,
|
|
2793
|
+
permissionDeviceId,
|
|
2794
|
+
commonParams,
|
|
2795
|
+
installContext,
|
|
2796
|
+
operationId,
|
|
2797
|
+
lockedRetryBudget - 1
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2801
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2802
|
+
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2803
|
+
throw createHwkError({
|
|
2804
|
+
code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2805
|
+
message: "Ledger connection ended during the app transition"
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
try {
|
|
2809
|
+
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2810
|
+
} catch (retryErr) {
|
|
2811
|
+
if (isStuckAppStateError(retryErr)) throw err;
|
|
2812
|
+
if (isDeviceDisconnectedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isTimeoutError(retryErr) || isConnectionLevelError(retryErr)) {
|
|
2813
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2814
|
+
else {
|
|
2815
|
+
this._sessions.delete(resolvedConnectId);
|
|
2816
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2817
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2818
|
+
}
|
|
2819
|
+
throw createHwkError({
|
|
2820
|
+
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(retryErr).code,
|
|
2821
|
+
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2822
|
+
params: !canReplayHardwareMethodAfterTransportFailure(method) ? operationMayHaveCompletedParams(method, {
|
|
2823
|
+
operationId,
|
|
2824
|
+
reason: "disconnect"
|
|
2825
|
+
}) : { operationId, reason: "disconnect" },
|
|
2826
|
+
recovery: !canReplayHardwareMethodAfterTransportFailure(method) ? { scope: "unknown" } : void 0
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
throw retryErr;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
if (!operationId && err?.code === HardwareErrorCode3.DeviceMismatch) {
|
|
2833
|
+
this._sessions.delete(resolvedConnectId);
|
|
2834
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2835
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2836
|
+
throw err;
|
|
2837
|
+
}
|
|
1905
2838
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
1906
2839
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
|
|
1907
2840
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -1957,157 +2890,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1957
2890
|
fingerprint,
|
|
1958
2891
|
permissionDeviceId,
|
|
1959
2892
|
commonParams,
|
|
1960
|
-
installContext
|
|
1961
|
-
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
if (isStuckAppStateError(err)) {
|
|
1965
|
-
try {
|
|
1966
|
-
this._sessions.delete(resolvedConnectId);
|
|
1967
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
1968
|
-
this.connector.reset?.();
|
|
1969
|
-
} catch {
|
|
1970
|
-
}
|
|
1971
|
-
debugLog(
|
|
1972
|
-
"[LedgerAdapter] stuck-app retry: method=",
|
|
1973
|
-
method,
|
|
1974
|
-
"delayMs=",
|
|
1975
|
-
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
|
|
1976
|
-
"_tag=",
|
|
1977
|
-
err?._tag
|
|
1978
|
-
);
|
|
1979
|
-
try {
|
|
1980
|
-
const retryResult = await this._retryAfterStuckApp(
|
|
1981
|
-
resolvedConnectId,
|
|
1982
|
-
method,
|
|
1983
|
-
effectiveParams,
|
|
1984
|
-
signal,
|
|
1985
|
-
err,
|
|
1986
|
-
fingerprint
|
|
2893
|
+
installContext,
|
|
2894
|
+
operationId
|
|
1987
2895
|
);
|
|
1988
|
-
debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
|
|
1989
|
-
return retryResult;
|
|
1990
|
-
} catch (retryErr) {
|
|
1991
|
-
if (signal.aborted) throw retryErr;
|
|
1992
|
-
if (isStuckAppStateError(retryErr)) {
|
|
1993
|
-
debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
|
|
1994
|
-
throw err;
|
|
1995
|
-
}
|
|
1996
|
-
debugLog(
|
|
1997
|
-
"[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
|
|
1998
|
-
method,
|
|
1999
|
-
"retryErrTag=",
|
|
2000
|
-
retryErr?._tag
|
|
2001
|
-
);
|
|
2002
|
-
throw retryErr;
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
|
|
2006
|
-
let lastErr = err;
|
|
2007
|
-
for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
|
|
2008
|
-
if (signal.aborted) throw lastErr;
|
|
2009
|
-
try {
|
|
2010
|
-
this._sessions.delete(resolvedConnectId);
|
|
2011
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2012
|
-
if (isDeviceDisconnectedError(lastErr)) {
|
|
2013
|
-
try {
|
|
2014
|
-
this.connector.reset?.();
|
|
2015
|
-
} catch {
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
const reConnectId = await this.ensureConnected(
|
|
2019
|
-
resolvedConnectId,
|
|
2020
|
-
signal,
|
|
2021
|
-
allowUsbEphemeralFallback
|
|
2022
|
-
);
|
|
2023
|
-
const reSessionId = this._sessions.get(reConnectId);
|
|
2024
|
-
if (!reSessionId) throw lastErr;
|
|
2025
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2026
|
-
const fp = await this._abortable(
|
|
2027
|
-
signal,
|
|
2028
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2029
|
-
reSessionId,
|
|
2030
|
-
fingerprint.deviceId,
|
|
2031
|
-
fingerprint.chain
|
|
2032
|
-
)
|
|
2033
|
-
);
|
|
2034
|
-
if (!fp.success) {
|
|
2035
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2036
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2037
|
-
});
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
return await this._callConnector(reSessionId, method, effectiveParams, signal);
|
|
2041
|
-
} catch (retryErr) {
|
|
2042
|
-
if (signal.aborted) throw retryErr;
|
|
2043
|
-
lastErr = retryErr;
|
|
2044
|
-
const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
|
|
2045
|
-
if (!canRetry) {
|
|
2046
|
-
throw retryErr;
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
2896
|
}
|
|
2050
|
-
throw lastErr;
|
|
2051
|
-
}
|
|
2052
|
-
if (isTimeoutError(err)) {
|
|
2053
|
-
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
2054
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2055
|
-
return this._retryWithFreshConnection(
|
|
2056
|
-
resolvedConnectId,
|
|
2057
|
-
method,
|
|
2058
|
-
effectiveParams,
|
|
2059
|
-
signal,
|
|
2060
|
-
err,
|
|
2061
|
-
fingerprint
|
|
2062
|
-
);
|
|
2063
|
-
}
|
|
2064
|
-
if (isConnectionLevelError(err)) {
|
|
2065
|
-
debugLog("[LedgerAdapter] connection-level fail-closed reset");
|
|
2066
|
-
this._sessions.delete(resolvedConnectId);
|
|
2067
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2068
|
-
this.connector.reset();
|
|
2069
|
-
const codeNum = err?.code;
|
|
2070
|
-
throw Object.assign(err, {
|
|
2071
|
-
code: codeNum ?? HardwareErrorCode3.DeviceDisconnected
|
|
2072
|
-
});
|
|
2073
2897
|
}
|
|
2074
2898
|
throw err;
|
|
2075
2899
|
}
|
|
2076
2900
|
}
|
|
2077
|
-
/**
|
|
2078
|
-
* Stuck-app recovery: pause for the device's UI transition, then retry once.
|
|
2079
|
-
*
|
|
2080
|
-
* Caller has already cleared the session + reset connector. We wait so Stax
|
|
2081
|
-
* finishes its post-CloseApp animation, then go through ensureConnected +
|
|
2082
|
-
* fingerprint check + call exactly once. Caller decides what to do on a
|
|
2083
|
-
* second stuck-app hit.
|
|
2084
|
-
*/
|
|
2085
|
-
async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2086
|
-
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2087
|
-
const retryConnectId = await this.ensureConnected(
|
|
2088
|
-
resolvedConnectId,
|
|
2089
|
-
signal,
|
|
2090
|
-
!!fingerprint?.deviceId && !fingerprint.skipFingerprint
|
|
2091
|
-
);
|
|
2092
|
-
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2093
|
-
if (!retrySessionId) throw originalErr;
|
|
2094
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2095
|
-
const fp = await this._abortable(
|
|
2096
|
-
signal,
|
|
2097
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2098
|
-
retrySessionId,
|
|
2099
|
-
fingerprint.deviceId,
|
|
2100
|
-
fingerprint.chain
|
|
2101
|
-
)
|
|
2102
|
-
);
|
|
2103
|
-
if (!fp.success) {
|
|
2104
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2105
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2106
|
-
});
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
return this._callConnector(retrySessionId, method, params, signal);
|
|
2110
|
-
}
|
|
2111
2901
|
_sleepAbortable(ms, signal) {
|
|
2112
2902
|
return new Promise((resolve, reject) => {
|
|
2113
2903
|
if (signal.aborted) {
|
|
@@ -2125,88 +2915,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2125
2915
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2126
2916
|
});
|
|
2127
2917
|
}
|
|
2128
|
-
/**
|
|
2129
|
-
* Clear stale session, reconnect, and retry the call.
|
|
2130
|
-
*
|
|
2131
|
-
* Timeout recovery starts with a full connector reset. After an APDU
|
|
2132
|
-
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
2133
|
-
* on the same DMK can poison the next chain switch.
|
|
2134
|
-
*/
|
|
2135
|
-
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2136
|
-
this.connector.reset();
|
|
2137
|
-
this._sessions.clear();
|
|
2138
|
-
this._discoveredDevices.clear();
|
|
2139
|
-
this._connectingPromise = null;
|
|
2140
|
-
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2141
|
-
const retryConnectId = await this.ensureConnected(
|
|
2142
|
-
targetConnectId,
|
|
2143
|
-
signal,
|
|
2144
|
-
allowUsbEphemeralFallback
|
|
2145
|
-
);
|
|
2146
|
-
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2147
|
-
if (!retrySessionId) {
|
|
2148
|
-
throw originalErr;
|
|
2149
|
-
}
|
|
2150
|
-
try {
|
|
2151
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2152
|
-
const fp = await this._abortable(
|
|
2153
|
-
signal,
|
|
2154
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2155
|
-
retrySessionId,
|
|
2156
|
-
fingerprint.deviceId,
|
|
2157
|
-
fingerprint.chain
|
|
2158
|
-
)
|
|
2159
|
-
);
|
|
2160
|
-
if (!fp.success) {
|
|
2161
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2162
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2163
|
-
});
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
return await this._callConnector(retrySessionId, method, params, signal);
|
|
2167
|
-
} catch (retryErr) {
|
|
2168
|
-
if (signal.aborted) throw retryErr;
|
|
2169
|
-
this.connector.reset();
|
|
2170
|
-
this._sessions.clear();
|
|
2171
|
-
this._discoveredDevices.clear();
|
|
2172
|
-
this._connectingPromise = null;
|
|
2173
|
-
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
2174
|
-
throw retryErr;
|
|
2175
|
-
}
|
|
2176
|
-
debugLog(
|
|
2177
|
-
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
2178
|
-
);
|
|
2179
|
-
this.connector.reset();
|
|
2180
|
-
this._sessions.clear();
|
|
2181
|
-
this._discoveredDevices.clear();
|
|
2182
|
-
this._connectingPromise = null;
|
|
2183
|
-
const finalConnectId = await this.ensureConnected(
|
|
2184
|
-
targetConnectId,
|
|
2185
|
-
signal,
|
|
2186
|
-
allowUsbEphemeralFallback
|
|
2187
|
-
);
|
|
2188
|
-
const finalSessionId = this._sessions.get(finalConnectId);
|
|
2189
|
-
if (!finalSessionId) {
|
|
2190
|
-
throw originalErr;
|
|
2191
|
-
}
|
|
2192
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2193
|
-
const fp = await this._abortable(
|
|
2194
|
-
signal,
|
|
2195
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2196
|
-
finalSessionId,
|
|
2197
|
-
fingerprint.deviceId,
|
|
2198
|
-
fingerprint.chain
|
|
2199
|
-
)
|
|
2200
|
-
);
|
|
2201
|
-
if (!fp.success) {
|
|
2202
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2203
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2204
|
-
});
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
return this._callConnector(finalSessionId, method, params, signal);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
2918
|
/**
|
|
2211
2919
|
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
2212
2920
|
*
|
|
@@ -2276,7 +2984,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2276
2984
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
2277
2985
|
const e = err;
|
|
2278
2986
|
const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
2279
|
-
return ledgerFailure(
|
|
2987
|
+
return ledgerFailure(
|
|
2988
|
+
e.code,
|
|
2989
|
+
e.message ?? "Unknown error",
|
|
2990
|
+
e.appName,
|
|
2991
|
+
tag,
|
|
2992
|
+
params,
|
|
2993
|
+
void 0,
|
|
2994
|
+
isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
|
|
2995
|
+
);
|
|
2280
2996
|
}
|
|
2281
2997
|
const mapped = mapLedgerError(err);
|
|
2282
2998
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -2303,7 +3019,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2303
3019
|
deviceId: device.deviceId,
|
|
2304
3020
|
connectId: device.connectId,
|
|
2305
3021
|
label: device.name,
|
|
2306
|
-
connectionType: this.connector.connectionType,
|
|
3022
|
+
connectionType: device.connectionType ?? this.connector.connectionType,
|
|
2307
3023
|
rssi: device.rssi,
|
|
2308
3024
|
isConnectable: device.isConnectable,
|
|
2309
3025
|
serialNumber: device.serialNumber,
|
|
@@ -2329,8 +3045,8 @@ var LedgerAdapter = _LedgerAdapter;
|
|
|
2329
3045
|
|
|
2330
3046
|
// src/connector/LedgerConnectorBase.ts
|
|
2331
3047
|
import {
|
|
2332
|
-
EConnectorInteraction as
|
|
2333
|
-
HardwareErrorCode as
|
|
3048
|
+
EConnectorInteraction as EConnectorInteraction3,
|
|
3049
|
+
HardwareErrorCode as HardwareErrorCode7,
|
|
2334
3050
|
isKnownNonTargetHardwareVendor,
|
|
2335
3051
|
serializeConnectorError
|
|
2336
3052
|
} from "@onekeyfe/hwk-adapter-core";
|
|
@@ -2603,7 +3319,7 @@ var LedgerDeviceManager = class {
|
|
|
2603
3319
|
|
|
2604
3320
|
// src/signer/SignerManager.ts
|
|
2605
3321
|
import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
|
|
2606
|
-
import { ContextModuleBuilder } from "@ledgerhq/context-module";
|
|
3322
|
+
import { ContextModuleBuilder, ContextModuleChainID } from "@ledgerhq/context-module";
|
|
2607
3323
|
|
|
2608
3324
|
// src/signer/SignerEth.ts
|
|
2609
3325
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
@@ -2869,7 +3585,7 @@ var SignerManager = class _SignerManager {
|
|
|
2869
3585
|
return (args) => new SignerEthBuilder(args);
|
|
2870
3586
|
}
|
|
2871
3587
|
static _createContextModule() {
|
|
2872
|
-
const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3588
|
+
const contextModule = new ContextModuleBuilder({}).setChain(ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
|
|
2873
3589
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2874
3590
|
}
|
|
2875
3591
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -3339,7 +4055,8 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
3339
4055
|
}
|
|
3340
4056
|
|
|
3341
4057
|
// src/connector/chains/sol.ts
|
|
3342
|
-
import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
|
|
4058
|
+
import { bytesToHex, hexToBytes as hexToBytes3, prepareSolanaOffchainMessageV1 } from "@onekeyfe/hwk-adapter-core";
|
|
4059
|
+
import bs58 from "bs58";
|
|
3343
4060
|
|
|
3344
4061
|
// src/signer/SignerSol.ts
|
|
3345
4062
|
var SignerSol = class {
|
|
@@ -3423,8 +4140,21 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3423
4140
|
const path = normalizePath(params.path);
|
|
3424
4141
|
const messageBytes = hexToBytes3(params.message);
|
|
3425
4142
|
try {
|
|
4143
|
+
if (params.messageVersion === 1) {
|
|
4144
|
+
const preparedMessage = prepareSolanaOffchainMessageV1({
|
|
4145
|
+
message: messageBytes,
|
|
4146
|
+
requiredSigners: params.requiredSigners
|
|
4147
|
+
});
|
|
4148
|
+
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4149
|
+
"@ledgerhq/device-signer-kit-solana"
|
|
4150
|
+
);
|
|
4151
|
+
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4152
|
+
version: SignMessageVersion.Raw
|
|
4153
|
+
});
|
|
4154
|
+
return { signature: decodeBase58Signature(result2.signature) };
|
|
4155
|
+
}
|
|
3426
4156
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
3427
|
-
return { signature: result.signature };
|
|
4157
|
+
return { signature: decodeBase58EnvelopeSignature(result.signature) };
|
|
3428
4158
|
} catch (err) {
|
|
3429
4159
|
ctx.invalidateSession(sessionId);
|
|
3430
4160
|
throw ctx.wrapError(err);
|
|
@@ -3432,11 +4162,27 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3432
4162
|
ctx.clearCanceller(sessionId);
|
|
3433
4163
|
}
|
|
3434
4164
|
}
|
|
4165
|
+
function decodeBase58Signature(signature) {
|
|
4166
|
+
const bytes = bs58.decode(signature);
|
|
4167
|
+
if (bytes.length !== 64) {
|
|
4168
|
+
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4169
|
+
}
|
|
4170
|
+
return bytesToHex(bytes);
|
|
4171
|
+
}
|
|
4172
|
+
function decodeBase58EnvelopeSignature(envelope) {
|
|
4173
|
+
const bytes = bs58.decode(envelope);
|
|
4174
|
+
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4175
|
+
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4176
|
+
}
|
|
4177
|
+
return bytesToHex(bytes.subarray(1, 65));
|
|
4178
|
+
}
|
|
3435
4179
|
async function _createSolSigner(ctx, sessionId) {
|
|
3436
4180
|
const dmk = await ctx.getOrCreateDmk();
|
|
3437
|
-
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit(
|
|
4181
|
+
const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
|
|
4182
|
+
"@ledgerhq/context-module"
|
|
4183
|
+
);
|
|
3438
4184
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
3439
|
-
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4185
|
+
const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
|
|
3440
4186
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
3441
4187
|
const signer = new SignerSol(sdkSigner);
|
|
3442
4188
|
signer.onInteraction = (interaction) => {
|
|
@@ -3452,361 +4198,192 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
3452
4198
|
return signer;
|
|
3453
4199
|
}
|
|
3454
4200
|
|
|
3455
|
-
// src/connector/chains/
|
|
3456
|
-
import {
|
|
3457
|
-
import Trx from "@ledgerhq/hw-app-trx";
|
|
4201
|
+
// src/connector/chains/zcash.ts
|
|
4202
|
+
import { bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
|
|
3458
4203
|
|
|
3459
|
-
// src/
|
|
3460
|
-
|
|
4204
|
+
// src/signer/SignerZcash.ts
|
|
4205
|
+
var SignerZcash = class {
|
|
4206
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4207
|
+
constructor(_sdk) {
|
|
4208
|
+
this._sdk = _sdk;
|
|
4209
|
+
}
|
|
4210
|
+
/** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
|
|
4211
|
+
async getFullViewingKey(derivationPath, options) {
|
|
4212
|
+
const action = this._sdk.getFullViewingKey(derivationPath, options);
|
|
4213
|
+
return deviceActionToPromise(
|
|
4214
|
+
action,
|
|
4215
|
+
this.onInteraction,
|
|
4216
|
+
void 0,
|
|
4217
|
+
this.onRegisterCanceller
|
|
4218
|
+
);
|
|
4219
|
+
}
|
|
4220
|
+
/** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
|
|
4221
|
+
async getShieldedAddress(derivationPath, options) {
|
|
4222
|
+
const action = this._sdk.getShieldedAddress(derivationPath, options);
|
|
4223
|
+
return deviceActionToPromise(
|
|
4224
|
+
action,
|
|
4225
|
+
this.onInteraction,
|
|
4226
|
+
void 0,
|
|
4227
|
+
this.onRegisterCanceller
|
|
4228
|
+
);
|
|
4229
|
+
}
|
|
4230
|
+
};
|
|
3461
4231
|
|
|
3462
|
-
// src/
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
ETH: "Ethereum",
|
|
3472
|
-
BTC: "Bitcoin",
|
|
3473
|
-
SOL: "Solana",
|
|
3474
|
-
TRX: "Tron",
|
|
3475
|
-
XRP: "XRP",
|
|
3476
|
-
ADA: "Cardano",
|
|
3477
|
-
DOT: "Polkadot",
|
|
3478
|
-
ATOM: "Cosmos"
|
|
3479
|
-
};
|
|
3480
|
-
var DASHBOARD_APP_NAME = "BOLOS";
|
|
3481
|
-
var AppManager = class {
|
|
3482
|
-
constructor(dmk, options) {
|
|
3483
|
-
this._dmk = dmk;
|
|
3484
|
-
this._waitMs = options?.waitMs ?? 1e3;
|
|
3485
|
-
this._maxRetries = options?.maxRetries ?? 10;
|
|
3486
|
-
}
|
|
3487
|
-
/**
|
|
3488
|
-
* Return the Ledger app name for a given chain ticker,
|
|
3489
|
-
* or undefined if the chain is not supported.
|
|
3490
|
-
*/
|
|
3491
|
-
static getAppName(chain) {
|
|
3492
|
-
return APP_NAME_MAP[chain];
|
|
3493
|
-
}
|
|
3494
|
-
/**
|
|
3495
|
-
* Ensure the target app is open on the device identified by `sessionId`.
|
|
3496
|
-
*
|
|
3497
|
-
* Flow:
|
|
3498
|
-
* 1. Check the currently running app.
|
|
3499
|
-
* 2. If it is already the target, return immediately.
|
|
3500
|
-
* 3. If a different app is running (not dashboard), close it first.
|
|
3501
|
-
* 4. Open the target app.
|
|
3502
|
-
* 5. Poll until the device confirms the target app is running.
|
|
3503
|
-
*/
|
|
3504
|
-
/**
|
|
3505
|
-
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
3506
|
-
* device is about to display "Open <app>" on screen and wait for the
|
|
3507
|
-
* user's button press. UI consumers should show their "open app" prompt
|
|
3508
|
-
* in response. NOT called when the target app is already open (no user
|
|
3509
|
-
* interaction needed in that case).
|
|
3510
|
-
*
|
|
3511
|
-
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
3512
|
-
* has physically confirmed on the device, so anything that runs AFTER
|
|
3513
|
-
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
3514
|
-
* Hence the callback must fire BEFORE that await.
|
|
3515
|
-
*/
|
|
3516
|
-
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
3517
|
-
const currentApp = await this._getCurrentApp(sessionId);
|
|
3518
|
-
if (currentApp === targetAppName) {
|
|
3519
|
-
return;
|
|
3520
|
-
}
|
|
3521
|
-
if (!this._isDashboard(currentApp)) {
|
|
3522
|
-
await this._closeCurrentApp(sessionId);
|
|
3523
|
-
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
3524
|
-
}
|
|
3525
|
-
onConfirmOnDevice?.();
|
|
3526
|
-
await this._openApp(sessionId, targetAppName);
|
|
3527
|
-
await this._waitForApp(sessionId, targetAppName);
|
|
3528
|
-
}
|
|
3529
|
-
// ---------------------------------------------------------------------------
|
|
3530
|
-
// Private helpers
|
|
3531
|
-
// ---------------------------------------------------------------------------
|
|
3532
|
-
async _getCurrentApp(sessionId) {
|
|
3533
|
-
const result = await this._dmk.sendCommand({
|
|
3534
|
-
sessionId,
|
|
3535
|
-
command: new GetAppAndVersionCommand()
|
|
3536
|
-
});
|
|
3537
|
-
if (isSuccessCommandResult(result)) {
|
|
3538
|
-
debugLog("[AppManager] currentApp:", result.data.name);
|
|
3539
|
-
return result.data.name;
|
|
3540
|
-
}
|
|
3541
|
-
const errResult = result;
|
|
3542
|
-
const dmkErr = errResult.error ?? {};
|
|
3543
|
-
const original = dmkErr.originalError;
|
|
3544
|
-
debugLog(
|
|
3545
|
-
"[AppManager] _getCurrentApp failed sessionId=",
|
|
3546
|
-
sessionId,
|
|
3547
|
-
"tag=",
|
|
3548
|
-
dmkErr._tag,
|
|
3549
|
-
"errorCode=",
|
|
3550
|
-
dmkErr.errorCode,
|
|
3551
|
-
"message=",
|
|
3552
|
-
dmkErr.message,
|
|
3553
|
-
"originalErrorMessage=",
|
|
3554
|
-
original?.message ?? String(original ?? "")
|
|
3555
|
-
);
|
|
3556
|
-
throw Object.assign(
|
|
3557
|
-
new Error(
|
|
3558
|
-
dmkErr.message ?? "Failed to get current app from device"
|
|
3559
|
-
),
|
|
3560
|
-
{
|
|
3561
|
-
_tag: dmkErr._tag,
|
|
3562
|
-
errorCode: dmkErr.errorCode,
|
|
3563
|
-
originalError: original
|
|
3564
|
-
}
|
|
3565
|
-
);
|
|
3566
|
-
}
|
|
3567
|
-
async _openApp(sessionId, appName) {
|
|
3568
|
-
const result = await this._dmk.sendCommand({
|
|
3569
|
-
sessionId,
|
|
3570
|
-
command: new OpenAppCommand({ appName })
|
|
3571
|
-
});
|
|
3572
|
-
if (!isSuccessCommandResult(result)) {
|
|
3573
|
-
const dmkErr = result.error;
|
|
3574
|
-
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
3575
|
-
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
3576
|
-
debugLog(
|
|
3577
|
-
"[AppManager] openApp failed:",
|
|
3578
|
-
appName,
|
|
3579
|
-
"errorCode:",
|
|
3580
|
-
errorCode,
|
|
3581
|
-
"tag:",
|
|
3582
|
-
dmkErr._tag
|
|
3583
|
-
);
|
|
3584
|
-
let code;
|
|
3585
|
-
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
3586
|
-
code = HardwareErrorCode6.AppNotInstalled;
|
|
3587
|
-
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
3588
|
-
code = HardwareErrorCode6.UserRejected;
|
|
3589
|
-
}
|
|
3590
|
-
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
3591
|
-
_tag: ERROR_TAG.OpenAppCommand,
|
|
3592
|
-
code,
|
|
3593
|
-
errorCode,
|
|
3594
|
-
statusCode: errorCode,
|
|
3595
|
-
appName,
|
|
3596
|
-
originalError: dmkErr
|
|
3597
|
-
});
|
|
4232
|
+
// src/connector/chains/zcash.ts
|
|
4233
|
+
async function zcashGetFullViewingKey(ctx, sessionId, params) {
|
|
4234
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4235
|
+
const path = normalizePath(params.path);
|
|
4236
|
+
const mode = params.mode ?? "ufvk";
|
|
4237
|
+
try {
|
|
4238
|
+
const result = await signer.getFullViewingKey(path, { mode });
|
|
4239
|
+
if (result.mode === "ufvk") {
|
|
4240
|
+
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
3598
4241
|
}
|
|
4242
|
+
return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
|
|
4243
|
+
} catch (err) {
|
|
4244
|
+
ctx.invalidateSession(sessionId);
|
|
4245
|
+
throw ctx.wrapError(err);
|
|
4246
|
+
} finally {
|
|
4247
|
+
ctx.clearCanceller(sessionId);
|
|
3599
4248
|
}
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
4249
|
+
}
|
|
4250
|
+
async function zcashGetShieldedAddress(ctx, sessionId, params) {
|
|
4251
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4252
|
+
const path = normalizePath(params.path);
|
|
4253
|
+
try {
|
|
4254
|
+
const result = await signer.getShieldedAddress(path, {
|
|
4255
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
3605
4256
|
});
|
|
4257
|
+
return { address: result.address, path: params.path };
|
|
4258
|
+
} catch (err) {
|
|
4259
|
+
ctx.invalidateSession(sessionId);
|
|
4260
|
+
throw ctx.wrapError(err);
|
|
4261
|
+
} finally {
|
|
4262
|
+
ctx.clearCanceller(sessionId);
|
|
3606
4263
|
}
|
|
3607
|
-
/**
|
|
3608
|
-
* Poll the device until the expected app is reported as running,
|
|
3609
|
-
* or throw after `_maxRetries` attempts.
|
|
3610
|
-
*/
|
|
3611
|
-
async _waitForApp(sessionId, expectedAppName) {
|
|
3612
|
-
let lastSeen = "";
|
|
3613
|
-
for (let i = 0; i < this._maxRetries; i++) {
|
|
3614
|
-
await this._wait();
|
|
3615
|
-
const current = await this._getCurrentApp(sessionId);
|
|
3616
|
-
lastSeen = current;
|
|
3617
|
-
if (current === expectedAppName) {
|
|
3618
|
-
return;
|
|
3619
|
-
}
|
|
3620
|
-
}
|
|
3621
|
-
debugLog(
|
|
3622
|
-
"[AppManager] waitForApp exhausted: expected=",
|
|
3623
|
-
expectedAppName,
|
|
3624
|
-
"lastSeen=",
|
|
3625
|
-
lastSeen
|
|
3626
|
-
);
|
|
3627
|
-
throw new Error(
|
|
3628
|
-
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
3629
|
-
);
|
|
3630
|
-
}
|
|
3631
|
-
_isDashboard(appName) {
|
|
3632
|
-
return appName === DASHBOARD_APP_NAME;
|
|
3633
|
-
}
|
|
3634
|
-
_wait() {
|
|
3635
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3636
|
-
}
|
|
3637
|
-
};
|
|
3638
|
-
|
|
3639
|
-
// src/connector/chains/legacyChainCall.ts
|
|
3640
|
-
function isLegacyWrongAppError(err, _appName) {
|
|
3641
|
-
return isWrongAppError(err);
|
|
3642
4264
|
}
|
|
3643
|
-
async function
|
|
3644
|
-
const
|
|
3645
|
-
|
|
3646
|
-
const
|
|
3647
|
-
|
|
4265
|
+
async function _createZcashSigner(ctx, sessionId) {
|
|
4266
|
+
const dmk = await ctx.getOrCreateDmk();
|
|
4267
|
+
const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
|
|
4268
|
+
const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
|
|
4269
|
+
const signer = new SignerZcash(sdkSigner);
|
|
4270
|
+
signer.onInteraction = (interaction) => {
|
|
4271
|
+
debugLog("[LedgerConnector] zcash.onInteraction:", interaction);
|
|
3648
4272
|
ctx.emit("ui-event", {
|
|
3649
|
-
type:
|
|
4273
|
+
type: collapseSignerInteraction(interaction),
|
|
3650
4274
|
payload: { sessionId }
|
|
3651
4275
|
});
|
|
3652
4276
|
};
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
ctx.emit("ui-event", {
|
|
3656
|
-
type: EConnectorInteraction3.InteractionComplete,
|
|
3657
|
-
payload: { sessionId }
|
|
3658
|
-
});
|
|
3659
|
-
openAppPromptShown = false;
|
|
3660
|
-
}
|
|
3661
|
-
};
|
|
3662
|
-
try {
|
|
3663
|
-
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3664
|
-
} catch (err) {
|
|
3665
|
-
debugLog(
|
|
3666
|
-
"[LegacyChainCall] pre-flight ensureAppOpen failed:",
|
|
3667
|
-
appName,
|
|
3668
|
-
err?.message
|
|
3669
|
-
);
|
|
3670
|
-
closeOpenAppUiIfShown();
|
|
3671
|
-
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3672
|
-
}
|
|
3673
|
-
const runOnce = async () => {
|
|
3674
|
-
let confirmEmitted = false;
|
|
3675
|
-
if (needsConfirmation) {
|
|
3676
|
-
ctx.emit("ui-event", {
|
|
3677
|
-
type: EConnectorInteraction3.ConfirmOnDevice,
|
|
3678
|
-
payload: { sessionId }
|
|
3679
|
-
});
|
|
3680
|
-
confirmEmitted = true;
|
|
3681
|
-
}
|
|
3682
|
-
try {
|
|
3683
|
-
return await action(sessionId);
|
|
3684
|
-
} finally {
|
|
3685
|
-
if (confirmEmitted || openAppPromptShown) {
|
|
3686
|
-
ctx.emit("ui-event", {
|
|
3687
|
-
type: EConnectorInteraction3.InteractionComplete,
|
|
3688
|
-
payload: { sessionId }
|
|
3689
|
-
});
|
|
3690
|
-
openAppPromptShown = false;
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
};
|
|
3694
|
-
try {
|
|
3695
|
-
return await runOnce();
|
|
3696
|
-
} catch (err) {
|
|
3697
|
-
if (!isLegacyWrongAppError(err, appName)) {
|
|
3698
|
-
debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
|
|
3699
|
-
ctx.invalidateSession(sessionId);
|
|
3700
|
-
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3701
|
-
}
|
|
3702
|
-
debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
|
|
3703
|
-
try {
|
|
3704
|
-
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3705
|
-
} catch (switchErr) {
|
|
3706
|
-
debugLog(
|
|
3707
|
-
"[LegacyChainCall] retry ensureAppOpen failed:",
|
|
3708
|
-
appName,
|
|
3709
|
-
switchErr?.message
|
|
3710
|
-
);
|
|
3711
|
-
closeOpenAppUiIfShown();
|
|
3712
|
-
throw ctx.wrapError(switchErr, { defaultAppName: appName });
|
|
3713
|
-
}
|
|
3714
|
-
ctx.clearAllSigners();
|
|
3715
|
-
const result = await runOnce();
|
|
3716
|
-
debugLog("[LegacyChainCall] retry succeeded:", appName);
|
|
3717
|
-
return result;
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
|
|
3721
|
-
const dmk = await ctx.getOrCreateDmk();
|
|
3722
|
-
const appManager = new AppManager(dmk);
|
|
3723
|
-
await appManager.ensureAppOpen(sessionId, appName, onPrompt);
|
|
4277
|
+
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
4278
|
+
return signer;
|
|
3724
4279
|
}
|
|
3725
4280
|
|
|
3726
|
-
// src/
|
|
3727
|
-
import
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
4281
|
+
// src/connector/chains/tron.ts
|
|
4282
|
+
import { HardwareErrorCode as HardwareErrorCode6, bytesToHex as bytesToHex3, hexToBytes as hexToBytes4 } from "@onekeyfe/hwk-adapter-core";
|
|
4283
|
+
|
|
4284
|
+
// src/signer/SignerTron.ts
|
|
4285
|
+
var SignerTron = class {
|
|
4286
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4287
|
+
constructor(_sdk) {
|
|
4288
|
+
this._sdk = _sdk;
|
|
3733
4289
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
}
|
|
3744
|
-
result.set(statusCode, data.length);
|
|
3745
|
-
return result;
|
|
4290
|
+
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4291
|
+
async getAddress(derivationPath, options) {
|
|
4292
|
+
const action = this._sdk.getAddress(derivationPath, options);
|
|
4293
|
+
const result = await deviceActionToPromise(
|
|
4294
|
+
action,
|
|
4295
|
+
this.onInteraction,
|
|
4296
|
+
void 0,
|
|
4297
|
+
this.onRegisterCanceller
|
|
4298
|
+
);
|
|
4299
|
+
return { address: result.address, publicKey: result.publicKey };
|
|
3746
4300
|
}
|
|
3747
|
-
|
|
4301
|
+
/** Sign a protobuf-encoded raw transaction. */
|
|
4302
|
+
async signTransaction(derivationPath, transaction, options) {
|
|
4303
|
+
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
4304
|
+
return deviceActionToPromise(
|
|
4305
|
+
action,
|
|
4306
|
+
this.onInteraction,
|
|
4307
|
+
void 0,
|
|
4308
|
+
this.onRegisterCanceller
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
/** Sign a personal message (TIP-191). */
|
|
4312
|
+
async signPersonalMessage(derivationPath, message, options) {
|
|
4313
|
+
const action = this._sdk.signPersonalMessage(derivationPath, message, options);
|
|
4314
|
+
return deviceActionToPromise(
|
|
4315
|
+
action,
|
|
4316
|
+
this.onInteraction,
|
|
4317
|
+
void 0,
|
|
4318
|
+
this.onRegisterCanceller
|
|
4319
|
+
);
|
|
3748
4320
|
}
|
|
3749
4321
|
};
|
|
3750
4322
|
|
|
3751
4323
|
// src/connector/chains/tron.ts
|
|
3752
4324
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4325
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3753
4326
|
const path = normalizePath(params.path);
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
{
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
const trx = await _createTrx(ctx, sid);
|
|
3766
|
-
const result = await trx.getAddress(path, showOnDevice);
|
|
3767
|
-
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
3768
|
-
}
|
|
3769
|
-
);
|
|
4327
|
+
try {
|
|
4328
|
+
const result = await tronSigner.getAddress(path, {
|
|
4329
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4330
|
+
});
|
|
4331
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
4332
|
+
} catch (err) {
|
|
4333
|
+
ctx.invalidateSession(sessionId);
|
|
4334
|
+
throw ctx.wrapError(err);
|
|
4335
|
+
} finally {
|
|
4336
|
+
ctx.clearCanceller(sessionId);
|
|
4337
|
+
}
|
|
3770
4338
|
}
|
|
3771
4339
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
3772
4340
|
if (!params.rawTxHex) {
|
|
3773
4341
|
throw Object.assign(
|
|
3774
4342
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
3775
|
-
{ code:
|
|
4343
|
+
{ code: HardwareErrorCode6.InvalidParams }
|
|
3776
4344
|
);
|
|
3777
4345
|
}
|
|
4346
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3778
4347
|
const path = normalizePath(params.path);
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
params.tokenSignatures ?? []
|
|
3789
|
-
);
|
|
3790
|
-
return { signature };
|
|
3791
|
-
}
|
|
3792
|
-
);
|
|
4348
|
+
try {
|
|
4349
|
+
const signature = await tronSigner.signTransaction(path, hexToBytes4(params.rawTxHex));
|
|
4350
|
+
return { signature: bytesToHex3(signature) };
|
|
4351
|
+
} catch (err) {
|
|
4352
|
+
ctx.invalidateSession(sessionId);
|
|
4353
|
+
throw ctx.wrapError(err);
|
|
4354
|
+
} finally {
|
|
4355
|
+
ctx.clearCanceller(sessionId);
|
|
4356
|
+
}
|
|
3793
4357
|
}
|
|
3794
4358
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4359
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3795
4360
|
const path = normalizePath(params.path);
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
);
|
|
4361
|
+
try {
|
|
4362
|
+
const signature = await tronSigner.signPersonalMessage(path, hexToBytes4(params.messageHex));
|
|
4363
|
+
return { signature: bytesToHex3(signature) };
|
|
4364
|
+
} catch (err) {
|
|
4365
|
+
ctx.invalidateSession(sessionId);
|
|
4366
|
+
throw ctx.wrapError(err);
|
|
4367
|
+
} finally {
|
|
4368
|
+
ctx.clearCanceller(sessionId);
|
|
4369
|
+
}
|
|
3806
4370
|
}
|
|
3807
|
-
async function
|
|
4371
|
+
async function _createTronSigner(ctx, sessionId) {
|
|
3808
4372
|
const dmk = await ctx.getOrCreateDmk();
|
|
3809
|
-
|
|
4373
|
+
const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
|
|
4374
|
+
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4375
|
+
const signer = new SignerTron(sdkSigner);
|
|
4376
|
+
signer.onInteraction = (interaction) => {
|
|
4377
|
+
debugLog("[LedgerConnector] tron.onInteraction:", interaction);
|
|
4378
|
+
ctx.emit("ui-event", {
|
|
4379
|
+
type: collapseSignerInteraction(interaction),
|
|
4380
|
+
payload: { sessionId }
|
|
4381
|
+
});
|
|
4382
|
+
};
|
|
4383
|
+
signer.onRegisterCanceller = (cancel) => {
|
|
4384
|
+
ctx.registerCanceller(sessionId, cancel);
|
|
4385
|
+
};
|
|
4386
|
+
return signer;
|
|
3810
4387
|
}
|
|
3811
4388
|
|
|
3812
4389
|
// src/device-apps/customActions.ts
|
|
@@ -3985,7 +4562,7 @@ var DeviceApps = class {
|
|
|
3985
4562
|
seTargetId: v.seTargetId,
|
|
3986
4563
|
mcuTargetId: v.mcuTargetId,
|
|
3987
4564
|
seVersion: v.seVersion,
|
|
3988
|
-
seFlagsHex:
|
|
4565
|
+
seFlagsHex: bytesToHex4(v.seFlags),
|
|
3989
4566
|
mcuSephVersion: v.mcuSephVersion,
|
|
3990
4567
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
3991
4568
|
hwVersion: v.hwVersion
|
|
@@ -4059,7 +4636,7 @@ var DeviceApps = class {
|
|
|
4059
4636
|
}
|
|
4060
4637
|
}
|
|
4061
4638
|
};
|
|
4062
|
-
function
|
|
4639
|
+
function bytesToHex4(bytes) {
|
|
4063
4640
|
if (!bytes) return "";
|
|
4064
4641
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4065
4642
|
}
|
|
@@ -4102,10 +4679,11 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4102
4679
|
evm: "Ethereum",
|
|
4103
4680
|
btc: "Bitcoin",
|
|
4104
4681
|
sol: "Solana",
|
|
4105
|
-
tron: "Tron"
|
|
4682
|
+
tron: "Tron",
|
|
4683
|
+
zcash: "Zcash"
|
|
4106
4684
|
};
|
|
4107
4685
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4108
|
-
Object.values(
|
|
4686
|
+
Object.values(HardwareErrorCode7).filter((value) => typeof value === "number")
|
|
4109
4687
|
);
|
|
4110
4688
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4111
4689
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4142,6 +4720,10 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4142
4720
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4143
4721
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4144
4722
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4723
|
+
case "@ledgerhq/device-signer-kit-tron":
|
|
4724
|
+
return import("@ledgerhq/device-signer-kit-tron");
|
|
4725
|
+
case "@ledgerhq/device-signer-kit-zcash":
|
|
4726
|
+
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4145
4727
|
case "@ledgerhq/context-module":
|
|
4146
4728
|
return import("@ledgerhq/context-module");
|
|
4147
4729
|
default:
|
|
@@ -4300,7 +4882,7 @@ var LedgerConnectorBase = class {
|
|
|
4300
4882
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
4301
4883
|
);
|
|
4302
4884
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
4303
|
-
err.code =
|
|
4885
|
+
err.code = HardwareErrorCode7.BlePairingTimeout;
|
|
4304
4886
|
reject(err);
|
|
4305
4887
|
}, HANG_CEILING_MS);
|
|
4306
4888
|
});
|
|
@@ -4333,7 +4915,7 @@ var LedgerConnectorBase = class {
|
|
|
4333
4915
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
4334
4916
|
);
|
|
4335
4917
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
4336
|
-
err.code =
|
|
4918
|
+
err.code = HardwareErrorCode7.DeviceNotFound;
|
|
4337
4919
|
throw err;
|
|
4338
4920
|
};
|
|
4339
4921
|
const doConnect = async (path) => {
|
|
@@ -4397,7 +4979,7 @@ var LedgerConnectorBase = class {
|
|
|
4397
4979
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
4398
4980
|
);
|
|
4399
4981
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
4400
|
-
wrapped.code =
|
|
4982
|
+
wrapped.code = HardwareErrorCode7.BlePairingTimeout;
|
|
4401
4983
|
wrapped.originalError = err;
|
|
4402
4984
|
throw wrapped;
|
|
4403
4985
|
}
|
|
@@ -4475,7 +5057,7 @@ var LedgerConnectorBase = class {
|
|
|
4475
5057
|
this._unwatchSessionState(sessionId);
|
|
4476
5058
|
this._signerManager?.invalidate(sessionId);
|
|
4477
5059
|
this._cancellers.get(sessionId)?.({
|
|
4478
|
-
code:
|
|
5060
|
+
code: HardwareErrorCode7.DeviceDisconnected,
|
|
4479
5061
|
tag: "DeviceDisconnected",
|
|
4480
5062
|
message: "Device disconnected"
|
|
4481
5063
|
});
|
|
@@ -4501,7 +5083,7 @@ var LedgerConnectorBase = class {
|
|
|
4501
5083
|
success: false,
|
|
4502
5084
|
error: serializeConnectorError(
|
|
4503
5085
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
4504
|
-
code:
|
|
5086
|
+
code: HardwareErrorCode7.DeviceAppStuck,
|
|
4505
5087
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
4506
5088
|
originalError: err
|
|
4507
5089
|
})
|
|
@@ -4513,7 +5095,7 @@ var LedgerConnectorBase = class {
|
|
|
4513
5095
|
success: false,
|
|
4514
5096
|
error: serializeConnectorError(
|
|
4515
5097
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
4516
|
-
code:
|
|
5098
|
+
code: HardwareErrorCode7.TransportError,
|
|
4517
5099
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
4518
5100
|
originalError: err
|
|
4519
5101
|
})
|
|
@@ -4559,6 +5141,11 @@ var LedgerConnectorBase = class {
|
|
|
4559
5141
|
return solSignTransaction(ctx, sessionId, params);
|
|
4560
5142
|
case "solSignMessage":
|
|
4561
5143
|
return solSignMessage(ctx, sessionId, params);
|
|
5144
|
+
// ZCASH
|
|
5145
|
+
case "zcashGetFullViewingKey":
|
|
5146
|
+
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5147
|
+
case "zcashGetShieldedAddress":
|
|
5148
|
+
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
4562
5149
|
// TRON
|
|
4563
5150
|
case "tronGetAddress":
|
|
4564
5151
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -4583,7 +5170,7 @@ var LedgerConnectorBase = class {
|
|
|
4583
5170
|
try {
|
|
4584
5171
|
return await apps.install(p.appName, ({ progress }) => {
|
|
4585
5172
|
ctx.emit("ui-event", {
|
|
4586
|
-
type:
|
|
5173
|
+
type: EConnectorInteraction3.AppInstallProgress,
|
|
4587
5174
|
payload: {
|
|
4588
5175
|
sessionId,
|
|
4589
5176
|
appName: p.appName,
|
|
@@ -4888,7 +5475,7 @@ var LedgerConnectorBase = class {
|
|
|
4888
5475
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
4889
5476
|
*/
|
|
4890
5477
|
_ctxForMethod(method) {
|
|
4891
|
-
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5478
|
+
const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
|
|
4892
5479
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
4893
5480
|
if (!defaultAppName) return this._ctx;
|
|
4894
5481
|
return {
|
|
@@ -4922,6 +5509,209 @@ var LedgerConnectorBase = class {
|
|
|
4922
5509
|
return error;
|
|
4923
5510
|
}
|
|
4924
5511
|
};
|
|
5512
|
+
|
|
5513
|
+
// src/transport/DmkTransport.ts
|
|
5514
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
5515
|
+
var DmkTransport = class extends Transport {
|
|
5516
|
+
constructor(dmk, sessionId) {
|
|
5517
|
+
super();
|
|
5518
|
+
this._dmk = dmk;
|
|
5519
|
+
this._sessionId = sessionId;
|
|
5520
|
+
}
|
|
5521
|
+
async exchange(apdu) {
|
|
5522
|
+
const response = await this._dmk.sendApdu({
|
|
5523
|
+
sessionId: this._sessionId,
|
|
5524
|
+
apdu: new Uint8Array(apdu)
|
|
5525
|
+
});
|
|
5526
|
+
const { data, statusCode } = response;
|
|
5527
|
+
const result = Buffer.alloc(data.length + 2);
|
|
5528
|
+
if (data.length > 0) {
|
|
5529
|
+
result.set(data, 0);
|
|
5530
|
+
}
|
|
5531
|
+
result.set(statusCode, data.length);
|
|
5532
|
+
return result;
|
|
5533
|
+
}
|
|
5534
|
+
async close() {
|
|
5535
|
+
}
|
|
5536
|
+
};
|
|
5537
|
+
|
|
5538
|
+
// src/app/AppManager.ts
|
|
5539
|
+
import {
|
|
5540
|
+
CloseAppCommand,
|
|
5541
|
+
GetAppAndVersionCommand,
|
|
5542
|
+
OpenAppCommand,
|
|
5543
|
+
isSuccessCommandResult
|
|
5544
|
+
} from "@ledgerhq/device-management-kit";
|
|
5545
|
+
import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
|
|
5546
|
+
var APP_NAME_MAP = {
|
|
5547
|
+
ETH: "Ethereum",
|
|
5548
|
+
BTC: "Bitcoin",
|
|
5549
|
+
SOL: "Solana",
|
|
5550
|
+
TRX: "Tron",
|
|
5551
|
+
ZEC: "Zcash",
|
|
5552
|
+
XRP: "XRP",
|
|
5553
|
+
ADA: "Cardano",
|
|
5554
|
+
DOT: "Polkadot",
|
|
5555
|
+
ATOM: "Cosmos"
|
|
5556
|
+
};
|
|
5557
|
+
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5558
|
+
var AppManager = class {
|
|
5559
|
+
constructor(dmk, options) {
|
|
5560
|
+
this._dmk = dmk;
|
|
5561
|
+
this._waitMs = options?.waitMs ?? 1e3;
|
|
5562
|
+
this._maxRetries = options?.maxRetries ?? 10;
|
|
5563
|
+
}
|
|
5564
|
+
/**
|
|
5565
|
+
* Return the Ledger app name for a given chain ticker,
|
|
5566
|
+
* or undefined if the chain is not supported.
|
|
5567
|
+
*/
|
|
5568
|
+
static getAppName(chain) {
|
|
5569
|
+
return APP_NAME_MAP[chain];
|
|
5570
|
+
}
|
|
5571
|
+
/**
|
|
5572
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5573
|
+
*
|
|
5574
|
+
* Flow:
|
|
5575
|
+
* 1. Check the currently running app.
|
|
5576
|
+
* 2. If it is already the target, return immediately.
|
|
5577
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
5578
|
+
* 4. Open the target app.
|
|
5579
|
+
* 5. Poll until the device confirms the target app is running.
|
|
5580
|
+
*/
|
|
5581
|
+
/**
|
|
5582
|
+
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5583
|
+
* device is about to display "Open <app>" on screen and wait for the
|
|
5584
|
+
* user's button press. UI consumers should show their "open app" prompt
|
|
5585
|
+
* in response. NOT called when the target app is already open (no user
|
|
5586
|
+
* interaction needed in that case).
|
|
5587
|
+
*
|
|
5588
|
+
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5589
|
+
* has physically confirmed on the device, so anything that runs AFTER
|
|
5590
|
+
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5591
|
+
* Hence the callback must fire BEFORE that await.
|
|
5592
|
+
*/
|
|
5593
|
+
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5594
|
+
const currentApp = await this._getCurrentApp(sessionId);
|
|
5595
|
+
if (currentApp === targetAppName) {
|
|
5596
|
+
return;
|
|
5597
|
+
}
|
|
5598
|
+
if (!this._isDashboard(currentApp)) {
|
|
5599
|
+
await this._closeCurrentApp(sessionId);
|
|
5600
|
+
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5601
|
+
}
|
|
5602
|
+
onConfirmOnDevice?.();
|
|
5603
|
+
await this._openApp(sessionId, targetAppName);
|
|
5604
|
+
await this._waitForApp(sessionId, targetAppName);
|
|
5605
|
+
}
|
|
5606
|
+
// ---------------------------------------------------------------------------
|
|
5607
|
+
// Private helpers
|
|
5608
|
+
// ---------------------------------------------------------------------------
|
|
5609
|
+
async _getCurrentApp(sessionId) {
|
|
5610
|
+
const result = await this._dmk.sendCommand({
|
|
5611
|
+
sessionId,
|
|
5612
|
+
command: new GetAppAndVersionCommand()
|
|
5613
|
+
});
|
|
5614
|
+
if (isSuccessCommandResult(result)) {
|
|
5615
|
+
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5616
|
+
return result.data.name;
|
|
5617
|
+
}
|
|
5618
|
+
const errResult = result;
|
|
5619
|
+
const dmkErr = errResult.error ?? {};
|
|
5620
|
+
const original = dmkErr.originalError;
|
|
5621
|
+
debugLog(
|
|
5622
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5623
|
+
sessionId,
|
|
5624
|
+
"tag=",
|
|
5625
|
+
dmkErr._tag,
|
|
5626
|
+
"errorCode=",
|
|
5627
|
+
dmkErr.errorCode,
|
|
5628
|
+
"message=",
|
|
5629
|
+
dmkErr.message,
|
|
5630
|
+
"originalErrorMessage=",
|
|
5631
|
+
original?.message ?? String(original ?? "")
|
|
5632
|
+
);
|
|
5633
|
+
throw Object.assign(
|
|
5634
|
+
new Error(
|
|
5635
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
5636
|
+
),
|
|
5637
|
+
{
|
|
5638
|
+
_tag: dmkErr._tag,
|
|
5639
|
+
errorCode: dmkErr.errorCode,
|
|
5640
|
+
originalError: original
|
|
5641
|
+
}
|
|
5642
|
+
);
|
|
5643
|
+
}
|
|
5644
|
+
async _openApp(sessionId, appName) {
|
|
5645
|
+
const result = await this._dmk.sendCommand({
|
|
5646
|
+
sessionId,
|
|
5647
|
+
command: new OpenAppCommand({ appName })
|
|
5648
|
+
});
|
|
5649
|
+
if (!isSuccessCommandResult(result)) {
|
|
5650
|
+
const dmkErr = result.error;
|
|
5651
|
+
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5652
|
+
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5653
|
+
debugLog(
|
|
5654
|
+
"[AppManager] openApp failed:",
|
|
5655
|
+
appName,
|
|
5656
|
+
"errorCode:",
|
|
5657
|
+
errorCode,
|
|
5658
|
+
"tag:",
|
|
5659
|
+
dmkErr._tag
|
|
5660
|
+
);
|
|
5661
|
+
let code;
|
|
5662
|
+
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5663
|
+
code = HardwareErrorCode8.AppNotInstalled;
|
|
5664
|
+
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5665
|
+
code = HardwareErrorCode8.UserRejected;
|
|
5666
|
+
}
|
|
5667
|
+
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5668
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
5669
|
+
code,
|
|
5670
|
+
errorCode,
|
|
5671
|
+
statusCode: errorCode,
|
|
5672
|
+
appName,
|
|
5673
|
+
originalError: dmkErr
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
async _closeCurrentApp(sessionId) {
|
|
5678
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
5679
|
+
await this._dmk.sendCommand({
|
|
5680
|
+
sessionId,
|
|
5681
|
+
command: new CloseAppCommand()
|
|
5682
|
+
});
|
|
5683
|
+
}
|
|
5684
|
+
/**
|
|
5685
|
+
* Poll the device until the expected app is reported as running,
|
|
5686
|
+
* or throw after `_maxRetries` attempts.
|
|
5687
|
+
*/
|
|
5688
|
+
async _waitForApp(sessionId, expectedAppName) {
|
|
5689
|
+
let lastSeen = "";
|
|
5690
|
+
for (let i = 0; i < this._maxRetries; i++) {
|
|
5691
|
+
await this._wait();
|
|
5692
|
+
const current = await this._getCurrentApp(sessionId);
|
|
5693
|
+
lastSeen = current;
|
|
5694
|
+
if (current === expectedAppName) {
|
|
5695
|
+
return;
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
debugLog(
|
|
5699
|
+
"[AppManager] waitForApp exhausted: expected=",
|
|
5700
|
+
expectedAppName,
|
|
5701
|
+
"lastSeen=",
|
|
5702
|
+
lastSeen
|
|
5703
|
+
);
|
|
5704
|
+
throw new Error(
|
|
5705
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5706
|
+
);
|
|
5707
|
+
}
|
|
5708
|
+
_isDashboard(appName) {
|
|
5709
|
+
return appName === DASHBOARD_APP_NAME;
|
|
5710
|
+
}
|
|
5711
|
+
_wait() {
|
|
5712
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5713
|
+
}
|
|
5714
|
+
};
|
|
4925
5715
|
export {
|
|
4926
5716
|
AppManager,
|
|
4927
5717
|
DmkTransport,
|
|
@@ -4932,6 +5722,7 @@ export {
|
|
|
4932
5722
|
SignerEth,
|
|
4933
5723
|
SignerManager,
|
|
4934
5724
|
SignerSol,
|
|
5725
|
+
SignerZcash,
|
|
4935
5726
|
debugLog,
|
|
4936
5727
|
deviceActionToPromise,
|
|
4937
5728
|
isDeviceLockedError,
|