@onekeyfe/hwk-ledger-adapter 1.2.3-alpha.8 → 1.2.3
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 +29 -86
- package/dist/index.d.ts +29 -86
- package/dist/index.js +800 -1755
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +798 -1773
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -14
package/dist/index.mjs
CHANGED
|
@@ -6,41 +6,30 @@ import {
|
|
|
6
6
|
DeviceJobQueue,
|
|
7
7
|
EConnectorInteraction,
|
|
8
8
|
HardwareErrorCode as HardwareErrorCode3,
|
|
9
|
-
OperationRegistry,
|
|
10
|
-
SDK,
|
|
11
9
|
TypedEventEmitter,
|
|
12
10
|
UI_REQUEST,
|
|
13
11
|
UI_REQUEST_PREEMPTED_TAG,
|
|
14
12
|
UiRequestRegistry,
|
|
15
|
-
canReplayHardwareMethodAfterTransportFailure,
|
|
16
13
|
createHwkError,
|
|
17
14
|
deriveDeviceFingerprint,
|
|
18
15
|
failure as failure2,
|
|
19
|
-
isHardwareOperationId,
|
|
20
|
-
isHwkRecoveryHint,
|
|
21
|
-
operationMayHaveCompletedParams,
|
|
22
16
|
rehydrateConnectorError,
|
|
23
|
-
requestBleDeviceSelection,
|
|
24
|
-
requestSaveDeviceBinding,
|
|
25
|
-
resolveSearchTargetReusePolicy,
|
|
26
17
|
success
|
|
27
18
|
} from "@onekeyfe/hwk-adapter-core";
|
|
28
19
|
|
|
29
20
|
// src/errors.ts
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
21
|
+
import { HardwareErrorCode, enrichErrorMessage } from "@onekeyfe/hwk-adapter-core";
|
|
22
|
+
var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
|
|
23
|
+
function createMultipleUsbLedgerDevicesError() {
|
|
24
|
+
return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
|
|
25
|
+
code: HardwareErrorCode.DeviceOneDeviceOnly
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function ledgerFailure(code, error, appName, tag, params) {
|
|
37
29
|
const payload = { error, code };
|
|
38
30
|
if (appName !== void 0) payload.appName = appName;
|
|
39
31
|
if (tag !== void 0) payload._tag = tag;
|
|
40
32
|
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);
|
|
44
33
|
return { success: false, payload };
|
|
45
34
|
}
|
|
46
35
|
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
@@ -181,12 +170,7 @@ var ERROR_TAG = {
|
|
|
181
170
|
UnknownDevice: "UnknownDeviceError",
|
|
182
171
|
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
183
172
|
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
184
|
-
|
|
185
|
-
// "ConnectionOpeningError" — and its typings widen `_tag` to `string`, which
|
|
186
|
-
// is why the class name looked authoritative. Both spellings are kept so a
|
|
187
|
-
// DMK version that aligns them does not silently drop back to UnknownError.
|
|
188
|
-
OpeningConnection: "ConnectionOpeningError",
|
|
189
|
-
OpeningConnectionLegacy: "OpeningConnectionError",
|
|
173
|
+
OpeningConnection: "OpeningConnectionError",
|
|
190
174
|
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
191
175
|
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
192
176
|
Disconnect: "DisconnectError",
|
|
@@ -199,18 +183,7 @@ var ERROR_TAG = {
|
|
|
199
183
|
// DMK remote-network failures (manager-api HTTP / secure-channel WS).
|
|
200
184
|
WebSocketConnection: "WebSocketConnectionError",
|
|
201
185
|
HttpFetch: "FetchError",
|
|
202
|
-
|
|
203
|
-
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
|
|
204
|
-
ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
|
|
205
|
-
// DMK OS/secure-channel device actions. `SecureChannelError` is the residual
|
|
206
|
-
// bucket left by SecureChannelError.mapInstallDAErrors() after the locked /
|
|
207
|
-
// refused / already-installed / OOM cases have been split out, so it means
|
|
208
|
-
// "the relay itself broke", not "the device answered".
|
|
209
|
-
SecureChannel: "SecureChannelError",
|
|
210
|
-
RefusedByUserDA: "RefusedByUserDAError",
|
|
211
|
-
AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
|
|
212
|
-
OutOfMemoryDA: "OutOfMemoryDAError",
|
|
213
|
-
DeviceNotOnboarded: "DeviceNotOnboardedError"
|
|
186
|
+
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError"
|
|
214
187
|
};
|
|
215
188
|
function isDeviceLockedError(err) {
|
|
216
189
|
if (!err || typeof err !== "object") return false;
|
|
@@ -240,6 +213,7 @@ function isBlePairingFailureError(err) {
|
|
|
240
213
|
return false;
|
|
241
214
|
}
|
|
242
215
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
216
|
+
ERROR_TAG.DeviceLocked,
|
|
243
217
|
ERROR_TAG.DeviceNotAdvertising,
|
|
244
218
|
ERROR_TAG.BlePairingTimeout,
|
|
245
219
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -251,7 +225,6 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
|
251
225
|
ERROR_TAG.DeviceSessionRefresher,
|
|
252
226
|
ERROR_TAG.DeviceNotInitialized,
|
|
253
227
|
ERROR_TAG.OpeningConnection,
|
|
254
|
-
ERROR_TAG.OpeningConnectionLegacy,
|
|
255
228
|
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
256
229
|
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
257
230
|
ERROR_TAG.Disconnect,
|
|
@@ -266,10 +239,7 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
|
266
239
|
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
267
240
|
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
268
241
|
]);
|
|
269
|
-
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
270
|
-
ERROR_TAG.OpeningConnection,
|
|
271
|
-
ERROR_TAG.OpeningConnectionLegacy
|
|
272
|
-
]);
|
|
242
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
|
|
273
243
|
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
274
244
|
ERROR_TAG.DeviceNotRecognized,
|
|
275
245
|
ERROR_TAG.DeviceSessionNotFound,
|
|
@@ -292,13 +262,6 @@ function isConnectionLevelError(err) {
|
|
|
292
262
|
function isKnownConnectionTag(tag) {
|
|
293
263
|
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
294
264
|
}
|
|
295
|
-
var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
|
|
296
|
-
ERROR_TAG.OpeningConnection,
|
|
297
|
-
ERROR_TAG.OpeningConnectionLegacy
|
|
298
|
-
]);
|
|
299
|
-
function isConnectionOpeningTag(tag) {
|
|
300
|
-
return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
|
|
301
|
-
}
|
|
302
265
|
function hasStatusCode(err, codeSet) {
|
|
303
266
|
if (!err || typeof err !== "object") return false;
|
|
304
267
|
const e = err;
|
|
@@ -334,14 +297,6 @@ function hasInvalidArgumentCode(err) {
|
|
|
334
297
|
if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
|
|
335
298
|
return false;
|
|
336
299
|
}
|
|
337
|
-
function hasErrorTag(err, tags) {
|
|
338
|
-
if (!err || typeof err !== "object") return false;
|
|
339
|
-
const e = err;
|
|
340
|
-
if (typeof e._tag === "string" && tags.has(e._tag)) return true;
|
|
341
|
-
if (e.originalError != null && hasErrorTag(e.originalError, tags)) return true;
|
|
342
|
-
if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
300
|
function isDeviceNotFoundError(err) {
|
|
346
301
|
if (!err || typeof err !== "object") return false;
|
|
347
302
|
const tag = err._tag;
|
|
@@ -360,14 +315,10 @@ function isDeviceBusyError(err) {
|
|
|
360
315
|
if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
|
|
361
316
|
return false;
|
|
362
317
|
}
|
|
363
|
-
var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
|
|
364
|
-
ERROR_TAG.UserRefusedOnDevice,
|
|
365
|
-
ERROR_TAG.RefusedByUserDA
|
|
366
|
-
]);
|
|
367
318
|
function isUserRejectedError(err) {
|
|
368
319
|
if (!err || typeof err !== "object") return false;
|
|
369
320
|
const e = err;
|
|
370
|
-
if (
|
|
321
|
+
if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
|
|
371
322
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
372
323
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
373
324
|
return false;
|
|
@@ -398,37 +349,15 @@ function isAppNotInstalledError(err) {
|
|
|
398
349
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
399
350
|
return false;
|
|
400
351
|
}
|
|
401
|
-
var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
|
|
402
352
|
function isOutOfMemoryError(err) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
ERROR_TAG.AppAlreadyInstalledDA
|
|
407
|
-
]);
|
|
408
|
-
function isAppAlreadyInstalledError(err) {
|
|
409
|
-
return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
|
|
410
|
-
}
|
|
411
|
-
var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([
|
|
412
|
-
ERROR_TAG.DeviceNotOnboarded
|
|
413
|
-
]);
|
|
414
|
-
function isDeviceNotOnboardedError(err) {
|
|
415
|
-
return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
|
|
416
|
-
}
|
|
417
|
-
var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
|
|
418
|
-
ERROR_TAG.InvalidFirmwareMetadataResponse,
|
|
419
|
-
ERROR_TAG.ApplicationsMetadataTask
|
|
420
|
-
]);
|
|
421
|
-
function isFirmwareMetadataError(err) {
|
|
422
|
-
return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
|
|
423
|
-
}
|
|
424
|
-
var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
|
|
425
|
-
function isSecureChannelError(err) {
|
|
426
|
-
return hasErrorTag(err, SECURE_CHANNEL_TAGS);
|
|
353
|
+
if (!err || typeof err !== "object") return false;
|
|
354
|
+
const e = err;
|
|
355
|
+
return e._tag === "OutOfMemoryDAError";
|
|
427
356
|
}
|
|
428
357
|
var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
|
|
429
358
|
ERROR_TAG.WebSocketConnection,
|
|
430
359
|
ERROR_TAG.HttpFetch,
|
|
431
|
-
ERROR_TAG.
|
|
360
|
+
ERROR_TAG.InvalidFirmwareMetadataResponse
|
|
432
361
|
]);
|
|
433
362
|
function isNetworkError(err) {
|
|
434
363
|
if (!err || typeof err !== "object") return false;
|
|
@@ -478,34 +407,23 @@ function isTransportStuck(err) {
|
|
|
478
407
|
function isStuckAppStateError(err) {
|
|
479
408
|
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
480
409
|
}
|
|
481
|
-
var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
|
|
482
|
-
function nestedErrorMessage(nested) {
|
|
483
|
-
if (!nested || typeof nested !== "object") return void 0;
|
|
484
|
-
const { message } = nested;
|
|
485
|
-
if (typeof message !== "string") return void 0;
|
|
486
|
-
const trimmed = message.trim();
|
|
487
|
-
if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
|
|
488
|
-
return message;
|
|
489
|
-
}
|
|
490
410
|
function mapLedgerError(err, opts) {
|
|
491
411
|
let originalMessage = "Unknown Ledger error";
|
|
492
412
|
if (err instanceof Error) {
|
|
493
413
|
originalMessage = err.message;
|
|
494
414
|
} else if (err && typeof err === "object") {
|
|
495
415
|
const e = err;
|
|
496
|
-
originalMessage = String(
|
|
497
|
-
e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
|
|
498
|
-
);
|
|
416
|
+
originalMessage = String(e.message ?? e._tag ?? e.type ?? JSON.stringify(err));
|
|
499
417
|
}
|
|
500
418
|
let code;
|
|
501
419
|
if (isDeviceLockedError(err)) {
|
|
502
420
|
code = HardwareErrorCode.DeviceLocked;
|
|
503
421
|
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
504
422
|
code = HardwareErrorCode.DeviceNotFound;
|
|
505
|
-
} else if (isBlePairingFailureError(err)) {
|
|
506
|
-
code = HardwareErrorCode.BlePairingTimeout;
|
|
507
423
|
} else if (isDeviceBusyError(err)) {
|
|
508
424
|
code = HardwareErrorCode.DeviceBusy;
|
|
425
|
+
} else if (isBlePairingFailureError(err)) {
|
|
426
|
+
code = HardwareErrorCode.BlePairingTimeout;
|
|
509
427
|
} else if (isUserAbortedError(err)) {
|
|
510
428
|
code = HardwareErrorCode.UserAborted;
|
|
511
429
|
} else if (isUserRejectedError(err)) {
|
|
@@ -514,18 +432,10 @@ function mapLedgerError(err, opts) {
|
|
|
514
432
|
code = HardwareErrorCode.WrongApp;
|
|
515
433
|
} else if (isAppNotInstalledError(err)) {
|
|
516
434
|
code = HardwareErrorCode.AppNotInstalled;
|
|
517
|
-
} else if (isAppAlreadyInstalledError(err)) {
|
|
518
|
-
code = HardwareErrorCode.AppAlreadyInstalled;
|
|
519
435
|
} else if (isOutOfMemoryError(err)) {
|
|
520
436
|
code = HardwareErrorCode.DeviceOutOfMemory;
|
|
521
|
-
} else if (isDeviceNotOnboardedError(err)) {
|
|
522
|
-
code = HardwareErrorCode.DeviceNotInitialized;
|
|
523
|
-
} else if (isFirmwareMetadataError(err)) {
|
|
524
|
-
code = HardwareErrorCode.LedgerFirmwareMetadataError;
|
|
525
437
|
} else if (isNetworkError(err)) {
|
|
526
438
|
code = HardwareErrorCode.NetworkError;
|
|
527
|
-
} else if (isSecureChannelError(err)) {
|
|
528
|
-
code = HardwareErrorCode.LedgerSecureChannelError;
|
|
529
439
|
} else if (isDeviceDisconnectedError(err)) {
|
|
530
440
|
code = HardwareErrorCode.DeviceDisconnected;
|
|
531
441
|
} else if (isTimeoutError(err)) {
|
|
@@ -538,32 +448,11 @@ function mapLedgerError(err, opts) {
|
|
|
538
448
|
}
|
|
539
449
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
540
450
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
541
|
-
return {
|
|
542
|
-
code,
|
|
543
|
-
message: enrichErrorMessage(code, originalMessage),
|
|
544
|
-
origin: defaultOriginForCode(code),
|
|
545
|
-
appName
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// src/utils/queueKey.ts
|
|
550
|
-
var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
|
|
551
|
-
function ledgerQueueKey({
|
|
552
|
-
operationId,
|
|
553
|
-
connectId
|
|
554
|
-
}) {
|
|
555
|
-
return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
|
|
451
|
+
return { code, message: enrichErrorMessage(code, originalMessage), appName };
|
|
556
452
|
}
|
|
557
453
|
|
|
558
454
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
559
|
-
import {
|
|
560
|
-
HardwareErrorCode as HardwareErrorCode2,
|
|
561
|
-
failure,
|
|
562
|
-
isConnectionLost,
|
|
563
|
-
isUserRefusal,
|
|
564
|
-
resolveHardwareOperationTarget,
|
|
565
|
-
runAllNetworkGetAddress
|
|
566
|
-
} from "@onekeyfe/hwk-adapter-core";
|
|
455
|
+
import { HardwareErrorCode as HardwareErrorCode2, failure, runAllNetworkGetAddress } from "@onekeyfe/hwk-adapter-core";
|
|
567
456
|
|
|
568
457
|
// src/utils/sdkEventBus.ts
|
|
569
458
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -620,102 +509,65 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
620
509
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
621
510
|
function createAllNetworkGetAddress({
|
|
622
511
|
callChain,
|
|
623
|
-
getChainFingerprint
|
|
624
|
-
retainOperation,
|
|
625
|
-
errorToFailure,
|
|
626
|
-
createCancelScope
|
|
512
|
+
getChainFingerprint
|
|
627
513
|
}) {
|
|
628
514
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
629
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
630
|
-
method: "allNetworkGetAddress",
|
|
631
|
-
connectId,
|
|
632
|
-
itemCount: params.bundle.length
|
|
633
|
-
});
|
|
634
|
-
const target = resolveHardwareOperationTarget(connectId, params.operationId, "ledger");
|
|
635
|
-
if (!target.success) return target;
|
|
636
|
-
const effectiveTargetId = target.payload.targetId ?? "";
|
|
637
|
-
let releaseOperationRetention;
|
|
638
|
-
try {
|
|
639
|
-
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
640
|
-
} catch (error) {
|
|
641
|
-
return errorToFailure(error);
|
|
642
|
-
}
|
|
643
|
-
const cancelScope = createCancelScope(
|
|
644
|
-
ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
645
|
-
);
|
|
515
|
+
debugLog("[LedgerAdapter][REQ]", { method: "allNetworkGetAddress", connectId, params });
|
|
646
516
|
const installContext = {};
|
|
647
517
|
const commonParams = {
|
|
648
|
-
autoInstallApp: params.autoInstallApp
|
|
649
|
-
operationId: target.payload.operationId,
|
|
650
|
-
knownConnections: params.knownConnections,
|
|
651
|
-
extra: params.extra,
|
|
652
|
-
allowDeviceSelection: params.allowDeviceSelection
|
|
518
|
+
autoInstallApp: params.autoInstallApp
|
|
653
519
|
};
|
|
654
520
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
itemDeviceId,
|
|
669
|
-
method,
|
|
670
|
-
item,
|
|
671
|
-
commonParams,
|
|
672
|
-
installContext
|
|
673
|
-
);
|
|
674
|
-
},
|
|
675
|
-
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
676
|
-
getChainFingerprint,
|
|
677
|
-
effectiveTargetId,
|
|
521
|
+
const result = await runAllNetworkGetAddress({
|
|
522
|
+
connectId,
|
|
523
|
+
deviceId: _deviceId,
|
|
524
|
+
params,
|
|
525
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
526
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
527
|
+
callItem: async ({ method, chain, item }) => {
|
|
528
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
529
|
+
return callAllNetworkMethod(
|
|
530
|
+
callChain,
|
|
531
|
+
connectId,
|
|
532
|
+
itemDeviceId,
|
|
533
|
+
method,
|
|
678
534
|
item,
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
535
|
+
commonParams,
|
|
536
|
+
installContext
|
|
537
|
+
);
|
|
538
|
+
},
|
|
539
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
540
|
+
getChainFingerprint,
|
|
541
|
+
connectId,
|
|
542
|
+
item,
|
|
543
|
+
chain,
|
|
544
|
+
payload,
|
|
545
|
+
chainFingerprints
|
|
546
|
+
),
|
|
547
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
548
|
+
buildTopLevelFailure: (response) => {
|
|
549
|
+
const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
|
|
550
|
+
return failure(
|
|
551
|
+
code,
|
|
552
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
553
|
+
response.payload?.params
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
558
|
+
method: "allNetworkGetAddress",
|
|
559
|
+
success: result.success,
|
|
560
|
+
payload: result
|
|
561
|
+
});
|
|
562
|
+
return result;
|
|
705
563
|
};
|
|
706
564
|
}
|
|
707
|
-
function buildCancelledFailure(signal) {
|
|
708
|
-
const reason = signal.reason;
|
|
709
|
-
const code = typeof reason?.code === "number" ? reason.code : HardwareErrorCode2.UserAborted;
|
|
710
|
-
const message = typeof reason?.message === "string" ? reason.message : "";
|
|
711
|
-
return failure(code, message || "All-network get-address cancelled");
|
|
712
|
-
}
|
|
713
565
|
function isTopLevelAllNetworkFailure(response) {
|
|
714
566
|
if (response.success) {
|
|
715
567
|
return false;
|
|
716
568
|
}
|
|
717
569
|
const code = response.payload?.code;
|
|
718
|
-
return code === HardwareErrorCode2.DeviceMismatch ||
|
|
570
|
+
return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
|
|
719
571
|
}
|
|
720
572
|
function getItemDeviceId(item) {
|
|
721
573
|
const { deviceId } = item;
|
|
@@ -745,18 +597,8 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
745
597
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
746
598
|
return coin ? { ...item, coin } : item;
|
|
747
599
|
}
|
|
748
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints
|
|
749
|
-
const
|
|
750
|
-
if (!knownFingerprint && cancelSignal.aborted) {
|
|
751
|
-
const cancelled = buildCancelledFailure(cancelSignal);
|
|
752
|
-
return { ...item, success: false, payload: cancelled.payload };
|
|
753
|
-
}
|
|
754
|
-
const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
|
|
755
|
-
getChainFingerprint,
|
|
756
|
-
context.connection?.connectId ?? connectId,
|
|
757
|
-
chain,
|
|
758
|
-
context
|
|
759
|
-
);
|
|
600
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
601
|
+
const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(getChainFingerprint, connectId, chain);
|
|
760
602
|
if (!fingerprint) {
|
|
761
603
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
762
604
|
}
|
|
@@ -777,8 +619,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
777
619
|
}
|
|
778
620
|
};
|
|
779
621
|
}
|
|
780
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain
|
|
781
|
-
const response = await getChainFingerprint(connectId, chain
|
|
622
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
623
|
+
const response = await getChainFingerprint(connectId, chain);
|
|
782
624
|
return response.success ? response.payload : "";
|
|
783
625
|
}
|
|
784
626
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -882,32 +724,9 @@ function btcAccountIndexFromPath(path) {
|
|
|
882
724
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
883
725
|
constructor(connector, options) {
|
|
884
726
|
this.vendor = "ledger";
|
|
885
|
-
// A cancel releases the DMK device action and its intent-queue slot, so the
|
|
886
|
-
// next call is not blocked behind it. It still cannot retract a confirmation
|
|
887
|
-
// screen the device is already showing.
|
|
888
|
-
this.cancelCapability = "stops-waiting";
|
|
889
727
|
this.emitter = new TypedEventEmitter();
|
|
890
|
-
this._operations = new OperationRegistry({
|
|
891
|
-
vendor: "ledger",
|
|
892
|
-
onEnded: (operation, reason) => {
|
|
893
|
-
const binding = this._pendingOperationBindings.get(operation.operationId);
|
|
894
|
-
if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
|
|
895
|
-
this._finishBleBinding("cancelled");
|
|
896
|
-
}
|
|
897
|
-
this._pendingOperationBindings.delete(operation.operationId);
|
|
898
|
-
this.emitter.emit(SDK.OPERATION_ENDED, {
|
|
899
|
-
type: SDK.OPERATION_ENDED,
|
|
900
|
-
payload: { operationId: operation.operationId, reason }
|
|
901
|
-
});
|
|
902
|
-
if (reason === "timeout") {
|
|
903
|
-
void this._releaseOperationConnection(operation);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
});
|
|
907
728
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
908
729
|
this._sessions = /* @__PURE__ */ new Map();
|
|
909
|
-
this._pendingOperationBindings = /* @__PURE__ */ new Map();
|
|
910
|
-
this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
911
730
|
this._uiRegistry = new UiRequestRegistry();
|
|
912
731
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
913
732
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -921,31 +740,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
921
740
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
922
741
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
923
742
|
this._doConnectAbortController = null;
|
|
924
|
-
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
925
|
-
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
926
|
-
this._resetPromise = null;
|
|
927
|
-
this._stateGeneration = 0;
|
|
928
|
-
this._connectorTeardownTail = Promise.resolve();
|
|
929
|
-
this._pendingConnectorTeardowns = 0;
|
|
930
|
-
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
931
|
-
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
932
743
|
this._installProgressLastEmittedValue = -Infinity;
|
|
933
744
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
934
745
|
callChain: this.callChain.bind(this),
|
|
935
|
-
getChainFingerprint:
|
|
936
|
-
try {
|
|
937
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
938
|
-
chain,
|
|
939
|
-
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
940
|
-
);
|
|
941
|
-
return success(fingerprint);
|
|
942
|
-
} catch (error) {
|
|
943
|
-
return this.errorToFailure(error);
|
|
944
|
-
}
|
|
945
|
-
},
|
|
946
|
-
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
947
|
-
errorToFailure: (error) => this.errorToFailure(error),
|
|
948
|
-
createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
|
|
746
|
+
getChainFingerprint: (connectId, chain) => this.getChainFingerprint(connectId, "", chain)
|
|
949
747
|
});
|
|
950
748
|
// ---------------------------------------------------------------------------
|
|
951
749
|
// Private helpers
|
|
@@ -955,7 +753,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
955
753
|
*
|
|
956
754
|
* - If a session already exists for the given connectId, reuse it.
|
|
957
755
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
958
|
-
* - Otherwise: search →
|
|
756
|
+
* - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
|
|
959
757
|
*/
|
|
960
758
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
961
759
|
this._connectingPromise = null;
|
|
@@ -972,12 +770,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
972
770
|
});
|
|
973
771
|
};
|
|
974
772
|
this.deviceDisconnectHandler = (data) => {
|
|
975
|
-
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
976
|
-
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
977
|
-
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
978
|
-
} else {
|
|
979
|
-
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
980
|
-
}
|
|
981
773
|
this._discoveredDevices.delete(data.connectId);
|
|
982
774
|
this._sessions.delete(data.connectId);
|
|
983
775
|
this.emitter.emit(DEVICE.DISCONNECT, {
|
|
@@ -1034,28 +826,11 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1034
826
|
this._jobQueue = new DeviceJobQueue();
|
|
1035
827
|
this.registerEventListeners();
|
|
1036
828
|
}
|
|
1037
|
-
_finishBleBinding(status) {
|
|
1038
|
-
const selectionRequestId = this._bindingSelectionRequestId;
|
|
1039
|
-
this._bindingSelectionRequestId = void 0;
|
|
1040
|
-
if (selectionRequestId)
|
|
1041
|
-
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
1042
|
-
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
1043
|
-
payload: { selectionRequestId, status }
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
_isBleConnection() {
|
|
1047
|
-
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
1048
|
-
}
|
|
1049
829
|
// Transport
|
|
1050
830
|
get activeTransport() {
|
|
1051
|
-
return this.
|
|
831
|
+
return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
|
|
1052
832
|
}
|
|
1053
833
|
getAvailableTransports() {
|
|
1054
|
-
if (this.connector.availableTransports) {
|
|
1055
|
-
return this.connector.availableTransports.map(
|
|
1056
|
-
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
834
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
1060
835
|
}
|
|
1061
836
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -1072,39 +847,21 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1072
847
|
* The next operation will re-discover and re-connect automatically.
|
|
1073
848
|
*/
|
|
1074
849
|
resetState() {
|
|
1075
|
-
void this._resetStateAndDisconnectSessions();
|
|
1076
|
-
}
|
|
1077
|
-
_resetStateAndDisconnectSessions() {
|
|
1078
|
-
if (this._resetPromise) return this._resetPromise;
|
|
1079
|
-
const sessionIds = new Set(this._sessions.values());
|
|
1080
|
-
this._stateGeneration += 1;
|
|
1081
|
-
this._finishBleBinding("cancelled");
|
|
1082
|
-
this._operations.endAll("runtime-reset");
|
|
1083
|
-
this._doConnectAbortController?.abort();
|
|
1084
850
|
this._discoveredDevices.clear();
|
|
1085
851
|
this._sessions.clear();
|
|
1086
|
-
this._verifiedBleReconnectTargets.clear();
|
|
1087
852
|
this._connectingPromise = null;
|
|
1088
|
-
this._doConnectAbortController = null;
|
|
1089
853
|
this._uiRegistry.reset();
|
|
1090
854
|
this._jobQueue.clear();
|
|
1091
855
|
this._btcHighIndexConfirmedThisSession = false;
|
|
1092
|
-
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1093
|
-
for (const sessionId of sessionIds) {
|
|
1094
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
1095
|
-
}
|
|
1096
|
-
});
|
|
1097
|
-
this._resetPromise = resetPromise;
|
|
1098
|
-
return resetPromise.finally(() => {
|
|
1099
|
-
if (this._resetPromise === resetPromise) {
|
|
1100
|
-
this._resetPromise = null;
|
|
1101
|
-
}
|
|
1102
|
-
});
|
|
1103
856
|
}
|
|
1104
857
|
async dispose() {
|
|
1105
|
-
|
|
858
|
+
this._uiRegistry.reset();
|
|
859
|
+
this._jobQueue.clear();
|
|
1106
860
|
this.unregisterEventListeners();
|
|
1107
861
|
this.connector.reset();
|
|
862
|
+
this._discoveredDevices.clear();
|
|
863
|
+
this._sessions.clear();
|
|
864
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
1108
865
|
this.emitter.removeAllListeners();
|
|
1109
866
|
}
|
|
1110
867
|
uiResponse(response) {
|
|
@@ -1114,32 +871,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1114
871
|
// Device management
|
|
1115
872
|
// ---------------------------------------------------------------------------
|
|
1116
873
|
async searchDevices(options) {
|
|
1117
|
-
return this._searchDevices(options);
|
|
1118
|
-
}
|
|
1119
|
-
async _searchDevices(options, signal) {
|
|
1120
874
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
1121
875
|
try {
|
|
1122
876
|
if (options?.resetSession) {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1129
|
-
const stateGeneration = this._stateGeneration;
|
|
1130
|
-
const devices = await this.connector.searchDevices(
|
|
1131
|
-
options?.transportType ? {
|
|
1132
|
-
transportType: options.transportType,
|
|
1133
|
-
waitForAll: options.waitForAllTransports
|
|
1134
|
-
} : void 0
|
|
1135
|
-
);
|
|
1136
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1137
|
-
if (stateGeneration !== this._stateGeneration) {
|
|
1138
|
-
throw createHwkError({
|
|
1139
|
-
code: HardwareErrorCode3.UserAborted,
|
|
1140
|
-
message: "Ledger discovery was reset"
|
|
1141
|
-
});
|
|
877
|
+
this._doConnectAbortController?.abort();
|
|
878
|
+
this._sessions.clear();
|
|
879
|
+
this._connectingPromise = null;
|
|
880
|
+
this._doConnectAbortController = null;
|
|
881
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
1142
882
|
}
|
|
883
|
+
await this._ensureDevicePermission();
|
|
884
|
+
const devices = await this.connector.searchDevices();
|
|
1143
885
|
this._discoveredDevices.clear();
|
|
1144
886
|
for (const d of devices) {
|
|
1145
887
|
if (d.connectId) {
|
|
@@ -1147,7 +889,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1147
889
|
}
|
|
1148
890
|
}
|
|
1149
891
|
if (this._discoveredDevices.size === 0) {
|
|
1150
|
-
await this._ensureDevicePermission(
|
|
892
|
+
await this._ensureDevicePermission();
|
|
1151
893
|
}
|
|
1152
894
|
const result = Array.from(this._discoveredDevices.values());
|
|
1153
895
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -1166,287 +908,41 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1166
908
|
throw err;
|
|
1167
909
|
}
|
|
1168
910
|
}
|
|
1169
|
-
async searchDeviceTargets(options) {
|
|
1170
|
-
const devices = await this.searchDevices(options);
|
|
1171
|
-
return devices.map((device) => ({
|
|
1172
|
-
searchTargetId: device.connectId,
|
|
1173
|
-
searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
|
|
1174
|
-
vendor: "ledger",
|
|
1175
|
-
connectionType: device.connectionType,
|
|
1176
|
-
kind: "physical",
|
|
1177
|
-
label: device.label,
|
|
1178
|
-
model: device.model,
|
|
1179
|
-
modelName: device.modelName,
|
|
1180
|
-
serialNumber: device.serialNumber
|
|
1181
|
-
}));
|
|
1182
|
-
}
|
|
1183
|
-
async listConnectionTargets(options) {
|
|
1184
|
-
const targets = await this.searchDeviceTargets(options);
|
|
1185
|
-
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1186
|
-
...target,
|
|
1187
|
-
targetId: searchTargetId
|
|
1188
|
-
}));
|
|
1189
|
-
}
|
|
1190
911
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
1191
|
-
async _evictAllSessions(
|
|
1192
|
-
this._operations.endAll("explicit", preserveOperationId);
|
|
912
|
+
async _evictAllSessions() {
|
|
1193
913
|
if (this._sessions.size === 0) return;
|
|
1194
914
|
const stale = [...this._sessions.values()];
|
|
1195
915
|
this._sessions.clear();
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
} catch {
|
|
1201
|
-
}
|
|
916
|
+
for (const sid of stale) {
|
|
917
|
+
try {
|
|
918
|
+
await this.connector.disconnect(sid);
|
|
919
|
+
} catch {
|
|
1202
920
|
}
|
|
1203
|
-
}
|
|
921
|
+
}
|
|
1204
922
|
}
|
|
1205
923
|
static _createDeviceBusyError(method) {
|
|
1206
924
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
1207
925
|
code: HardwareErrorCode3.DeviceBusy
|
|
1208
926
|
});
|
|
1209
927
|
}
|
|
1210
|
-
async connectDevice(
|
|
1211
|
-
try {
|
|
1212
|
-
return await this._jobQueue.enqueue(
|
|
1213
|
-
searchTargetId || "__ledger_connect__",
|
|
1214
|
-
async () => {
|
|
1215
|
-
const connected = await this._connectTarget(searchTargetId);
|
|
1216
|
-
if (!connected.success) return connected;
|
|
1217
|
-
return this._createOperation(searchTargetId, connected.payload);
|
|
1218
|
-
},
|
|
1219
|
-
{
|
|
1220
|
-
label: "connectDevice",
|
|
1221
|
-
rejectIfBusy: true,
|
|
1222
|
-
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1223
|
-
}
|
|
1224
|
-
);
|
|
1225
|
-
} catch (error) {
|
|
1226
|
-
return this.errorToFailure(error);
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
async bindBleDevice(params) {
|
|
1230
|
-
if (params.identity.vendor !== "ledger" || !params.identity.value) {
|
|
1231
|
-
return failure2(HardwareErrorCode3.InvalidParams, "Ledger wallet identity is required");
|
|
1232
|
-
}
|
|
1233
|
-
const { chain, value: expectedFingerprint } = params.identity;
|
|
1234
|
-
try {
|
|
1235
|
-
return await this._jobQueue.enqueue(
|
|
1236
|
-
expectedFingerprint,
|
|
1237
|
-
async (signal) => {
|
|
1238
|
-
if (!this.getAvailableTransports().includes("ble")) {
|
|
1239
|
-
throw createHwkError({
|
|
1240
|
-
code: HardwareErrorCode3.TransportNotAvailable,
|
|
1241
|
-
message: "Ledger Bluetooth transport is not available"
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
this._activeConnectionType = "ble";
|
|
1245
|
-
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1246
|
-
const attempt = {
|
|
1247
|
-
extra: params.extra,
|
|
1248
|
-
bindingReason: "manual-rebind"
|
|
1249
|
-
};
|
|
1250
|
-
try {
|
|
1251
|
-
for (; ; ) {
|
|
1252
|
-
const connectId = await this._connectFirstOrSelect(
|
|
1253
|
-
[],
|
|
1254
|
-
void 0,
|
|
1255
|
-
true,
|
|
1256
|
-
void 0,
|
|
1257
|
-
attempt,
|
|
1258
|
-
signal
|
|
1259
|
-
);
|
|
1260
|
-
const sessionId = this._sessions.get(connectId);
|
|
1261
|
-
if (!sessionId) {
|
|
1262
|
-
throw createHwkError({
|
|
1263
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
1264
|
-
message: "Selected Ledger connection ended"
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
let saved = false;
|
|
1268
|
-
try {
|
|
1269
|
-
const installContext = {
|
|
1270
|
-
connection: { connectId, sessionId }
|
|
1271
|
-
};
|
|
1272
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
1273
|
-
chain,
|
|
1274
|
-
(method, callParams) => this._runConnectorCall(
|
|
1275
|
-
connectId,
|
|
1276
|
-
method,
|
|
1277
|
-
callParams,
|
|
1278
|
-
signal,
|
|
1279
|
-
void 0,
|
|
1280
|
-
void 0,
|
|
1281
|
-
{ autoInstallApp: true },
|
|
1282
|
-
installContext
|
|
1283
|
-
)
|
|
1284
|
-
);
|
|
1285
|
-
if (fingerprint !== expectedFingerprint) {
|
|
1286
|
-
attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
1287
|
-
attempt.rejectedConnectIds.add(connectId);
|
|
1288
|
-
attempt.rejectedConnectId = connectId;
|
|
1289
|
-
} else {
|
|
1290
|
-
const persisted = await this._publishVerifiedBleBinding(
|
|
1291
|
-
connectId,
|
|
1292
|
-
chain,
|
|
1293
|
-
fingerprint,
|
|
1294
|
-
attempt,
|
|
1295
|
-
void 0,
|
|
1296
|
-
signal
|
|
1297
|
-
);
|
|
1298
|
-
if (!persisted) {
|
|
1299
|
-
throw createHwkError({
|
|
1300
|
-
code: HardwareErrorCode3.UnknownError,
|
|
1301
|
-
message: "Bluetooth binding could not be saved",
|
|
1302
|
-
origin: "host"
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
saved = true;
|
|
1306
|
-
return success(connectId);
|
|
1307
|
-
}
|
|
1308
|
-
} finally {
|
|
1309
|
-
if (!saved && this._sessions.get(connectId) === sessionId) {
|
|
1310
|
-
this._sessions.delete(connectId);
|
|
1311
|
-
const teardown = this._runConnectorTeardown(
|
|
1312
|
-
() => this.connector.disconnect(sessionId)
|
|
1313
|
-
).catch(() => void 0);
|
|
1314
|
-
if (!signal.aborted) await teardown;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
} catch (error) {
|
|
1319
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1320
|
-
throw error;
|
|
1321
|
-
}
|
|
1322
|
-
},
|
|
1323
|
-
{
|
|
1324
|
-
label: "bindBleDevice",
|
|
1325
|
-
rejectIfBusy: true,
|
|
1326
|
-
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1327
|
-
}
|
|
1328
|
-
);
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
return this.errorToFailure(error);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
async acquireOperation(connectId, context) {
|
|
1334
|
-
try {
|
|
1335
|
-
return await this._jobQueue.enqueue(
|
|
1336
|
-
connectId || "__ledger_acquire__",
|
|
1337
|
-
async (signal) => {
|
|
1338
|
-
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1339
|
-
const hint = context.knownConnections?.find(
|
|
1340
|
-
(connection) => connection.transport === transport
|
|
1341
|
-
);
|
|
1342
|
-
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1343
|
-
await this._ensureDevicePermission(target, void 0, signal);
|
|
1344
|
-
const attempt = {
|
|
1345
|
-
...context,
|
|
1346
|
-
extra: context.extra ? { ...context.extra } : void 0
|
|
1347
|
-
};
|
|
1348
|
-
try {
|
|
1349
|
-
const resolvedConnectId = await this.ensureConnected(
|
|
1350
|
-
target,
|
|
1351
|
-
signal,
|
|
1352
|
-
true,
|
|
1353
|
-
void 0,
|
|
1354
|
-
attempt
|
|
1355
|
-
);
|
|
1356
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
1357
|
-
const result = this._createOperation(connectId, resolvedConnectId);
|
|
1358
|
-
if (result.success && attempt.selectedConnection) {
|
|
1359
|
-
this._pendingOperationBindings.set(result.payload, attempt);
|
|
1360
|
-
}
|
|
1361
|
-
return result;
|
|
1362
|
-
} catch (error) {
|
|
1363
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1364
|
-
throw error;
|
|
1365
|
-
}
|
|
1366
|
-
},
|
|
1367
|
-
{
|
|
1368
|
-
label: "acquireOperation",
|
|
1369
|
-
rejectIfBusy: true,
|
|
1370
|
-
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1371
|
-
}
|
|
1372
|
-
);
|
|
1373
|
-
} catch (error) {
|
|
1374
|
-
return this.errorToFailure(error);
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1378
|
-
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1379
|
-
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1380
|
-
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1381
|
-
const connectionType = this._isBleConnection() ? "ble" : "usb";
|
|
1382
|
-
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1383
|
-
vendor: "ledger",
|
|
1384
|
-
model: "unknown",
|
|
1385
|
-
firmwareVersion: "",
|
|
1386
|
-
deviceId: "",
|
|
1387
|
-
connectId: resolvedConnectId,
|
|
1388
|
-
connectionType
|
|
1389
|
-
};
|
|
1390
|
-
const operation = this._operations.create({
|
|
1391
|
-
searchTargetId,
|
|
1392
|
-
connectId: resolvedConnectId,
|
|
1393
|
-
device,
|
|
1394
|
-
connectionType,
|
|
1395
|
-
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1396
|
-
});
|
|
1397
|
-
return success(operation.operationId);
|
|
1398
|
-
}
|
|
1399
|
-
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
928
|
+
async connectDevice(connectId) {
|
|
1400
929
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
1401
930
|
try {
|
|
1402
|
-
this.
|
|
1403
|
-
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1404
|
-
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1405
|
-
this._activeConnectionType = discoveredType;
|
|
1406
|
-
}
|
|
1407
|
-
if (this._isBleConnection() && !connectId) {
|
|
931
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
1408
932
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1409
933
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1410
934
|
});
|
|
1411
935
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
if (this.connector.availableTransports && this.connector.availableTransports.length > 1) {
|
|
1415
|
-
await this._evictAllSessions(preserveOperationId);
|
|
1416
|
-
} else if (this._isBleConnection()) {
|
|
1417
|
-
const previousSessionId = this._sessions.get(connectId);
|
|
1418
|
-
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1419
|
-
if (previousSessionId) {
|
|
1420
|
-
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1421
|
-
this._sessions.delete(connectId);
|
|
1422
|
-
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1423
|
-
}
|
|
1424
|
-
} else {
|
|
1425
|
-
await this._evictAllSessions(preserveOperationId);
|
|
1426
|
-
}
|
|
1427
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1428
|
-
const stateGeneration = this._stateGeneration;
|
|
1429
|
-
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1430
|
-
let session;
|
|
1431
|
-
try {
|
|
1432
|
-
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1433
|
-
transportType: this._activeConnectionType ?? "usb"
|
|
1434
|
-
}) : await this.connector.connect(connectId);
|
|
1435
|
-
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1436
|
-
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1437
|
-
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1438
|
-
code: HardwareErrorCode3.UserAborted
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
} finally {
|
|
1442
|
-
releaseOperation();
|
|
936
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
937
|
+
await this._evictAllSessions();
|
|
1443
938
|
}
|
|
1444
|
-
|
|
1445
|
-
this.
|
|
939
|
+
await this._ensureDevicePermission(connectId);
|
|
940
|
+
const session = await this.connector.connect(connectId);
|
|
941
|
+
this._sessions.set(connectId, session.sessionId);
|
|
1446
942
|
if (session.deviceInfo) {
|
|
1447
|
-
this._discoveredDevices.set(
|
|
943
|
+
this._discoveredDevices.set(connectId, session.deviceInfo);
|
|
1448
944
|
}
|
|
1449
|
-
const result = success(
|
|
945
|
+
const result = success(connectId);
|
|
1450
946
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
1451
947
|
return result;
|
|
1452
948
|
} catch (err) {
|
|
@@ -1459,60 +955,30 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1459
955
|
return failureResult;
|
|
1460
956
|
}
|
|
1461
957
|
}
|
|
1462
|
-
async
|
|
1463
|
-
const operation = this._operations.find(operationId);
|
|
1464
|
-
if (!operation) {
|
|
1465
|
-
this._operations.resolve(operationId);
|
|
1466
|
-
return;
|
|
1467
|
-
}
|
|
1468
|
-
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1469
|
-
if (!endedOperation) return;
|
|
1470
|
-
const { connectId } = operation;
|
|
958
|
+
async disconnectDevice(connectId) {
|
|
1471
959
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1472
|
-
method: "
|
|
960
|
+
method: "disconnectDevice",
|
|
1473
961
|
connectId,
|
|
1474
962
|
params: { connectId }
|
|
1475
963
|
});
|
|
1476
964
|
try {
|
|
1477
|
-
|
|
1478
|
-
|
|
965
|
+
const sessionId = this._sessions.get(connectId);
|
|
966
|
+
if (sessionId) {
|
|
967
|
+
await this.connector.disconnect(sessionId);
|
|
968
|
+
this._sessions.delete(connectId);
|
|
969
|
+
}
|
|
970
|
+
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1479
971
|
} catch (err) {
|
|
1480
972
|
const e = err;
|
|
1481
973
|
debugLog("[LedgerAdapter][RES]", {
|
|
1482
|
-
method: "
|
|
974
|
+
method: "disconnectDevice",
|
|
1483
975
|
success: false,
|
|
1484
976
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1485
977
|
});
|
|
1486
978
|
throw err;
|
|
1487
979
|
}
|
|
1488
980
|
}
|
|
1489
|
-
async
|
|
1490
|
-
const sessionIds = /* @__PURE__ */ new Set();
|
|
1491
|
-
for (const [connectId, sessionId] of this._sessions) {
|
|
1492
|
-
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1493
|
-
this._sessions.delete(connectId);
|
|
1494
|
-
sessionIds.add(sessionId);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
for (const sessionId of sessionIds) {
|
|
1498
|
-
await this._runConnectorTeardown(
|
|
1499
|
-
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1500
|
-
);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
async _releaseLostOperationConnection(operationId) {
|
|
1504
|
-
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1505
|
-
if (!endedOperation) return;
|
|
1506
|
-
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1507
|
-
await this._releaseOperationConnection(endedOperation);
|
|
1508
|
-
}
|
|
1509
|
-
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1510
|
-
let connectId;
|
|
1511
|
-
try {
|
|
1512
|
-
connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1513
|
-
} catch (error) {
|
|
1514
|
-
return this.errorToFailure(error);
|
|
1515
|
-
}
|
|
981
|
+
async getDeviceInfo(connectId, deviceId) {
|
|
1516
982
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1517
983
|
method: "getDeviceInfo",
|
|
1518
984
|
connectId,
|
|
@@ -1550,6 +1016,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1550
1016
|
throw err;
|
|
1551
1017
|
}
|
|
1552
1018
|
}
|
|
1019
|
+
getSupportedChains() {
|
|
1020
|
+
return ["evm", "btc", "sol", "tron"];
|
|
1021
|
+
}
|
|
1553
1022
|
// ---------------------------------------------------------------------------
|
|
1554
1023
|
// Chain call helper
|
|
1555
1024
|
// ---------------------------------------------------------------------------
|
|
@@ -1584,21 +1053,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1584
1053
|
if (params && typeof params === "object") {
|
|
1585
1054
|
const {
|
|
1586
1055
|
autoInstallApp,
|
|
1587
|
-
operationId,
|
|
1588
1056
|
passphraseState: _passphraseState,
|
|
1589
1057
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1590
|
-
knownConnections,
|
|
1591
|
-
extra,
|
|
1592
|
-
allowDeviceSelection,
|
|
1593
1058
|
...rest
|
|
1594
1059
|
} = params;
|
|
1595
1060
|
return {
|
|
1596
1061
|
commonParams: {
|
|
1597
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1598
|
-
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1599
|
-
knownConnections,
|
|
1600
|
-
extra,
|
|
1601
|
-
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0
|
|
1062
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1602
1063
|
},
|
|
1603
1064
|
rest
|
|
1604
1065
|
};
|
|
@@ -1774,27 +1235,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1774
1235
|
);
|
|
1775
1236
|
}
|
|
1776
1237
|
// ---------------------------------------------------------------------------
|
|
1777
|
-
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1778
|
-
// ---------------------------------------------------------------------------
|
|
1779
|
-
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
1780
|
-
return this.callChainWithMergedParams(
|
|
1781
|
-
connectId,
|
|
1782
|
-
deviceId,
|
|
1783
|
-
"zcash",
|
|
1784
|
-
"zcashGetFullViewingKey",
|
|
1785
|
-
params
|
|
1786
|
-
);
|
|
1787
|
-
}
|
|
1788
|
-
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1789
|
-
return this.callChainWithMergedParams(
|
|
1790
|
-
connectId,
|
|
1791
|
-
deviceId,
|
|
1792
|
-
"zcash",
|
|
1793
|
-
"zcashGetShieldedAddress",
|
|
1794
|
-
params
|
|
1795
|
-
);
|
|
1796
|
-
}
|
|
1797
|
-
// ---------------------------------------------------------------------------
|
|
1798
1238
|
// App management — OS-level Ledger app install / list. Bypasses fingerprint
|
|
1799
1239
|
// and chain-handler dispatch; installApp progress is forwarded to the adapter
|
|
1800
1240
|
// emitter via 'ui-event' AppInstallProgress events.
|
|
@@ -1883,7 +1323,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1883
1323
|
);
|
|
1884
1324
|
}
|
|
1885
1325
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1886
|
-
|
|
1326
|
+
this.resetState();
|
|
1887
1327
|
}
|
|
1888
1328
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1889
1329
|
if (!result.isGenuine) {
|
|
@@ -1909,10 +1349,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1909
1349
|
if (relayUrl) {
|
|
1910
1350
|
try {
|
|
1911
1351
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1912
|
-
|
|
1352
|
+
this.resetState();
|
|
1913
1353
|
} catch {
|
|
1914
1354
|
this.connector.reset();
|
|
1915
|
-
|
|
1355
|
+
this.resetState();
|
|
1916
1356
|
}
|
|
1917
1357
|
}
|
|
1918
1358
|
}
|
|
@@ -1924,20 +1364,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1924
1364
|
this.emitter.off(event, listener);
|
|
1925
1365
|
}
|
|
1926
1366
|
cancel(connectId) {
|
|
1927
|
-
const namedOperationIsLive = (id) => {
|
|
1928
|
-
try {
|
|
1929
|
-
this._operations.resolve(id);
|
|
1930
|
-
return true;
|
|
1931
|
-
} catch {
|
|
1932
|
-
return false;
|
|
1933
|
-
}
|
|
1934
|
-
};
|
|
1935
|
-
if (isHardwareOperationId(connectId) && !namedOperationIsLive(connectId)) {
|
|
1936
|
-
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1937
|
-
connectId
|
|
1938
|
-
});
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
1941
1367
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1942
1368
|
code: HardwareErrorCode3.UserAborted,
|
|
1943
1369
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1948,50 +1374,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1948
1374
|
this._lastCancelReason = void 0;
|
|
1949
1375
|
}
|
|
1950
1376
|
}, 2e3);
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
operationId = connectId;
|
|
1955
|
-
} else if (!connectId && isHardwareOperationId(activeJobId)) {
|
|
1956
|
-
operationId = activeJobId;
|
|
1957
|
-
}
|
|
1958
|
-
let resolvedConnectId = connectId;
|
|
1959
|
-
if (operationId) {
|
|
1960
|
-
try {
|
|
1961
|
-
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1962
|
-
} catch {
|
|
1963
|
-
resolvedConnectId = void 0;
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1967
|
-
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1968
|
-
if (!connectId) {
|
|
1969
|
-
this._uiRegistry.cancel();
|
|
1970
|
-
} else if (pendingOperationId) {
|
|
1971
|
-
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1972
|
-
}
|
|
1973
|
-
this._finishBleBinding("cancelled");
|
|
1974
|
-
if (!connectId) this._pendingOperationBindings.clear();
|
|
1975
|
-
else if (pendingOperationId) {
|
|
1976
|
-
this._pendingOperationBindings.delete(pendingOperationId);
|
|
1977
|
-
}
|
|
1978
|
-
const queueKeys = /* @__PURE__ */ new Set();
|
|
1979
|
-
if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
|
|
1980
|
-
if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
|
|
1981
|
-
if (queueKeys.size) {
|
|
1982
|
-
let cancelledAnyJob = false;
|
|
1983
|
-
for (const key of queueKeys) {
|
|
1984
|
-
cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
|
|
1985
|
-
}
|
|
1986
|
-
debugLog("[LedgerAdapter] cancel routed to queue keys", {
|
|
1987
|
-
queueKeys: [...queueKeys],
|
|
1988
|
-
cancelledAnyJob
|
|
1989
|
-
});
|
|
1377
|
+
this._uiRegistry.cancel();
|
|
1378
|
+
if (connectId) {
|
|
1379
|
+
this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
|
|
1990
1380
|
} else {
|
|
1991
1381
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1992
1382
|
}
|
|
1993
|
-
if (
|
|
1994
|
-
const sessionId = this._sessions.get(
|
|
1383
|
+
if (connectId) {
|
|
1384
|
+
const sessionId = this._sessions.get(connectId) ?? connectId;
|
|
1995
1385
|
void this.connector.cancel(sessionId);
|
|
1996
1386
|
} else {
|
|
1997
1387
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -2003,99 +1393,41 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2003
1393
|
// ---------------------------------------------------------------------------
|
|
2004
1394
|
// Chain fingerprint
|
|
2005
1395
|
// ---------------------------------------------------------------------------
|
|
2006
|
-
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
2007
1396
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
2008
1397
|
try {
|
|
2009
1398
|
const fingerprint = await this._computeChainFingerprint(
|
|
2010
1399
|
chain,
|
|
2011
1400
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
2012
1401
|
);
|
|
2013
|
-
if (deviceId) {
|
|
2014
|
-
if (fingerprint !== deviceId) {
|
|
2015
|
-
return failure2(
|
|
2016
|
-
HardwareErrorCode3.DeviceMismatch,
|
|
2017
|
-
formatDeviceMismatchError(deviceId, fingerprint)
|
|
2018
|
-
);
|
|
2019
|
-
}
|
|
2020
|
-
if (isHardwareOperationId(connectId)) {
|
|
2021
|
-
const operation = this._operations.resolve(connectId);
|
|
2022
|
-
await this._publishVerifiedBleBinding(
|
|
2023
|
-
operation.connectId,
|
|
2024
|
-
chain,
|
|
2025
|
-
fingerprint,
|
|
2026
|
-
void 0,
|
|
2027
|
-
connectId
|
|
2028
|
-
);
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
1402
|
return success(fingerprint);
|
|
2032
1403
|
} catch (err) {
|
|
2033
1404
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
2034
1405
|
return this.errorToFailure(err);
|
|
2035
1406
|
}
|
|
2036
1407
|
}
|
|
2037
|
-
/**
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Verify fingerprint using an existing sessionId directly.
|
|
1410
|
+
* Safe to call inside connectorCall without causing queue deadlock.
|
|
1411
|
+
*/
|
|
1412
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
1413
|
+
if (!deviceId) return { success: true };
|
|
2043
1414
|
try {
|
|
2044
|
-
const
|
|
2045
|
-
|
|
2046
|
-
this.
|
|
2047
|
-
{
|
|
2048
|
-
selectionRequestId: binding.selectedConnection.requestId,
|
|
2049
|
-
connection: { transport: "ble", connectId },
|
|
2050
|
-
identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
|
|
2051
|
-
extra: binding.extra,
|
|
2052
|
-
operationId
|
|
2053
|
-
},
|
|
2054
|
-
signal
|
|
1415
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1416
|
+
chain,
|
|
1417
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
2055
1418
|
);
|
|
2056
|
-
if (
|
|
2057
|
-
|
|
2058
|
-
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
2059
|
-
connectId,
|
|
2060
|
-
reason: outcome.reason
|
|
2061
|
-
});
|
|
2062
|
-
}
|
|
2063
|
-
return outcome.saved;
|
|
2064
|
-
} catch (error) {
|
|
2065
|
-
if (operationId) {
|
|
2066
|
-
this._pendingOperationBindings.delete(operationId);
|
|
2067
|
-
this._operations.end(operationId, "explicit");
|
|
2068
|
-
}
|
|
2069
|
-
const sessionId = this._sessions.get(connectId);
|
|
2070
|
-
this._sessions.delete(connectId);
|
|
2071
|
-
if (sessionId) {
|
|
2072
|
-
const teardown = this._runConnectorTeardown(
|
|
2073
|
-
() => this.connector.disconnect(sessionId)
|
|
2074
|
-
).catch(() => void 0);
|
|
2075
|
-
if (!signal?.aborted) await teardown;
|
|
2076
|
-
}
|
|
2077
|
-
throw error;
|
|
2078
|
-
} finally {
|
|
2079
|
-
if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
|
|
2080
|
-
this._bindingSelectionRequestId = void 0;
|
|
1419
|
+
if (fingerprint === deviceId) {
|
|
1420
|
+
return { success: true };
|
|
2081
1421
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
1422
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
const mapped = mapLedgerError(err);
|
|
1425
|
+
if (mapped.code === HardwareErrorCode3.WrongApp || mapped.code === HardwareErrorCode3.DeviceLocked) {
|
|
1426
|
+
return { success: true };
|
|
2084
1427
|
}
|
|
1428
|
+
throw err;
|
|
2085
1429
|
}
|
|
2086
1430
|
}
|
|
2087
|
-
/** Verify on the acquired session without re-entering the job queue. */
|
|
2088
|
-
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
2089
|
-
if (!deviceId) return { success: true };
|
|
2090
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
2091
|
-
chain,
|
|
2092
|
-
(method, params) => this._callConnector(sessionId, method, params)
|
|
2093
|
-
);
|
|
2094
|
-
if (fingerprint === deviceId) {
|
|
2095
|
-
return { success: true };
|
|
2096
|
-
}
|
|
2097
|
-
return { success: false, expected: deviceId, actual: fingerprint };
|
|
2098
|
-
}
|
|
2099
1431
|
/**
|
|
2100
1432
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
2101
1433
|
*
|
|
@@ -2123,27 +1455,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2123
1455
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
2124
1456
|
} else if (chain === "tron") {
|
|
2125
1457
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2126
|
-
} else if (chain === "zcash") {
|
|
2127
|
-
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
2128
1458
|
} else {
|
|
2129
1459
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
2130
1460
|
}
|
|
2131
1461
|
return deriveDeviceFingerprint(address);
|
|
2132
1462
|
}
|
|
2133
1463
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
2134
|
-
/**
|
|
2135
|
-
* The operation the running device job belongs to, so a UI request raised
|
|
2136
|
-
* mid-call can name it. A call pinned to an operation queues under its id
|
|
2137
|
-
* (`ledgerQueueKey`); a call without one queues under the connectId, and the
|
|
2138
|
-
* live operation on that connection is still the owner. Only work with no
|
|
2139
|
-
* operation at all (cold start) comes back undefined.
|
|
2140
|
-
*/
|
|
2141
|
-
_activeOperationId() {
|
|
2142
|
-
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
2143
|
-
if (!activeJobId) return void 0;
|
|
2144
|
-
if (isHardwareOperationId(activeJobId)) return activeJobId;
|
|
2145
|
-
return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
|
|
2146
|
-
}
|
|
2147
1464
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
2148
1465
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
2149
1466
|
// registry slot is released and a stale RECEIVE_DEVICE_CONNECT won't land in
|
|
@@ -2152,18 +1469,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2152
1469
|
if (signal?.aborted) {
|
|
2153
1470
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2154
1471
|
}
|
|
2155
|
-
const operationId = this._activeOperationId();
|
|
2156
1472
|
const waitPromise = this._uiRegistry.wait(
|
|
2157
|
-
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2158
|
-
{ operationId }
|
|
1473
|
+
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2159
1474
|
);
|
|
2160
1475
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
2161
1476
|
type: UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2162
1477
|
payload: {
|
|
2163
1478
|
vendor: "ledger",
|
|
2164
1479
|
reason: "device-not-found",
|
|
2165
|
-
message: "Please connect and unlock your Ledger device"
|
|
2166
|
-
operationId
|
|
1480
|
+
message: "Please connect and unlock your Ledger device"
|
|
2167
1481
|
}
|
|
2168
1482
|
});
|
|
2169
1483
|
let payload;
|
|
@@ -2224,18 +1538,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2224
1538
|
return { ...params, showOnDevice: true };
|
|
2225
1539
|
}
|
|
2226
1540
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2227
|
-
const operationId = this._activeOperationId();
|
|
2228
1541
|
const waitPromise = this._uiRegistry.wait(
|
|
2229
|
-
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2230
|
-
{ operationId }
|
|
1542
|
+
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2231
1543
|
);
|
|
2232
1544
|
this.emitter.emit(UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
2233
1545
|
type: UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2234
1546
|
payload: {
|
|
2235
1547
|
vendor: "ledger",
|
|
2236
1548
|
path,
|
|
2237
|
-
accountIndex
|
|
2238
|
-
operationId
|
|
1549
|
+
accountIndex
|
|
2239
1550
|
}
|
|
2240
1551
|
});
|
|
2241
1552
|
try {
|
|
@@ -2252,14 +1563,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2252
1563
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
2253
1564
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
2254
1565
|
async _waitForInstallAppConfirm(appName) {
|
|
2255
|
-
const operationId = this._activeOperationId();
|
|
2256
1566
|
const waitPromise = this._uiRegistry.wait(
|
|
2257
|
-
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2258
|
-
{ operationId }
|
|
1567
|
+
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2259
1568
|
);
|
|
2260
1569
|
this.emitter.emit(UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
2261
1570
|
type: UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2262
|
-
payload: { vendor: "ledger", appName
|
|
1571
|
+
payload: { vendor: "ledger", appName }
|
|
2263
1572
|
});
|
|
2264
1573
|
try {
|
|
2265
1574
|
const payload = await waitPromise;
|
|
@@ -2275,17 +1584,16 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2275
1584
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
2276
1585
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
2277
1586
|
// doesn't kill caller B's await.
|
|
2278
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false
|
|
1587
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2279
1588
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
2280
|
-
this.
|
|
2281
|
-
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1589
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
2282
1590
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
2283
1591
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2284
1592
|
});
|
|
2285
1593
|
}
|
|
2286
1594
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
2287
1595
|
if (!connectId && this._sessions.size > 0) {
|
|
2288
|
-
if (!this.
|
|
1596
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
|
|
2289
1597
|
throw Object.assign(
|
|
2290
1598
|
new Error(
|
|
2291
1599
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -2295,24 +1603,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2295
1603
|
}
|
|
2296
1604
|
return this._sessions.keys().next().value;
|
|
2297
1605
|
}
|
|
2298
|
-
if (!this._connectingPromise
|
|
2299
|
-
|
|
2300
|
-
this._doConnectAbortController
|
|
2301
|
-
const innerSignal = controller.signal;
|
|
1606
|
+
if (!this._connectingPromise) {
|
|
1607
|
+
this._doConnectAbortController = new AbortController();
|
|
1608
|
+
const innerSignal = this._doConnectAbortController.signal;
|
|
2302
1609
|
this._connectingPromise = (async () => {
|
|
2303
1610
|
try {
|
|
2304
|
-
return await this._doConnect(
|
|
2305
|
-
innerSignal,
|
|
2306
|
-
connectId,
|
|
2307
|
-
allowUsbEphemeralFallback,
|
|
2308
|
-
preserveOperationId,
|
|
2309
|
-
context
|
|
2310
|
-
);
|
|
1611
|
+
return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback);
|
|
2311
1612
|
} finally {
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
this._doConnectAbortController = null;
|
|
2315
|
-
}
|
|
1613
|
+
this._connectingPromise = null;
|
|
1614
|
+
this._doConnectAbortController = null;
|
|
2316
1615
|
}
|
|
2317
1616
|
})();
|
|
2318
1617
|
}
|
|
@@ -2321,51 +1620,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2321
1620
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
2322
1621
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
2323
1622
|
// throw DeviceNotFound so the user is kicked out of the loop.
|
|
2324
|
-
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
)
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
preserveOperationId,
|
|
2340
|
-
context,
|
|
2341
|
-
internalSignal
|
|
2342
|
-
);
|
|
2343
|
-
}
|
|
2344
|
-
this._activeConnectionType = "ble";
|
|
2345
|
-
const knownBle = context?.knownConnections?.find(
|
|
2346
|
-
(connection) => connection.transport === "ble"
|
|
2347
|
-
);
|
|
2348
|
-
if (knownBle?.transport === "ble") {
|
|
2349
|
-
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2350
|
-
}
|
|
2351
|
-
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2352
|
-
throw createHwkError({
|
|
2353
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2354
|
-
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2355
|
-
});
|
|
1623
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
1624
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
|
|
1625
|
+
try {
|
|
1626
|
+
return await this._connectDeviceOrThrow(targetConnectId);
|
|
1627
|
+
} catch (err) {
|
|
1628
|
+
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
1629
|
+
throw err;
|
|
1630
|
+
}
|
|
1631
|
+
this._discoveredDevices.delete(targetConnectId);
|
|
1632
|
+
if (isDeviceDisconnectedError(err)) {
|
|
1633
|
+
try {
|
|
1634
|
+
this.connector.reset?.();
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
2356
1638
|
}
|
|
2357
|
-
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2358
|
-
return this._connectFirstOrSelect(
|
|
2359
|
-
bleDevices,
|
|
2360
|
-
void 0,
|
|
2361
|
-
allowUsbEphemeralFallback,
|
|
2362
|
-
preserveOperationId,
|
|
2363
|
-
context,
|
|
2364
|
-
internalSignal
|
|
2365
|
-
);
|
|
2366
|
-
}
|
|
2367
|
-
if (this._isBleConnection() && targetConnectId) {
|
|
2368
|
-
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
2369
1639
|
}
|
|
2370
1640
|
let confirms = 0;
|
|
2371
1641
|
while (!internalSignal.aborted) {
|
|
@@ -2373,28 +1643,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2373
1643
|
type: EConnectorInteraction.Searching,
|
|
2374
1644
|
payload: { sessionId: "" }
|
|
2375
1645
|
});
|
|
2376
|
-
let devices = await this.
|
|
2377
|
-
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1646
|
+
let devices = await this.searchDevices();
|
|
2378
1647
|
if (devices.length === 0) {
|
|
2379
1648
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
2380
1649
|
await new Promise((resolve) => {
|
|
2381
1650
|
setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
2382
1651
|
});
|
|
2383
|
-
|
|
2384
|
-
devices = await this._searchDevices(void 0, internalSignal);
|
|
2385
|
-
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1652
|
+
devices = await this.searchDevices();
|
|
2386
1653
|
if (devices.length > 0) break;
|
|
2387
1654
|
}
|
|
2388
1655
|
}
|
|
2389
|
-
if (devices.length > 0
|
|
1656
|
+
if (devices.length > 0) {
|
|
2390
1657
|
try {
|
|
2391
1658
|
return await this._connectFirstOrSelect(
|
|
2392
1659
|
devices,
|
|
2393
1660
|
targetConnectId,
|
|
2394
|
-
allowUsbEphemeralFallback
|
|
2395
|
-
preserveOperationId,
|
|
2396
|
-
context,
|
|
2397
|
-
internalSignal
|
|
1661
|
+
allowUsbEphemeralFallback
|
|
2398
1662
|
);
|
|
2399
1663
|
} catch (err) {
|
|
2400
1664
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -2423,136 +1687,45 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2423
1687
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2424
1688
|
throw new Error("_doConnect aborted");
|
|
2425
1689
|
}
|
|
2426
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2427
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
1690
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
2428
1691
|
if (targetConnectId) {
|
|
2429
1692
|
const target = devices.find(
|
|
2430
1693
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
2431
1694
|
);
|
|
2432
1695
|
if (target) {
|
|
2433
|
-
return this._connectDeviceOrThrow(target.connectId
|
|
1696
|
+
return this._connectDeviceOrThrow(target.connectId);
|
|
2434
1697
|
}
|
|
2435
|
-
if (!this.
|
|
1698
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
2436
1699
|
debugLog(
|
|
2437
1700
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
2438
1701
|
);
|
|
2439
|
-
return this._connectDeviceOrThrow(devices[0].connectId
|
|
1702
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2440
1703
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2447
|
-
}
|
|
2448
|
-
throw err;
|
|
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;
|
|
2449
1709
|
}
|
|
1710
|
+
throw err;
|
|
2450
1711
|
}
|
|
2451
|
-
|
|
2452
|
-
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1712
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
2453
1713
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
2454
1714
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2455
1715
|
});
|
|
2456
1716
|
}
|
|
2457
|
-
if (
|
|
2458
|
-
|
|
2459
|
-
if (context) context.bindingSessionId = bindingSessionId;
|
|
2460
|
-
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2461
|
-
const knownUsb = context?.knownConnections?.find(
|
|
2462
|
-
(connection) => connection.transport === "usb"
|
|
2463
|
-
);
|
|
2464
|
-
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2465
|
-
const { device, requestId } = await requestBleDeviceSelection({
|
|
2466
|
-
emitter: this.emitter,
|
|
2467
|
-
registry: this._uiRegistry,
|
|
2468
|
-
signal,
|
|
2469
|
-
allowUsbFallback,
|
|
2470
|
-
scan: async () => {
|
|
2471
|
-
if (allowUsbFallback) {
|
|
2472
|
-
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2473
|
-
const candidate = usbDevices.find(
|
|
2474
|
-
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2475
|
-
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2476
|
-
if (candidate) return [candidate];
|
|
2477
|
-
}
|
|
2478
|
-
return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
|
|
2479
|
-
},
|
|
2480
|
-
request: {
|
|
2481
|
-
devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
|
|
2482
|
-
bindingSessionId,
|
|
2483
|
-
rejectedConnectId: context?.rejectedConnectId,
|
|
2484
|
-
context: {
|
|
2485
|
-
kind: "bind-connection",
|
|
2486
|
-
transport: "ble",
|
|
2487
|
-
reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
|
|
2488
|
-
},
|
|
2489
|
-
extra: context?.extra,
|
|
2490
|
-
operationId: preserveOperationId
|
|
2491
|
-
}
|
|
2492
|
-
});
|
|
2493
|
-
if (device.connectionType === "usb") {
|
|
2494
|
-
this._activeConnectionType = "usb";
|
|
2495
|
-
if (context) context.selectedConnection = void 0;
|
|
2496
|
-
if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
|
|
2497
|
-
this._bindingSelectionRequestId = void 0;
|
|
2498
|
-
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
2499
|
-
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
2500
|
-
payload: { selectionRequestId: requestId, status: "cancelled" }
|
|
2501
|
-
});
|
|
2502
|
-
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2503
|
-
}
|
|
2504
|
-
if (context) context.selectedConnection = { connectId: device.connectId, requestId };
|
|
2505
|
-
this._bindingSelectionRequestId = requestId;
|
|
2506
|
-
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2507
|
-
}
|
|
2508
|
-
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2509
|
-
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2510
|
-
throw createHwkError({
|
|
2511
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2512
|
-
message: "Select a Ledger device before continuing"
|
|
2513
|
-
});
|
|
2514
|
-
}
|
|
2515
|
-
const requestId = this._uiRegistry.createRequestId();
|
|
2516
|
-
const operationId = this._activeOperationId();
|
|
2517
|
-
const waitPromise = this._uiRegistry.wait(
|
|
2518
|
-
UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2519
|
-
{ requestId, operationId }
|
|
2520
|
-
);
|
|
2521
|
-
this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2522
|
-
type: UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2523
|
-
payload: {
|
|
2524
|
-
devices,
|
|
2525
|
-
requestId,
|
|
2526
|
-
operationId,
|
|
2527
|
-
context: requiresBleSelection ? {
|
|
2528
|
-
kind: "bind-connection",
|
|
2529
|
-
transport: "ble",
|
|
2530
|
-
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2531
|
-
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2532
|
-
extra: context?.extra
|
|
2533
|
-
}
|
|
2534
|
-
});
|
|
2535
|
-
const { sdkConnectId } = await (signal ? this._abortable(signal, waitPromise) : waitPromise);
|
|
2536
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
2537
|
-
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2538
|
-
if (!selected) {
|
|
2539
|
-
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2540
|
-
code: HardwareErrorCode3.DeviceNotFound
|
|
2541
|
-
});
|
|
2542
|
-
}
|
|
2543
|
-
if (context && requiresBleSelection)
|
|
2544
|
-
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2545
|
-
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1717
|
+
if (devices.length > 1) {
|
|
1718
|
+
throw createMultipleUsbLedgerDevicesError();
|
|
2546
1719
|
}
|
|
2547
1720
|
if (devices.length !== 1) {
|
|
2548
1721
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
2549
1722
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2550
1723
|
});
|
|
2551
1724
|
}
|
|
2552
|
-
return this._connectDeviceOrThrow(devices[0].connectId
|
|
1725
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2553
1726
|
}
|
|
2554
|
-
async _connectDeviceOrThrow(chosenConnectId
|
|
2555
|
-
const result = await this.
|
|
1727
|
+
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1728
|
+
const result = await this.connectDevice(chosenConnectId);
|
|
2556
1729
|
if (!result.success) {
|
|
2557
1730
|
const payload = result.payload;
|
|
2558
1731
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -2563,7 +1736,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2563
1736
|
}
|
|
2564
1737
|
throw rethrow;
|
|
2565
1738
|
}
|
|
2566
|
-
return
|
|
1739
|
+
return chosenConnectId;
|
|
2567
1740
|
}
|
|
2568
1741
|
/**
|
|
2569
1742
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -2593,122 +1766,26 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2593
1766
|
* lives in one place.
|
|
2594
1767
|
*/
|
|
2595
1768
|
async _callConnector(sessionId, method, params, signal) {
|
|
2596
|
-
this.
|
|
2597
|
-
if (signal?.aborted) throw this._abortReason(signal);
|
|
2598
|
-
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2599
|
-
let promise;
|
|
2600
|
-
try {
|
|
2601
|
-
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2602
|
-
} catch (error) {
|
|
2603
|
-
releaseOperation();
|
|
2604
|
-
throw error;
|
|
2605
|
-
}
|
|
1769
|
+
const promise = this.connector.call(sessionId, method, params);
|
|
2606
1770
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
2607
1771
|
return this._unwrapConnectorResult(result);
|
|
2608
1772
|
}
|
|
2609
|
-
_assertConnectorReady(method) {
|
|
2610
|
-
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2611
|
-
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
_runConnectorTeardown(task) {
|
|
2615
|
-
const previous = this._connectorTeardownTail;
|
|
2616
|
-
let releaseTail = () => void 0;
|
|
2617
|
-
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2618
|
-
releaseTail = resolve;
|
|
2619
|
-
});
|
|
2620
|
-
this._pendingConnectorTeardowns += 1;
|
|
2621
|
-
return (async () => {
|
|
2622
|
-
try {
|
|
2623
|
-
await previous;
|
|
2624
|
-
await this._waitForConnectorOperationsToDrain();
|
|
2625
|
-
await task();
|
|
2626
|
-
} finally {
|
|
2627
|
-
this._pendingConnectorTeardowns -= 1;
|
|
2628
|
-
releaseTail();
|
|
2629
|
-
}
|
|
2630
|
-
})();
|
|
2631
|
-
}
|
|
2632
|
-
_waitForConnectorOperationsToDrain() {
|
|
2633
|
-
if (this._unsettledConnectorOperations.size === 0) {
|
|
2634
|
-
return Promise.resolve();
|
|
2635
|
-
}
|
|
2636
|
-
return new Promise((resolve) => {
|
|
2637
|
-
this._connectorIdleWaiters.add(resolve);
|
|
2638
|
-
});
|
|
2639
|
-
}
|
|
2640
|
-
_retainConnectorOperation(key) {
|
|
2641
|
-
this._unsettledConnectorOperations.set(
|
|
2642
|
-
key,
|
|
2643
|
-
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2644
|
-
);
|
|
2645
|
-
let released = false;
|
|
2646
|
-
return () => {
|
|
2647
|
-
if (released) return;
|
|
2648
|
-
released = true;
|
|
2649
|
-
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2650
|
-
if (remaining > 0) {
|
|
2651
|
-
this._unsettledConnectorOperations.set(key, remaining);
|
|
2652
|
-
} else {
|
|
2653
|
-
this._unsettledConnectorOperations.delete(key);
|
|
2654
|
-
}
|
|
2655
|
-
if (this._unsettledConnectorOperations.size === 0) {
|
|
2656
|
-
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2657
|
-
this._connectorIdleWaiters.clear();
|
|
2658
|
-
}
|
|
2659
|
-
};
|
|
2660
|
-
}
|
|
2661
1773
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
throw createHwkError({
|
|
2665
|
-
code: HardwareErrorCode3.InvalidParams,
|
|
2666
|
-
message: "Conflicting Ledger operation ids",
|
|
2667
|
-
params: {
|
|
2668
|
-
positionalOperationId,
|
|
2669
|
-
commonOperationId: commonParams.operationId
|
|
2670
|
-
}
|
|
2671
|
-
});
|
|
2672
|
-
}
|
|
2673
|
-
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2674
|
-
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2675
|
-
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2676
|
-
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2677
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
2678
|
-
method,
|
|
2679
|
-
connectId: effectiveConnectId || "(empty)",
|
|
2680
|
-
params
|
|
2681
|
-
});
|
|
2682
|
-
const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
|
|
1774
|
+
debugLog("[LedgerAdapter][REQ]", { method, connectId: connectId || "(empty)", params });
|
|
1775
|
+
const queueKey = connectId || "__ledger_default__";
|
|
2683
1776
|
try {
|
|
2684
1777
|
const result = await this._jobQueue.enqueue(
|
|
2685
1778
|
queueKey,
|
|
2686
|
-
async (signal) =>
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
commonParams,
|
|
2697
|
-
installContext ?? {},
|
|
2698
|
-
operationId
|
|
2699
|
-
);
|
|
2700
|
-
} catch (error) {
|
|
2701
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
2702
|
-
throw error;
|
|
2703
|
-
} finally {
|
|
2704
|
-
if (operationId) {
|
|
2705
|
-
this._activeOperationJobs.delete(operationId);
|
|
2706
|
-
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2707
|
-
await this._releaseLostOperationConnection(operationId);
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
},
|
|
1779
|
+
async (signal) => this._runConnectorCall(
|
|
1780
|
+
connectId,
|
|
1781
|
+
method,
|
|
1782
|
+
params,
|
|
1783
|
+
signal,
|
|
1784
|
+
fingerprint,
|
|
1785
|
+
permissionDeviceId,
|
|
1786
|
+
commonParams,
|
|
1787
|
+
installContext
|
|
1788
|
+
),
|
|
2712
1789
|
{
|
|
2713
1790
|
label: method,
|
|
2714
1791
|
rejectIfBusy: true,
|
|
@@ -2729,22 +1806,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2729
1806
|
}
|
|
2730
1807
|
});
|
|
2731
1808
|
throw err;
|
|
2732
|
-
} finally {
|
|
2733
|
-
releaseOperationRetention?.();
|
|
2734
1809
|
}
|
|
2735
1810
|
}
|
|
2736
1811
|
/**
|
|
2737
1812
|
* Race a promise against an abort signal. On abort, rejects with
|
|
2738
1813
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
2739
1814
|
*/
|
|
2740
|
-
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2741
|
-
_abortReason(signal) {
|
|
2742
|
-
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2743
|
-
}
|
|
2744
1815
|
_abortable(signal, promise) {
|
|
2745
|
-
const getAbortReason = () => this.
|
|
1816
|
+
const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2746
1817
|
if (signal.aborted) {
|
|
2747
|
-
void promise.catch(() => void 0);
|
|
2748
1818
|
return Promise.reject(getAbortReason());
|
|
2749
1819
|
}
|
|
2750
1820
|
return new Promise((resolve, reject) => {
|
|
@@ -2771,15 +1841,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2771
1841
|
}
|
|
2772
1842
|
}
|
|
2773
1843
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
2774
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext
|
|
1844
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2775
1845
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
);
|
|
2782
|
-
}
|
|
1846
|
+
await this._ensureDevicePermission(
|
|
1847
|
+
connectId,
|
|
1848
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
1849
|
+
signal
|
|
1850
|
+
);
|
|
2783
1851
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2784
1852
|
let effectiveParams = params;
|
|
2785
1853
|
if (method === "btcGetPublicKey") {
|
|
@@ -2793,135 +1861,36 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2793
1861
|
effectiveParams = gatedParams;
|
|
2794
1862
|
}
|
|
2795
1863
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2799
|
-
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2800
|
-
(connection) => connection.transport === knownTransport
|
|
2801
|
-
);
|
|
2802
|
-
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2803
|
-
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
|
|
2804
|
-
const connectionAttempt = { ...commonParams };
|
|
2805
|
-
const bundleConnection = installContext?.connection;
|
|
2806
|
-
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2807
|
-
throw createHwkError({
|
|
2808
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2809
|
-
message: "Ledger all-network connection ended"
|
|
2810
|
-
});
|
|
2811
|
-
}
|
|
2812
|
-
let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2813
|
-
preferredConnectId,
|
|
1864
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1865
|
+
connectId,
|
|
2814
1866
|
signal,
|
|
2815
|
-
allowUsbEphemeralFallback
|
|
2816
|
-
void 0,
|
|
2817
|
-
connectionAttempt
|
|
1867
|
+
allowUsbEphemeralFallback
|
|
2818
1868
|
);
|
|
2819
|
-
|
|
2820
|
-
if (sessionId && installContext && !installContext.connection) {
|
|
2821
|
-
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2822
|
-
}
|
|
1869
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
2823
1870
|
if (!sessionId) {
|
|
2824
|
-
if (operationId) {
|
|
2825
|
-
this._operations.end(operationId, "disconnect");
|
|
2826
|
-
throw createHwkError({
|
|
2827
|
-
code: HardwareErrorCode3.OperationEnded,
|
|
2828
|
-
message: "Ledger operation connection is no longer active",
|
|
2829
|
-
params: { operationId, reason: "disconnect" }
|
|
2830
|
-
});
|
|
2831
|
-
}
|
|
2832
1871
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
2833
1872
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
2834
1873
|
});
|
|
2835
1874
|
}
|
|
2836
1875
|
try {
|
|
2837
1876
|
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
)
|
|
2846
|
-
);
|
|
2847
|
-
if (fp.success) break;
|
|
2848
|
-
const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
|
|
2849
|
-
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
|
|
2850
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2851
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2852
|
-
});
|
|
2853
|
-
}
|
|
2854
|
-
binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
2855
|
-
binding.rejectedConnectIds.add(resolvedConnectId);
|
|
2856
|
-
binding.rejectedConnectId = resolvedConnectId;
|
|
2857
|
-
this._sessions.delete(resolvedConnectId);
|
|
2858
|
-
await this.connector.disconnect(sessionId);
|
|
2859
|
-
if (operationId) this._pendingOperationDisconnects.delete(operationId);
|
|
2860
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
2861
|
-
resolvedConnectId = await this._connectFirstOrSelect(
|
|
2862
|
-
[],
|
|
2863
|
-
void 0,
|
|
2864
|
-
true,
|
|
2865
|
-
operationId,
|
|
2866
|
-
binding,
|
|
2867
|
-
signal
|
|
2868
|
-
);
|
|
2869
|
-
const selectedSession = this._sessions.get(resolvedConnectId);
|
|
2870
|
-
const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
|
|
2871
|
-
if (!selectedSession || !selectedDevice)
|
|
2872
|
-
throw createHwkError({
|
|
2873
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2874
|
-
message: "Selected Ledger connection ended"
|
|
2875
|
-
});
|
|
2876
|
-
sessionId = selectedSession;
|
|
2877
|
-
if (operationId)
|
|
2878
|
-
this._operations.rebind(operationId, {
|
|
2879
|
-
connectId: resolvedConnectId,
|
|
2880
|
-
device: selectedDevice,
|
|
2881
|
-
// Same source as `_createOperation`: the selected transport, not
|
|
2882
|
-
// the device snapshot a session connect overwrote.
|
|
2883
|
-
connectionType: this._isBleConnection() ? "ble" : "usb",
|
|
2884
|
-
connectionKeys: [sessionId]
|
|
2885
|
-
});
|
|
2886
|
-
if (installContext)
|
|
2887
|
-
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2888
|
-
}
|
|
2889
|
-
await this._publishVerifiedBleBinding(
|
|
2890
|
-
resolvedConnectId,
|
|
2891
|
-
fingerprint.chain,
|
|
2892
|
-
fingerprint.deviceId,
|
|
2893
|
-
connectionAttempt,
|
|
2894
|
-
operationId,
|
|
2895
|
-
signal
|
|
1877
|
+
const fp = await this._abortable(
|
|
1878
|
+
signal,
|
|
1879
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1880
|
+
sessionId,
|
|
1881
|
+
fingerprint.deviceId,
|
|
1882
|
+
fingerprint.chain
|
|
1883
|
+
)
|
|
2896
1884
|
);
|
|
2897
|
-
if (!
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
chain: fingerprint.chain,
|
|
2901
|
-
fingerprint: fingerprint.deviceId
|
|
1885
|
+
if (!fp.success) {
|
|
1886
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1887
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2902
1888
|
});
|
|
2903
1889
|
}
|
|
2904
1890
|
}
|
|
2905
|
-
businessCallStarted = true;
|
|
2906
1891
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2907
1892
|
} catch (err) {
|
|
2908
1893
|
if (signal.aborted) throw err;
|
|
2909
|
-
if (isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err)) {
|
|
2910
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2911
|
-
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2912
|
-
else {
|
|
2913
|
-
this._sessions.delete(resolvedConnectId);
|
|
2914
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2915
|
-
}
|
|
2916
|
-
const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2917
|
-
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2918
|
-
throw createHwkError({
|
|
2919
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(err).code,
|
|
2920
|
-
message: "Ledger operation connection was lost; start a new operation",
|
|
2921
|
-
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId }) : interactionParams,
|
|
2922
|
-
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2923
|
-
});
|
|
2924
|
-
}
|
|
2925
1894
|
const errObj = err;
|
|
2926
1895
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
2927
1896
|
message: errObj?.message,
|
|
@@ -2933,65 +1902,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2933
1902
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
2934
1903
|
isStuckApp: isStuckAppStateError(err)
|
|
2935
1904
|
});
|
|
2936
|
-
if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2937
|
-
await this._waitForDeviceConnect(signal);
|
|
2938
|
-
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2939
|
-
throw createHwkError({
|
|
2940
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2941
|
-
message: "Ledger connection ended while waiting for unlock"
|
|
2942
|
-
});
|
|
2943
|
-
}
|
|
2944
|
-
return this._runConnectorCall(
|
|
2945
|
-
resolvedConnectId,
|
|
2946
|
-
method,
|
|
2947
|
-
effectiveParams,
|
|
2948
|
-
signal,
|
|
2949
|
-
fingerprint,
|
|
2950
|
-
permissionDeviceId,
|
|
2951
|
-
commonParams,
|
|
2952
|
-
installContext,
|
|
2953
|
-
operationId,
|
|
2954
|
-
lockedRetryBudget - 1
|
|
2955
|
-
);
|
|
2956
|
-
}
|
|
2957
|
-
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2958
|
-
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2959
|
-
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2960
|
-
throw createHwkError({
|
|
2961
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2962
|
-
message: "Ledger connection ended during the app transition"
|
|
2963
|
-
});
|
|
2964
|
-
}
|
|
2965
|
-
try {
|
|
2966
|
-
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2967
|
-
} catch (retryErr) {
|
|
2968
|
-
if (isStuckAppStateError(retryErr)) throw err;
|
|
2969
|
-
if (isDeviceDisconnectedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isTimeoutError(retryErr) || isConnectionLevelError(retryErr)) {
|
|
2970
|
-
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2971
|
-
else {
|
|
2972
|
-
this._sessions.delete(resolvedConnectId);
|
|
2973
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2974
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2975
|
-
}
|
|
2976
|
-
throw createHwkError({
|
|
2977
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(retryErr).code,
|
|
2978
|
-
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2979
|
-
params: !canReplayHardwareMethodAfterTransportFailure(method) ? operationMayHaveCompletedParams(method, {
|
|
2980
|
-
operationId,
|
|
2981
|
-
reason: "disconnect"
|
|
2982
|
-
}) : { operationId, reason: "disconnect" },
|
|
2983
|
-
recovery: !canReplayHardwareMethodAfterTransportFailure(method) ? { scope: "unknown" } : void 0
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
2986
|
-
throw retryErr;
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
if (!operationId && err?.code === HardwareErrorCode3.DeviceMismatch) {
|
|
2990
|
-
this._sessions.delete(resolvedConnectId);
|
|
2991
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2992
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2993
|
-
throw err;
|
|
2994
|
-
}
|
|
2995
1905
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
2996
1906
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
|
|
2997
1907
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -3047,14 +1957,157 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3047
1957
|
fingerprint,
|
|
3048
1958
|
permissionDeviceId,
|
|
3049
1959
|
commonParams,
|
|
3050
|
-
installContext
|
|
3051
|
-
operationId
|
|
1960
|
+
installContext
|
|
3052
1961
|
);
|
|
3053
1962
|
}
|
|
3054
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
|
|
1987
|
+
);
|
|
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
|
+
}
|
|
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
|
+
}
|
|
3055
2074
|
throw err;
|
|
3056
2075
|
}
|
|
3057
2076
|
}
|
|
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
|
+
}
|
|
3058
2111
|
_sleepAbortable(ms, signal) {
|
|
3059
2112
|
return new Promise((resolve, reject) => {
|
|
3060
2113
|
if (signal.aborted) {
|
|
@@ -3072,6 +2125,88 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3072
2125
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
3073
2126
|
});
|
|
3074
2127
|
}
|
|
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
|
+
}
|
|
3075
2210
|
/**
|
|
3076
2211
|
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
3077
2212
|
*
|
|
@@ -3089,14 +2224,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3089
2224
|
_LedgerAdapter._throwIfAborted(signal);
|
|
3090
2225
|
}
|
|
3091
2226
|
const transportType = this.activeTransport ?? "hid";
|
|
3092
|
-
const operationId = this._activeOperationId();
|
|
3093
2227
|
const waitPromise = this._uiRegistry.wait(
|
|
3094
2228
|
UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3095
|
-
{ timeoutMs: 6e4
|
|
2229
|
+
{ timeoutMs: 6e4 }
|
|
3096
2230
|
);
|
|
3097
2231
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
3098
2232
|
type: UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3099
|
-
payload: { transportType, connectId, deviceId
|
|
2233
|
+
payload: { transportType, connectId, deviceId }
|
|
3100
2234
|
});
|
|
3101
2235
|
let response;
|
|
3102
2236
|
const onAbort = () => {
|
|
@@ -3142,15 +2276,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3142
2276
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
3143
2277
|
const e = err;
|
|
3144
2278
|
const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
3145
|
-
return ledgerFailure(
|
|
3146
|
-
e.code,
|
|
3147
|
-
e.message ?? "Unknown error",
|
|
3148
|
-
e.appName,
|
|
3149
|
-
tag,
|
|
3150
|
-
params,
|
|
3151
|
-
void 0,
|
|
3152
|
-
isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
|
|
3153
|
-
);
|
|
2279
|
+
return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
|
|
3154
2280
|
}
|
|
3155
2281
|
const mapped = mapLedgerError(err);
|
|
3156
2282
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -3177,7 +2303,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3177
2303
|
deviceId: device.deviceId,
|
|
3178
2304
|
connectId: device.connectId,
|
|
3179
2305
|
label: device.name,
|
|
3180
|
-
connectionType:
|
|
2306
|
+
connectionType: this.connector.connectionType,
|
|
3181
2307
|
rssi: device.rssi,
|
|
3182
2308
|
isConnectable: device.isConnectable,
|
|
3183
2309
|
serialNumber: device.serialNumber,
|
|
@@ -3203,8 +2329,8 @@ var LedgerAdapter = _LedgerAdapter;
|
|
|
3203
2329
|
|
|
3204
2330
|
// src/connector/LedgerConnectorBase.ts
|
|
3205
2331
|
import {
|
|
3206
|
-
EConnectorInteraction as
|
|
3207
|
-
HardwareErrorCode as
|
|
2332
|
+
EConnectorInteraction as EConnectorInteraction4,
|
|
2333
|
+
HardwareErrorCode as HardwareErrorCode8,
|
|
3208
2334
|
isKnownNonTargetHardwareVendor,
|
|
3209
2335
|
serializeConnectorError
|
|
3210
2336
|
} from "@onekeyfe/hwk-adapter-core";
|
|
@@ -3477,7 +2603,7 @@ var LedgerDeviceManager = class {
|
|
|
3477
2603
|
|
|
3478
2604
|
// src/signer/SignerManager.ts
|
|
3479
2605
|
import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
|
|
3480
|
-
import { ContextModuleBuilder
|
|
2606
|
+
import { ContextModuleBuilder } from "@ledgerhq/context-module";
|
|
3481
2607
|
|
|
3482
2608
|
// src/signer/SignerEth.ts
|
|
3483
2609
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
@@ -3743,7 +2869,7 @@ var SignerManager = class _SignerManager {
|
|
|
3743
2869
|
return (args) => new SignerEthBuilder(args);
|
|
3744
2870
|
}
|
|
3745
2871
|
static _createContextModule() {
|
|
3746
|
-
const contextModule = new ContextModuleBuilder({}).
|
|
2872
|
+
const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3747
2873
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
3748
2874
|
}
|
|
3749
2875
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -4213,8 +3339,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
4213
3339
|
}
|
|
4214
3340
|
|
|
4215
3341
|
// src/connector/chains/sol.ts
|
|
4216
|
-
import { bytesToHex, hexToBytes as hexToBytes3
|
|
4217
|
-
import bs58 from "bs58";
|
|
3342
|
+
import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
|
|
4218
3343
|
|
|
4219
3344
|
// src/signer/SignerSol.ts
|
|
4220
3345
|
var SignerSol = class {
|
|
@@ -4298,21 +3423,8 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
4298
3423
|
const path = normalizePath(params.path);
|
|
4299
3424
|
const messageBytes = hexToBytes3(params.message);
|
|
4300
3425
|
try {
|
|
4301
|
-
if (params.messageVersion === 1) {
|
|
4302
|
-
const preparedMessage = prepareSolanaOffchainMessageV1({
|
|
4303
|
-
message: messageBytes,
|
|
4304
|
-
requiredSigners: params.requiredSigners
|
|
4305
|
-
});
|
|
4306
|
-
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4307
|
-
"@ledgerhq/device-signer-kit-solana"
|
|
4308
|
-
);
|
|
4309
|
-
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4310
|
-
version: SignMessageVersion.Raw
|
|
4311
|
-
});
|
|
4312
|
-
return { signature: decodeBase58Signature(result2.signature) };
|
|
4313
|
-
}
|
|
4314
3426
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
4315
|
-
return { signature:
|
|
3427
|
+
return { signature: result.signature };
|
|
4316
3428
|
} catch (err) {
|
|
4317
3429
|
ctx.invalidateSession(sessionId);
|
|
4318
3430
|
throw ctx.wrapError(err);
|
|
@@ -4320,27 +3432,11 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
4320
3432
|
ctx.clearCanceller(sessionId);
|
|
4321
3433
|
}
|
|
4322
3434
|
}
|
|
4323
|
-
function decodeBase58Signature(signature) {
|
|
4324
|
-
const bytes = bs58.decode(signature);
|
|
4325
|
-
if (bytes.length !== 64) {
|
|
4326
|
-
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4327
|
-
}
|
|
4328
|
-
return bytesToHex(bytes);
|
|
4329
|
-
}
|
|
4330
|
-
function decodeBase58EnvelopeSignature(envelope) {
|
|
4331
|
-
const bytes = bs58.decode(envelope);
|
|
4332
|
-
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4333
|
-
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4334
|
-
}
|
|
4335
|
-
return bytesToHex(bytes.subarray(1, 65));
|
|
4336
|
-
}
|
|
4337
3435
|
async function _createSolSigner(ctx, sessionId) {
|
|
4338
3436
|
const dmk = await ctx.getOrCreateDmk();
|
|
4339
|
-
const { ContextModuleBuilder: ContextModuleBuilder2
|
|
4340
|
-
"@ledgerhq/context-module"
|
|
4341
|
-
);
|
|
3437
|
+
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit("@ledgerhq/context-module");
|
|
4342
3438
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
4343
|
-
const contextModule = new ContextModuleBuilder2({}).
|
|
3439
|
+
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4344
3440
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
4345
3441
|
const signer = new SignerSol(sdkSigner);
|
|
4346
3442
|
signer.onInteraction = (interaction) => {
|
|
@@ -4356,192 +3452,361 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
4356
3452
|
return signer;
|
|
4357
3453
|
}
|
|
4358
3454
|
|
|
4359
|
-
// src/connector/chains/
|
|
4360
|
-
import {
|
|
3455
|
+
// src/connector/chains/tron.ts
|
|
3456
|
+
import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
|
|
3457
|
+
import Trx from "@ledgerhq/hw-app-trx";
|
|
4361
3458
|
|
|
4362
|
-
// src/
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
3459
|
+
// src/connector/chains/legacyChainCall.ts
|
|
3460
|
+
import { EConnectorInteraction as EConnectorInteraction3 } from "@onekeyfe/hwk-adapter-core";
|
|
3461
|
+
|
|
3462
|
+
// src/app/AppManager.ts
|
|
3463
|
+
import {
|
|
3464
|
+
CloseAppCommand,
|
|
3465
|
+
GetAppAndVersionCommand,
|
|
3466
|
+
OpenAppCommand,
|
|
3467
|
+
isSuccessCommandResult
|
|
3468
|
+
} from "@ledgerhq/device-management-kit";
|
|
3469
|
+
import { HardwareErrorCode as HardwareErrorCode6 } from "@onekeyfe/hwk-adapter-core";
|
|
3470
|
+
var APP_NAME_MAP = {
|
|
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;
|
|
4367
3486
|
}
|
|
4368
|
-
/**
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
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
|
+
}
|
|
4376
3565
|
);
|
|
4377
3566
|
}
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
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
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
async _closeCurrentApp(sessionId) {
|
|
3601
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
3602
|
+
await this._dmk.sendCommand({
|
|
3603
|
+
sessionId,
|
|
3604
|
+
command: new CloseAppCommand()
|
|
3605
|
+
});
|
|
3606
|
+
}
|
|
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
|
|
4386
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));
|
|
4387
3636
|
}
|
|
4388
3637
|
};
|
|
4389
3638
|
|
|
4390
|
-
// src/connector/chains/
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
3639
|
+
// src/connector/chains/legacyChainCall.ts
|
|
3640
|
+
function isLegacyWrongAppError(err, _appName) {
|
|
3641
|
+
return isWrongAppError(err);
|
|
3642
|
+
}
|
|
3643
|
+
async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
3644
|
+
const { appName, needsConfirmation } = options;
|
|
3645
|
+
let openAppPromptShown = false;
|
|
3646
|
+
const onAppOpenPrompt = () => {
|
|
3647
|
+
openAppPromptShown = true;
|
|
3648
|
+
ctx.emit("ui-event", {
|
|
3649
|
+
type: EConnectorInteraction3.ConfirmOpenApp,
|
|
3650
|
+
payload: { sessionId }
|
|
3651
|
+
});
|
|
3652
|
+
};
|
|
3653
|
+
const closeOpenAppUiIfShown = () => {
|
|
3654
|
+
if (openAppPromptShown) {
|
|
3655
|
+
ctx.emit("ui-event", {
|
|
3656
|
+
type: EConnectorInteraction3.InteractionComplete,
|
|
3657
|
+
payload: { sessionId }
|
|
3658
|
+
});
|
|
3659
|
+
openAppPromptShown = false;
|
|
4399
3660
|
}
|
|
4400
|
-
|
|
3661
|
+
};
|
|
3662
|
+
try {
|
|
3663
|
+
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
4401
3664
|
} catch (err) {
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
const
|
|
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
|
+
};
|
|
4411
3694
|
try {
|
|
4412
|
-
|
|
4413
|
-
checkOnDevice: params.showOnDevice ?? false
|
|
4414
|
-
});
|
|
4415
|
-
return { address: result.address, path: params.path };
|
|
3695
|
+
return await runOnce();
|
|
4416
3696
|
} catch (err) {
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
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;
|
|
4421
3718
|
}
|
|
4422
3719
|
}
|
|
4423
|
-
async function
|
|
3720
|
+
async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
|
|
4424
3721
|
const dmk = await ctx.getOrCreateDmk();
|
|
4425
|
-
const
|
|
4426
|
-
|
|
4427
|
-
const signer = new SignerZcash(sdkSigner);
|
|
4428
|
-
signer.onInteraction = (interaction) => {
|
|
4429
|
-
debugLog("[LedgerConnector] zcash.onInteraction:", interaction);
|
|
4430
|
-
ctx.emit("ui-event", {
|
|
4431
|
-
type: collapseSignerInteraction(interaction),
|
|
4432
|
-
payload: { sessionId }
|
|
4433
|
-
});
|
|
4434
|
-
};
|
|
4435
|
-
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
4436
|
-
return signer;
|
|
3722
|
+
const appManager = new AppManager(dmk);
|
|
3723
|
+
await appManager.ensureAppOpen(sessionId, appName, onPrompt);
|
|
4437
3724
|
}
|
|
4438
3725
|
|
|
4439
|
-
// src/
|
|
4440
|
-
import
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
this._sdk = _sdk;
|
|
4447
|
-
}
|
|
4448
|
-
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4449
|
-
async getAddress(derivationPath, options) {
|
|
4450
|
-
const action = this._sdk.getAddress(derivationPath, options);
|
|
4451
|
-
const result = await deviceActionToPromise(
|
|
4452
|
-
action,
|
|
4453
|
-
this.onInteraction,
|
|
4454
|
-
void 0,
|
|
4455
|
-
this.onRegisterCanceller
|
|
4456
|
-
);
|
|
4457
|
-
return { address: result.address, publicKey: result.publicKey };
|
|
4458
|
-
}
|
|
4459
|
-
/** Sign a protobuf-encoded raw transaction. */
|
|
4460
|
-
async signTransaction(derivationPath, transaction, options) {
|
|
4461
|
-
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
4462
|
-
return deviceActionToPromise(
|
|
4463
|
-
action,
|
|
4464
|
-
this.onInteraction,
|
|
4465
|
-
void 0,
|
|
4466
|
-
this.onRegisterCanceller
|
|
4467
|
-
);
|
|
3726
|
+
// src/transport/DmkTransport.ts
|
|
3727
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
3728
|
+
var DmkTransport = class extends Transport {
|
|
3729
|
+
constructor(dmk, sessionId) {
|
|
3730
|
+
super();
|
|
3731
|
+
this._dmk = dmk;
|
|
3732
|
+
this._sessionId = sessionId;
|
|
4468
3733
|
}
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
3734
|
+
async exchange(apdu) {
|
|
3735
|
+
const response = await this._dmk.sendApdu({
|
|
3736
|
+
sessionId: this._sessionId,
|
|
3737
|
+
apdu: new Uint8Array(apdu)
|
|
3738
|
+
});
|
|
3739
|
+
const { data, statusCode } = response;
|
|
3740
|
+
const result = Buffer.alloc(data.length + 2);
|
|
3741
|
+
if (data.length > 0) {
|
|
3742
|
+
result.set(data, 0);
|
|
3743
|
+
}
|
|
3744
|
+
result.set(statusCode, data.length);
|
|
3745
|
+
return result;
|
|
3746
|
+
}
|
|
3747
|
+
async close() {
|
|
4478
3748
|
}
|
|
4479
3749
|
};
|
|
4480
3750
|
|
|
4481
3751
|
// src/connector/chains/tron.ts
|
|
4482
3752
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4483
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4484
3753
|
const path = normalizePath(params.path);
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
3754
|
+
const showOnDevice = params.showOnDevice ?? false;
|
|
3755
|
+
return withLegacyChainCall(
|
|
3756
|
+
ctx,
|
|
3757
|
+
sessionId,
|
|
3758
|
+
{
|
|
3759
|
+
appName: "Tron",
|
|
3760
|
+
// Only show "confirm on device" UI when the device is actually going
|
|
3761
|
+
// to display the address for the user to verify.
|
|
3762
|
+
needsConfirmation: showOnDevice
|
|
3763
|
+
},
|
|
3764
|
+
async (sid) => {
|
|
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
|
+
);
|
|
4496
3770
|
}
|
|
4497
3771
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
4498
3772
|
if (!params.rawTxHex) {
|
|
4499
3773
|
throw Object.assign(
|
|
4500
3774
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
4501
|
-
{ code:
|
|
3775
|
+
{ code: HardwareErrorCode7.InvalidParams }
|
|
4502
3776
|
);
|
|
4503
3777
|
}
|
|
4504
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4505
3778
|
const path = normalizePath(params.path);
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
3779
|
+
return withLegacyChainCall(
|
|
3780
|
+
ctx,
|
|
3781
|
+
sessionId,
|
|
3782
|
+
{ appName: "Tron", needsConfirmation: true },
|
|
3783
|
+
async (sid) => {
|
|
3784
|
+
const trx = await _createTrx(ctx, sid);
|
|
3785
|
+
const signature = await trx.signTransaction(
|
|
3786
|
+
path,
|
|
3787
|
+
params.rawTxHex,
|
|
3788
|
+
params.tokenSignatures ?? []
|
|
3789
|
+
);
|
|
3790
|
+
return { signature };
|
|
3791
|
+
}
|
|
3792
|
+
);
|
|
4515
3793
|
}
|
|
4516
3794
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4517
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4518
3795
|
const path = normalizePath(params.path);
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
3796
|
+
return withLegacyChainCall(
|
|
3797
|
+
ctx,
|
|
3798
|
+
sessionId,
|
|
3799
|
+
{ appName: "Tron", needsConfirmation: true },
|
|
3800
|
+
async (sid) => {
|
|
3801
|
+
const trx = await _createTrx(ctx, sid);
|
|
3802
|
+
const signature = await trx.signPersonalMessage(path, params.messageHex);
|
|
3803
|
+
return { signature };
|
|
3804
|
+
}
|
|
3805
|
+
);
|
|
4528
3806
|
}
|
|
4529
|
-
async function
|
|
3807
|
+
async function _createTrx(ctx, sessionId) {
|
|
4530
3808
|
const dmk = await ctx.getOrCreateDmk();
|
|
4531
|
-
|
|
4532
|
-
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4533
|
-
const signer = new SignerTron(sdkSigner);
|
|
4534
|
-
signer.onInteraction = (interaction) => {
|
|
4535
|
-
debugLog("[LedgerConnector] tron.onInteraction:", interaction);
|
|
4536
|
-
ctx.emit("ui-event", {
|
|
4537
|
-
type: collapseSignerInteraction(interaction),
|
|
4538
|
-
payload: { sessionId }
|
|
4539
|
-
});
|
|
4540
|
-
};
|
|
4541
|
-
signer.onRegisterCanceller = (cancel) => {
|
|
4542
|
-
ctx.registerCanceller(sessionId, cancel);
|
|
4543
|
-
};
|
|
4544
|
-
return signer;
|
|
3809
|
+
return new Trx(new DmkTransport(dmk, sessionId));
|
|
4545
3810
|
}
|
|
4546
3811
|
|
|
4547
3812
|
// src/device-apps/customActions.ts
|
|
@@ -4720,7 +3985,7 @@ var DeviceApps = class {
|
|
|
4720
3985
|
seTargetId: v.seTargetId,
|
|
4721
3986
|
mcuTargetId: v.mcuTargetId,
|
|
4722
3987
|
seVersion: v.seVersion,
|
|
4723
|
-
seFlagsHex:
|
|
3988
|
+
seFlagsHex: bytesToHex2(v.seFlags),
|
|
4724
3989
|
mcuSephVersion: v.mcuSephVersion,
|
|
4725
3990
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
4726
3991
|
hwVersion: v.hwVersion
|
|
@@ -4794,7 +4059,7 @@ var DeviceApps = class {
|
|
|
4794
4059
|
}
|
|
4795
4060
|
}
|
|
4796
4061
|
};
|
|
4797
|
-
function
|
|
4062
|
+
function bytesToHex2(bytes) {
|
|
4798
4063
|
if (!bytes) return "";
|
|
4799
4064
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4800
4065
|
}
|
|
@@ -4837,11 +4102,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4837
4102
|
evm: "Ethereum",
|
|
4838
4103
|
btc: "Bitcoin",
|
|
4839
4104
|
sol: "Solana",
|
|
4840
|
-
tron: "Tron"
|
|
4841
|
-
zcash: "Zcash"
|
|
4105
|
+
tron: "Tron"
|
|
4842
4106
|
};
|
|
4843
4107
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4844
|
-
Object.values(
|
|
4108
|
+
Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
|
|
4845
4109
|
);
|
|
4846
4110
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4847
4111
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4878,10 +4142,6 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4878
4142
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4879
4143
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4880
4144
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4881
|
-
case "@ledgerhq/device-signer-kit-tron":
|
|
4882
|
-
return import("@ledgerhq/device-signer-kit-tron");
|
|
4883
|
-
case "@ledgerhq/device-signer-kit-zcash":
|
|
4884
|
-
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4885
4145
|
case "@ledgerhq/context-module":
|
|
4886
4146
|
return import("@ledgerhq/context-module");
|
|
4887
4147
|
default:
|
|
@@ -4941,7 +4201,6 @@ var LedgerConnectorBase = class {
|
|
|
4941
4201
|
this._ctx = {
|
|
4942
4202
|
emit: (event, data) => this._emit(event, data),
|
|
4943
4203
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4944
|
-
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4945
4204
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4946
4205
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4947
4206
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -5041,7 +4300,7 @@ var LedgerConnectorBase = class {
|
|
|
5041
4300
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
5042
4301
|
);
|
|
5043
4302
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
5044
|
-
err.code =
|
|
4303
|
+
err.code = HardwareErrorCode8.BlePairingTimeout;
|
|
5045
4304
|
reject(err);
|
|
5046
4305
|
}, HANG_CEILING_MS);
|
|
5047
4306
|
});
|
|
@@ -5074,7 +4333,7 @@ var LedgerConnectorBase = class {
|
|
|
5074
4333
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
5075
4334
|
);
|
|
5076
4335
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
5077
|
-
err.code =
|
|
4336
|
+
err.code = HardwareErrorCode8.DeviceNotFound;
|
|
5078
4337
|
throw err;
|
|
5079
4338
|
};
|
|
5080
4339
|
const doConnect = async (path) => {
|
|
@@ -5131,14 +4390,14 @@ var LedgerConnectorBase = class {
|
|
|
5131
4390
|
this._resetSignersAndSessions();
|
|
5132
4391
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
5133
4392
|
const tag = err?._tag;
|
|
5134
|
-
if (isKnownConnectionTag(tag)
|
|
4393
|
+
if (isKnownConnectionTag(tag)) {
|
|
5135
4394
|
throw err;
|
|
5136
4395
|
}
|
|
5137
4396
|
const wrapped = new Error(
|
|
5138
4397
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
5139
4398
|
);
|
|
5140
4399
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
5141
|
-
wrapped.code =
|
|
4400
|
+
wrapped.code = HardwareErrorCode8.BlePairingTimeout;
|
|
5142
4401
|
wrapped.originalError = err;
|
|
5143
4402
|
throw wrapped;
|
|
5144
4403
|
}
|
|
@@ -5216,7 +4475,7 @@ var LedgerConnectorBase = class {
|
|
|
5216
4475
|
this._unwatchSessionState(sessionId);
|
|
5217
4476
|
this._signerManager?.invalidate(sessionId);
|
|
5218
4477
|
this._cancellers.get(sessionId)?.({
|
|
5219
|
-
code:
|
|
4478
|
+
code: HardwareErrorCode8.DeviceDisconnected,
|
|
5220
4479
|
tag: "DeviceDisconnected",
|
|
5221
4480
|
message: "Device disconnected"
|
|
5222
4481
|
});
|
|
@@ -5242,7 +4501,7 @@ var LedgerConnectorBase = class {
|
|
|
5242
4501
|
success: false,
|
|
5243
4502
|
error: serializeConnectorError(
|
|
5244
4503
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
5245
|
-
code:
|
|
4504
|
+
code: HardwareErrorCode8.DeviceAppStuck,
|
|
5246
4505
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
5247
4506
|
originalError: err
|
|
5248
4507
|
})
|
|
@@ -5254,7 +4513,7 @@ var LedgerConnectorBase = class {
|
|
|
5254
4513
|
success: false,
|
|
5255
4514
|
error: serializeConnectorError(
|
|
5256
4515
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
5257
|
-
code:
|
|
4516
|
+
code: HardwareErrorCode8.TransportError,
|
|
5258
4517
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
5259
4518
|
originalError: err
|
|
5260
4519
|
})
|
|
@@ -5300,11 +4559,6 @@ var LedgerConnectorBase = class {
|
|
|
5300
4559
|
return solSignTransaction(ctx, sessionId, params);
|
|
5301
4560
|
case "solSignMessage":
|
|
5302
4561
|
return solSignMessage(ctx, sessionId, params);
|
|
5303
|
-
// ZCASH
|
|
5304
|
-
case "zcashGetFullViewingKey":
|
|
5305
|
-
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5306
|
-
case "zcashGetShieldedAddress":
|
|
5307
|
-
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
5308
4562
|
// TRON
|
|
5309
4563
|
case "tronGetAddress":
|
|
5310
4564
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -5329,7 +4583,7 @@ var LedgerConnectorBase = class {
|
|
|
5329
4583
|
try {
|
|
5330
4584
|
return await apps.install(p.appName, ({ progress }) => {
|
|
5331
4585
|
ctx.emit("ui-event", {
|
|
5332
|
-
type:
|
|
4586
|
+
type: EConnectorInteraction4.AppInstallProgress,
|
|
5333
4587
|
payload: {
|
|
5334
4588
|
sessionId,
|
|
5335
4589
|
appName: p.appName,
|
|
@@ -5338,7 +4592,7 @@ var LedgerConnectorBase = class {
|
|
|
5338
4592
|
});
|
|
5339
4593
|
});
|
|
5340
4594
|
} catch (err) {
|
|
5341
|
-
ctx.
|
|
4595
|
+
ctx.invalidateSession(sessionId);
|
|
5342
4596
|
throw ctx.wrapError(err);
|
|
5343
4597
|
} finally {
|
|
5344
4598
|
ctx.clearCanceller(sessionId);
|
|
@@ -5428,7 +4682,7 @@ var LedgerConnectorBase = class {
|
|
|
5428
4682
|
);
|
|
5429
4683
|
return { isGenuine: output.isGenuine, deviceId };
|
|
5430
4684
|
} catch (err) {
|
|
5431
|
-
ctx.
|
|
4685
|
+
ctx.invalidateSession(sessionId);
|
|
5432
4686
|
throw ctx.wrapError(err);
|
|
5433
4687
|
} finally {
|
|
5434
4688
|
ctx.clearCanceller(sessionId);
|
|
@@ -5547,34 +4801,9 @@ var LedgerConnectorBase = class {
|
|
|
5547
4801
|
}
|
|
5548
4802
|
return this._deviceAppsManager;
|
|
5549
4803
|
}
|
|
5550
|
-
// DeviceAppsManager is intentionally absent here: it is a per-call factory
|
|
5551
|
-
// with no cached session state, so there is nothing to invalidate. Cancelling
|
|
5552
|
-
// the device action is what closes the secure channel.
|
|
5553
4804
|
_invalidateSession(sessionId) {
|
|
5554
4805
|
this._signerManager?.invalidate(sessionId);
|
|
5555
4806
|
}
|
|
5556
|
-
/**
|
|
5557
|
-
* Teardown for the OS-level actions that open a manager-api secure channel
|
|
5558
|
-
* (install, uninstall, genuine check). Cancelling the device action is what
|
|
5559
|
-
* stops DMK's xstate actor, which unsubscribes the secure-channel observable
|
|
5560
|
-
* and closes its WebSocket; dropping the canceller without firing it leaves
|
|
5561
|
-
* that teardown to run only if the action settled on its own.
|
|
5562
|
-
*
|
|
5563
|
-
* The DMK device session is deliberately kept: the secure channel is a
|
|
5564
|
-
* separate WebSocket per call, and disconnecting here would break the bounded
|
|
5565
|
-
* unlock/retry recovery the adapter runs on the original session.
|
|
5566
|
-
*/
|
|
5567
|
-
_teardownSecureChannelSession(sessionId) {
|
|
5568
|
-
const cancel = this._cancellers.get(sessionId);
|
|
5569
|
-
this._cancellers.delete(sessionId);
|
|
5570
|
-
if (cancel) {
|
|
5571
|
-
try {
|
|
5572
|
-
cancel();
|
|
5573
|
-
} catch {
|
|
5574
|
-
}
|
|
5575
|
-
}
|
|
5576
|
-
this._invalidateSession(sessionId);
|
|
5577
|
-
}
|
|
5578
4807
|
/**
|
|
5579
4808
|
* Replace an old session with a new one after app switch.
|
|
5580
4809
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -5659,7 +4888,7 @@ var LedgerConnectorBase = class {
|
|
|
5659
4888
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
5660
4889
|
*/
|
|
5661
4890
|
_ctxForMethod(method) {
|
|
5662
|
-
const prefix = /^(evm|btc|sol|tron
|
|
4891
|
+
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5663
4892
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
5664
4893
|
if (!defaultAppName) return this._ctx;
|
|
5665
4894
|
return {
|
|
@@ -5693,209 +4922,6 @@ var LedgerConnectorBase = class {
|
|
|
5693
4922
|
return error;
|
|
5694
4923
|
}
|
|
5695
4924
|
};
|
|
5696
|
-
|
|
5697
|
-
// src/transport/DmkTransport.ts
|
|
5698
|
-
import Transport from "@ledgerhq/hw-transport";
|
|
5699
|
-
var DmkTransport = class extends Transport {
|
|
5700
|
-
constructor(dmk, sessionId) {
|
|
5701
|
-
super();
|
|
5702
|
-
this._dmk = dmk;
|
|
5703
|
-
this._sessionId = sessionId;
|
|
5704
|
-
}
|
|
5705
|
-
async exchange(apdu) {
|
|
5706
|
-
const response = await this._dmk.sendApdu({
|
|
5707
|
-
sessionId: this._sessionId,
|
|
5708
|
-
apdu: new Uint8Array(apdu)
|
|
5709
|
-
});
|
|
5710
|
-
const { data, statusCode } = response;
|
|
5711
|
-
const result = Buffer.alloc(data.length + 2);
|
|
5712
|
-
if (data.length > 0) {
|
|
5713
|
-
result.set(data, 0);
|
|
5714
|
-
}
|
|
5715
|
-
result.set(statusCode, data.length);
|
|
5716
|
-
return result;
|
|
5717
|
-
}
|
|
5718
|
-
async close() {
|
|
5719
|
-
}
|
|
5720
|
-
};
|
|
5721
|
-
|
|
5722
|
-
// src/app/AppManager.ts
|
|
5723
|
-
import {
|
|
5724
|
-
CloseAppCommand,
|
|
5725
|
-
GetAppAndVersionCommand,
|
|
5726
|
-
OpenAppCommand,
|
|
5727
|
-
isSuccessCommandResult
|
|
5728
|
-
} from "@ledgerhq/device-management-kit";
|
|
5729
|
-
import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
|
|
5730
|
-
var APP_NAME_MAP = {
|
|
5731
|
-
ETH: "Ethereum",
|
|
5732
|
-
BTC: "Bitcoin",
|
|
5733
|
-
SOL: "Solana",
|
|
5734
|
-
TRX: "Tron",
|
|
5735
|
-
ZEC: "Zcash",
|
|
5736
|
-
XRP: "XRP",
|
|
5737
|
-
ADA: "Cardano",
|
|
5738
|
-
DOT: "Polkadot",
|
|
5739
|
-
ATOM: "Cosmos"
|
|
5740
|
-
};
|
|
5741
|
-
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5742
|
-
var AppManager = class {
|
|
5743
|
-
constructor(dmk, options) {
|
|
5744
|
-
this._dmk = dmk;
|
|
5745
|
-
this._waitMs = options?.waitMs ?? 1e3;
|
|
5746
|
-
this._maxRetries = options?.maxRetries ?? 10;
|
|
5747
|
-
}
|
|
5748
|
-
/**
|
|
5749
|
-
* Return the Ledger app name for a given chain ticker,
|
|
5750
|
-
* or undefined if the chain is not supported.
|
|
5751
|
-
*/
|
|
5752
|
-
static getAppName(chain) {
|
|
5753
|
-
return APP_NAME_MAP[chain];
|
|
5754
|
-
}
|
|
5755
|
-
/**
|
|
5756
|
-
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5757
|
-
*
|
|
5758
|
-
* Flow:
|
|
5759
|
-
* 1. Check the currently running app.
|
|
5760
|
-
* 2. If it is already the target, return immediately.
|
|
5761
|
-
* 3. If a different app is running (not dashboard), close it first.
|
|
5762
|
-
* 4. Open the target app.
|
|
5763
|
-
* 5. Poll until the device confirms the target app is running.
|
|
5764
|
-
*/
|
|
5765
|
-
/**
|
|
5766
|
-
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5767
|
-
* device is about to display "Open <app>" on screen and wait for the
|
|
5768
|
-
* user's button press. UI consumers should show their "open app" prompt
|
|
5769
|
-
* in response. NOT called when the target app is already open (no user
|
|
5770
|
-
* interaction needed in that case).
|
|
5771
|
-
*
|
|
5772
|
-
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5773
|
-
* has physically confirmed on the device, so anything that runs AFTER
|
|
5774
|
-
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5775
|
-
* Hence the callback must fire BEFORE that await.
|
|
5776
|
-
*/
|
|
5777
|
-
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5778
|
-
const currentApp = await this._getCurrentApp(sessionId);
|
|
5779
|
-
if (currentApp === targetAppName) {
|
|
5780
|
-
return;
|
|
5781
|
-
}
|
|
5782
|
-
if (!this._isDashboard(currentApp)) {
|
|
5783
|
-
await this._closeCurrentApp(sessionId);
|
|
5784
|
-
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5785
|
-
}
|
|
5786
|
-
onConfirmOnDevice?.();
|
|
5787
|
-
await this._openApp(sessionId, targetAppName);
|
|
5788
|
-
await this._waitForApp(sessionId, targetAppName);
|
|
5789
|
-
}
|
|
5790
|
-
// ---------------------------------------------------------------------------
|
|
5791
|
-
// Private helpers
|
|
5792
|
-
// ---------------------------------------------------------------------------
|
|
5793
|
-
async _getCurrentApp(sessionId) {
|
|
5794
|
-
const result = await this._dmk.sendCommand({
|
|
5795
|
-
sessionId,
|
|
5796
|
-
command: new GetAppAndVersionCommand()
|
|
5797
|
-
});
|
|
5798
|
-
if (isSuccessCommandResult(result)) {
|
|
5799
|
-
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5800
|
-
return result.data.name;
|
|
5801
|
-
}
|
|
5802
|
-
const errResult = result;
|
|
5803
|
-
const dmkErr = errResult.error ?? {};
|
|
5804
|
-
const original = dmkErr.originalError;
|
|
5805
|
-
debugLog(
|
|
5806
|
-
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5807
|
-
sessionId,
|
|
5808
|
-
"tag=",
|
|
5809
|
-
dmkErr._tag,
|
|
5810
|
-
"errorCode=",
|
|
5811
|
-
dmkErr.errorCode,
|
|
5812
|
-
"message=",
|
|
5813
|
-
dmkErr.message,
|
|
5814
|
-
"originalErrorMessage=",
|
|
5815
|
-
original?.message ?? String(original ?? "")
|
|
5816
|
-
);
|
|
5817
|
-
throw Object.assign(
|
|
5818
|
-
new Error(
|
|
5819
|
-
dmkErr.message ?? "Failed to get current app from device"
|
|
5820
|
-
),
|
|
5821
|
-
{
|
|
5822
|
-
_tag: dmkErr._tag,
|
|
5823
|
-
errorCode: dmkErr.errorCode,
|
|
5824
|
-
originalError: original
|
|
5825
|
-
}
|
|
5826
|
-
);
|
|
5827
|
-
}
|
|
5828
|
-
async _openApp(sessionId, appName) {
|
|
5829
|
-
const result = await this._dmk.sendCommand({
|
|
5830
|
-
sessionId,
|
|
5831
|
-
command: new OpenAppCommand({ appName })
|
|
5832
|
-
});
|
|
5833
|
-
if (!isSuccessCommandResult(result)) {
|
|
5834
|
-
const dmkErr = result.error;
|
|
5835
|
-
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5836
|
-
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5837
|
-
debugLog(
|
|
5838
|
-
"[AppManager] openApp failed:",
|
|
5839
|
-
appName,
|
|
5840
|
-
"errorCode:",
|
|
5841
|
-
errorCode,
|
|
5842
|
-
"tag:",
|
|
5843
|
-
dmkErr._tag
|
|
5844
|
-
);
|
|
5845
|
-
let code;
|
|
5846
|
-
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5847
|
-
code = HardwareErrorCode8.AppNotInstalled;
|
|
5848
|
-
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5849
|
-
code = HardwareErrorCode8.UserRejected;
|
|
5850
|
-
}
|
|
5851
|
-
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5852
|
-
_tag: ERROR_TAG.OpenAppCommand,
|
|
5853
|
-
code,
|
|
5854
|
-
errorCode,
|
|
5855
|
-
statusCode: errorCode,
|
|
5856
|
-
appName,
|
|
5857
|
-
originalError: dmkErr
|
|
5858
|
-
});
|
|
5859
|
-
}
|
|
5860
|
-
}
|
|
5861
|
-
async _closeCurrentApp(sessionId) {
|
|
5862
|
-
debugLog("[AppManager] closeCurrentApp");
|
|
5863
|
-
await this._dmk.sendCommand({
|
|
5864
|
-
sessionId,
|
|
5865
|
-
command: new CloseAppCommand()
|
|
5866
|
-
});
|
|
5867
|
-
}
|
|
5868
|
-
/**
|
|
5869
|
-
* Poll the device until the expected app is reported as running,
|
|
5870
|
-
* or throw after `_maxRetries` attempts.
|
|
5871
|
-
*/
|
|
5872
|
-
async _waitForApp(sessionId, expectedAppName) {
|
|
5873
|
-
let lastSeen = "";
|
|
5874
|
-
for (let i = 0; i < this._maxRetries; i++) {
|
|
5875
|
-
await this._wait();
|
|
5876
|
-
const current = await this._getCurrentApp(sessionId);
|
|
5877
|
-
lastSeen = current;
|
|
5878
|
-
if (current === expectedAppName) {
|
|
5879
|
-
return;
|
|
5880
|
-
}
|
|
5881
|
-
}
|
|
5882
|
-
debugLog(
|
|
5883
|
-
"[AppManager] waitForApp exhausted: expected=",
|
|
5884
|
-
expectedAppName,
|
|
5885
|
-
"lastSeen=",
|
|
5886
|
-
lastSeen
|
|
5887
|
-
);
|
|
5888
|
-
throw new Error(
|
|
5889
|
-
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5890
|
-
);
|
|
5891
|
-
}
|
|
5892
|
-
_isDashboard(appName) {
|
|
5893
|
-
return appName === DASHBOARD_APP_NAME;
|
|
5894
|
-
}
|
|
5895
|
-
_wait() {
|
|
5896
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5897
|
-
}
|
|
5898
|
-
};
|
|
5899
4925
|
export {
|
|
5900
4926
|
AppManager,
|
|
5901
4927
|
DmkTransport,
|
|
@@ -5906,7 +4932,6 @@ export {
|
|
|
5906
4932
|
SignerEth,
|
|
5907
4933
|
SignerManager,
|
|
5908
4934
|
SignerSol,
|
|
5909
|
-
SignerZcash,
|
|
5910
4935
|
debugLog,
|
|
5911
4936
|
deviceActionToPromise,
|
|
5912
4937
|
isDeviceLockedError,
|