@onekeyfe/hwk-ledger-adapter 1.2.5-alpha.2 → 1.2.5-alpha.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 +39 -79
- package/dist/index.d.ts +39 -79
- package/dist/index.js +845 -1744
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +852 -1771
- 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,10 +170,7 @@ var ERROR_TAG = {
|
|
|
181
170
|
UnknownDevice: "UnknownDeviceError",
|
|
182
171
|
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
183
172
|
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
184
|
-
|
|
185
|
-
// spellings are kept so a DMK release that aligns them still classifies.
|
|
186
|
-
OpeningConnection: "ConnectionOpeningError",
|
|
187
|
-
OpeningConnectionLegacy: "OpeningConnectionError",
|
|
173
|
+
OpeningConnection: "OpeningConnectionError",
|
|
188
174
|
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
189
175
|
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
190
176
|
Disconnect: "DisconnectError",
|
|
@@ -197,16 +183,7 @@ var ERROR_TAG = {
|
|
|
197
183
|
// DMK remote-network failures (manager-api HTTP / secure-channel WS).
|
|
198
184
|
WebSocketConnection: "WebSocketConnectionError",
|
|
199
185
|
HttpFetch: "FetchError",
|
|
200
|
-
|
|
201
|
-
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
|
|
202
|
-
ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
|
|
203
|
-
// DMK OS device actions. SecureChannelError is what mapInstallDAErrors() leaves
|
|
204
|
-
// after splitting out device answers, i.e. the relay itself broke.
|
|
205
|
-
SecureChannel: "SecureChannelError",
|
|
206
|
-
RefusedByUserDA: "RefusedByUserDAError",
|
|
207
|
-
AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
|
|
208
|
-
OutOfMemoryDA: "OutOfMemoryDAError",
|
|
209
|
-
DeviceNotOnboarded: "DeviceNotOnboardedError"
|
|
186
|
+
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError"
|
|
210
187
|
};
|
|
211
188
|
function isDeviceLockedError(err) {
|
|
212
189
|
if (!err || typeof err !== "object") return false;
|
|
@@ -236,6 +213,7 @@ function isBlePairingFailureError(err) {
|
|
|
236
213
|
return false;
|
|
237
214
|
}
|
|
238
215
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
216
|
+
ERROR_TAG.DeviceLocked,
|
|
239
217
|
ERROR_TAG.DeviceNotAdvertising,
|
|
240
218
|
ERROR_TAG.BlePairingTimeout,
|
|
241
219
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -247,7 +225,6 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
|
247
225
|
ERROR_TAG.DeviceSessionRefresher,
|
|
248
226
|
ERROR_TAG.DeviceNotInitialized,
|
|
249
227
|
ERROR_TAG.OpeningConnection,
|
|
250
|
-
ERROR_TAG.OpeningConnectionLegacy,
|
|
251
228
|
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
252
229
|
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
253
230
|
ERROR_TAG.Disconnect,
|
|
@@ -262,10 +239,7 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
|
262
239
|
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
263
240
|
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
264
241
|
]);
|
|
265
|
-
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
266
|
-
ERROR_TAG.OpeningConnection,
|
|
267
|
-
ERROR_TAG.OpeningConnectionLegacy
|
|
268
|
-
]);
|
|
242
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
|
|
269
243
|
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
270
244
|
ERROR_TAG.DeviceNotRecognized,
|
|
271
245
|
ERROR_TAG.DeviceSessionNotFound,
|
|
@@ -277,18 +251,17 @@ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
|
277
251
|
ERROR_TAG.WebHIDDisconnect
|
|
278
252
|
]);
|
|
279
253
|
function isConnectionLevelError(err) {
|
|
280
|
-
|
|
254
|
+
if (!err || typeof err !== "object") return false;
|
|
255
|
+
const tag = err._tag;
|
|
256
|
+
if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
|
|
257
|
+
const e = err;
|
|
258
|
+
if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
|
|
259
|
+
if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
|
|
260
|
+
return false;
|
|
281
261
|
}
|
|
282
262
|
function isKnownConnectionTag(tag) {
|
|
283
263
|
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
284
264
|
}
|
|
285
|
-
var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
|
|
286
|
-
ERROR_TAG.OpeningConnection,
|
|
287
|
-
ERROR_TAG.OpeningConnectionLegacy
|
|
288
|
-
]);
|
|
289
|
-
function isConnectionOpeningTag(tag) {
|
|
290
|
-
return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
|
|
291
|
-
}
|
|
292
265
|
function hasStatusCode(err, codeSet) {
|
|
293
266
|
if (!err || typeof err !== "object") return false;
|
|
294
267
|
const e = err;
|
|
@@ -324,28 +297,28 @@ function hasInvalidArgumentCode(err) {
|
|
|
324
297
|
if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
|
|
325
298
|
return false;
|
|
326
299
|
}
|
|
327
|
-
function
|
|
300
|
+
function isDeviceNotFoundError(err) {
|
|
328
301
|
if (!err || typeof err !== "object") return false;
|
|
302
|
+
const tag = err._tag;
|
|
303
|
+
if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
|
|
329
304
|
const e = err;
|
|
330
|
-
if (
|
|
331
|
-
if (e.
|
|
332
|
-
if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
|
|
305
|
+
if (e.originalError != null && isDeviceNotFoundError(e.originalError)) return true;
|
|
306
|
+
if (e.error != null && e._tag && isDeviceNotFoundError(e.error)) return true;
|
|
333
307
|
return false;
|
|
334
308
|
}
|
|
335
|
-
function isDeviceNotFoundError(err) {
|
|
336
|
-
return hasErrorTag(err, DEVICE_NOT_FOUND_TAGS);
|
|
337
|
-
}
|
|
338
309
|
function isDeviceBusyError(err) {
|
|
339
|
-
|
|
310
|
+
if (!err || typeof err !== "object") return false;
|
|
311
|
+
const tag = err._tag;
|
|
312
|
+
if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
|
|
313
|
+
const e = err;
|
|
314
|
+
if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
|
|
315
|
+
if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
|
|
316
|
+
return false;
|
|
340
317
|
}
|
|
341
|
-
var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
|
|
342
|
-
ERROR_TAG.UserRefusedOnDevice,
|
|
343
|
-
ERROR_TAG.RefusedByUserDA
|
|
344
|
-
]);
|
|
345
318
|
function isUserRejectedError(err) {
|
|
346
319
|
if (!err || typeof err !== "object") return false;
|
|
347
320
|
const e = err;
|
|
348
|
-
if (
|
|
321
|
+
if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
|
|
349
322
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
350
323
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
351
324
|
return false;
|
|
@@ -376,33 +349,15 @@ function isAppNotInstalledError(err) {
|
|
|
376
349
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
377
350
|
return false;
|
|
378
351
|
}
|
|
379
|
-
var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
|
|
380
352
|
function isOutOfMemoryError(err) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
function isAppAlreadyInstalledError(err) {
|
|
385
|
-
return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
|
|
386
|
-
}
|
|
387
|
-
var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.DeviceNotOnboarded]);
|
|
388
|
-
function isDeviceNotOnboardedError(err) {
|
|
389
|
-
return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
|
|
390
|
-
}
|
|
391
|
-
var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
|
|
392
|
-
ERROR_TAG.InvalidFirmwareMetadataResponse,
|
|
393
|
-
ERROR_TAG.ApplicationsMetadataTask
|
|
394
|
-
]);
|
|
395
|
-
function isFirmwareMetadataError(err) {
|
|
396
|
-
return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
|
|
397
|
-
}
|
|
398
|
-
var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
|
|
399
|
-
function isSecureChannelError(err) {
|
|
400
|
-
return hasErrorTag(err, SECURE_CHANNEL_TAGS);
|
|
353
|
+
if (!err || typeof err !== "object") return false;
|
|
354
|
+
const e = err;
|
|
355
|
+
return e._tag === "OutOfMemoryDAError";
|
|
401
356
|
}
|
|
402
357
|
var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
|
|
403
358
|
ERROR_TAG.WebSocketConnection,
|
|
404
359
|
ERROR_TAG.HttpFetch,
|
|
405
|
-
ERROR_TAG.
|
|
360
|
+
ERROR_TAG.InvalidFirmwareMetadataResponse
|
|
406
361
|
]);
|
|
407
362
|
function isNetworkError(err) {
|
|
408
363
|
if (!err || typeof err !== "object") return false;
|
|
@@ -452,34 +407,23 @@ function isTransportStuck(err) {
|
|
|
452
407
|
function isStuckAppStateError(err) {
|
|
453
408
|
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
454
409
|
}
|
|
455
|
-
var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
|
|
456
|
-
function nestedErrorMessage(nested) {
|
|
457
|
-
if (!nested || typeof nested !== "object") return void 0;
|
|
458
|
-
const { message } = nested;
|
|
459
|
-
if (typeof message !== "string") return void 0;
|
|
460
|
-
const trimmed = message.trim();
|
|
461
|
-
if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
|
|
462
|
-
return message;
|
|
463
|
-
}
|
|
464
410
|
function mapLedgerError(err, opts) {
|
|
465
411
|
let originalMessage = "Unknown Ledger error";
|
|
466
412
|
if (err instanceof Error) {
|
|
467
413
|
originalMessage = err.message;
|
|
468
414
|
} else if (err && typeof err === "object") {
|
|
469
415
|
const e = err;
|
|
470
|
-
originalMessage = String(
|
|
471
|
-
e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
|
|
472
|
-
);
|
|
416
|
+
originalMessage = String(e.message ?? e._tag ?? e.type ?? JSON.stringify(err));
|
|
473
417
|
}
|
|
474
418
|
let code;
|
|
475
419
|
if (isDeviceLockedError(err)) {
|
|
476
420
|
code = HardwareErrorCode.DeviceLocked;
|
|
477
421
|
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
478
422
|
code = HardwareErrorCode.DeviceNotFound;
|
|
479
|
-
} else if (isBlePairingFailureError(err)) {
|
|
480
|
-
code = HardwareErrorCode.BlePairingTimeout;
|
|
481
423
|
} else if (isDeviceBusyError(err)) {
|
|
482
424
|
code = HardwareErrorCode.DeviceBusy;
|
|
425
|
+
} else if (isBlePairingFailureError(err)) {
|
|
426
|
+
code = HardwareErrorCode.BlePairingTimeout;
|
|
483
427
|
} else if (isUserAbortedError(err)) {
|
|
484
428
|
code = HardwareErrorCode.UserAborted;
|
|
485
429
|
} else if (isUserRejectedError(err)) {
|
|
@@ -488,18 +432,10 @@ function mapLedgerError(err, opts) {
|
|
|
488
432
|
code = HardwareErrorCode.WrongApp;
|
|
489
433
|
} else if (isAppNotInstalledError(err)) {
|
|
490
434
|
code = HardwareErrorCode.AppNotInstalled;
|
|
491
|
-
} else if (isAppAlreadyInstalledError(err)) {
|
|
492
|
-
code = HardwareErrorCode.AppAlreadyInstalled;
|
|
493
435
|
} else if (isOutOfMemoryError(err)) {
|
|
494
436
|
code = HardwareErrorCode.DeviceOutOfMemory;
|
|
495
|
-
} else if (isDeviceNotOnboardedError(err)) {
|
|
496
|
-
code = HardwareErrorCode.DeviceNotInitialized;
|
|
497
|
-
} else if (isFirmwareMetadataError(err)) {
|
|
498
|
-
code = HardwareErrorCode.LedgerFirmwareMetadataError;
|
|
499
437
|
} else if (isNetworkError(err)) {
|
|
500
438
|
code = HardwareErrorCode.NetworkError;
|
|
501
|
-
} else if (isSecureChannelError(err)) {
|
|
502
|
-
code = HardwareErrorCode.LedgerSecureChannelError;
|
|
503
439
|
} else if (isDeviceDisconnectedError(err)) {
|
|
504
440
|
code = HardwareErrorCode.DeviceDisconnected;
|
|
505
441
|
} else if (isTimeoutError(err)) {
|
|
@@ -512,32 +448,11 @@ function mapLedgerError(err, opts) {
|
|
|
512
448
|
}
|
|
513
449
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
514
450
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
515
|
-
return {
|
|
516
|
-
code,
|
|
517
|
-
message: enrichErrorMessage(code, originalMessage),
|
|
518
|
-
origin: defaultOriginForCode(code),
|
|
519
|
-
appName
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// src/utils/queueKey.ts
|
|
524
|
-
var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
|
|
525
|
-
function ledgerQueueKey({
|
|
526
|
-
operationId,
|
|
527
|
-
connectId
|
|
528
|
-
}) {
|
|
529
|
-
return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
|
|
451
|
+
return { code, message: enrichErrorMessage(code, originalMessage), appName };
|
|
530
452
|
}
|
|
531
453
|
|
|
532
454
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
533
|
-
import {
|
|
534
|
-
HardwareErrorCode as HardwareErrorCode2,
|
|
535
|
-
failure,
|
|
536
|
-
isConnectionLost,
|
|
537
|
-
isUserRefusal,
|
|
538
|
-
resolveHardwareOperationTarget,
|
|
539
|
-
runAllNetworkGetAddress
|
|
540
|
-
} from "@onekeyfe/hwk-adapter-core";
|
|
455
|
+
import { HardwareErrorCode as HardwareErrorCode2, failure, runAllNetworkGetAddress } from "@onekeyfe/hwk-adapter-core";
|
|
541
456
|
|
|
542
457
|
// src/utils/sdkEventBus.ts
|
|
543
458
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -594,103 +509,65 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
594
509
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
595
510
|
function createAllNetworkGetAddress({
|
|
596
511
|
callChain,
|
|
597
|
-
getChainFingerprint
|
|
598
|
-
retainOperation,
|
|
599
|
-
errorToFailure,
|
|
600
|
-
createCancelScope
|
|
512
|
+
getChainFingerprint
|
|
601
513
|
}) {
|
|
602
514
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
603
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
604
|
-
method: "allNetworkGetAddress",
|
|
605
|
-
connectId,
|
|
606
|
-
itemCount: params.bundle.length
|
|
607
|
-
});
|
|
608
|
-
const target = resolveHardwareOperationTarget(connectId, params.operationId, "ledger");
|
|
609
|
-
if (!target.success) return target;
|
|
610
|
-
const effectiveTargetId = target.payload.targetId ?? "";
|
|
611
|
-
let releaseOperationRetention;
|
|
612
|
-
try {
|
|
613
|
-
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
614
|
-
} catch (error) {
|
|
615
|
-
return errorToFailure(error);
|
|
616
|
-
}
|
|
617
|
-
const cancelScope = createCancelScope(
|
|
618
|
-
ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
619
|
-
);
|
|
515
|
+
debugLog("[LedgerAdapter][REQ]", { method: "allNetworkGetAddress", connectId, params });
|
|
620
516
|
const installContext = {};
|
|
621
517
|
const commonParams = {
|
|
622
|
-
autoInstallApp: params.autoInstallApp
|
|
623
|
-
operationId: target.payload.operationId,
|
|
624
|
-
knownConnections: params.knownConnections,
|
|
625
|
-
extra: params.extra,
|
|
626
|
-
allowDeviceSelection: params.allowDeviceSelection,
|
|
627
|
-
supportedTransports: params.supportedTransports
|
|
518
|
+
autoInstallApp: params.autoInstallApp
|
|
628
519
|
};
|
|
629
520
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
itemDeviceId,
|
|
644
|
-
method,
|
|
645
|
-
item,
|
|
646
|
-
commonParams,
|
|
647
|
-
installContext
|
|
648
|
-
);
|
|
649
|
-
},
|
|
650
|
-
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
651
|
-
getChainFingerprint,
|
|
652
|
-
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,
|
|
653
534
|
item,
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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;
|
|
680
563
|
};
|
|
681
564
|
}
|
|
682
|
-
function buildCancelledFailure(signal) {
|
|
683
|
-
const reason = signal.reason;
|
|
684
|
-
const code = typeof reason?.code === "number" ? reason.code : HardwareErrorCode2.UserAborted;
|
|
685
|
-
const message = typeof reason?.message === "string" ? reason.message : "";
|
|
686
|
-
return failure(code, message || "All-network get-address cancelled");
|
|
687
|
-
}
|
|
688
565
|
function isTopLevelAllNetworkFailure(response) {
|
|
689
566
|
if (response.success) {
|
|
690
567
|
return false;
|
|
691
568
|
}
|
|
692
569
|
const code = response.payload?.code;
|
|
693
|
-
return code === HardwareErrorCode2.DeviceMismatch ||
|
|
570
|
+
return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
|
|
694
571
|
}
|
|
695
572
|
function getItemDeviceId(item) {
|
|
696
573
|
const { deviceId } = item;
|
|
@@ -720,18 +597,8 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
720
597
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
721
598
|
return coin ? { ...item, coin } : item;
|
|
722
599
|
}
|
|
723
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints
|
|
724
|
-
const
|
|
725
|
-
if (!knownFingerprint && cancelSignal.aborted) {
|
|
726
|
-
const cancelled = buildCancelledFailure(cancelSignal);
|
|
727
|
-
return { ...item, success: false, payload: cancelled.payload };
|
|
728
|
-
}
|
|
729
|
-
const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
|
|
730
|
-
getChainFingerprint,
|
|
731
|
-
context.connection?.connectId ?? connectId,
|
|
732
|
-
chain,
|
|
733
|
-
context
|
|
734
|
-
);
|
|
600
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
601
|
+
const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(getChainFingerprint, connectId, chain);
|
|
735
602
|
if (!fingerprint) {
|
|
736
603
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
737
604
|
}
|
|
@@ -752,8 +619,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
752
619
|
}
|
|
753
620
|
};
|
|
754
621
|
}
|
|
755
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain
|
|
756
|
-
const response = await getChainFingerprint(connectId, chain
|
|
622
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
623
|
+
const response = await getChainFingerprint(connectId, chain);
|
|
757
624
|
return response.success ? response.payload : "";
|
|
758
625
|
}
|
|
759
626
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -846,16 +713,6 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
|
|
|
846
713
|
function formatDeviceMismatchError(expected, actual) {
|
|
847
714
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
848
715
|
}
|
|
849
|
-
var LOG_REDACTED_RESULT_KEYS = ["ufvk", "orchardFvk"];
|
|
850
|
-
function redactResultForLog(result) {
|
|
851
|
-
if (!result || typeof result !== "object") return result;
|
|
852
|
-
const keys = LOG_REDACTED_RESULT_KEYS.filter((key) => key in result);
|
|
853
|
-
if (!keys.length) return result;
|
|
854
|
-
return { ...result, ...Object.fromEntries(keys.map((key) => [key, "[redacted]"])) };
|
|
855
|
-
}
|
|
856
|
-
function isLostConnectionError(err) {
|
|
857
|
-
return isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err);
|
|
858
|
-
}
|
|
859
716
|
var BTC_HIGH_INDEX_THRESHOLD = 100;
|
|
860
717
|
function btcAccountIndexFromPath(path) {
|
|
861
718
|
const segments = path.replace(/^m\//, "").split("/");
|
|
@@ -867,31 +724,9 @@ function btcAccountIndexFromPath(path) {
|
|
|
867
724
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
868
725
|
constructor(connector, options) {
|
|
869
726
|
this.vendor = "ledger";
|
|
870
|
-
// Cancel frees the DMK action and its intent-queue slot, but cannot retract a
|
|
871
|
-
// confirmation screen the device is already showing.
|
|
872
|
-
this.cancelCapability = "stops-waiting";
|
|
873
727
|
this.emitter = new TypedEventEmitter();
|
|
874
|
-
this._operations = new OperationRegistry({
|
|
875
|
-
vendor: "ledger",
|
|
876
|
-
onEnded: (operation, reason) => {
|
|
877
|
-
const binding = this._pendingOperationBindings.get(operation.operationId);
|
|
878
|
-
if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
|
|
879
|
-
this._finishBleBinding("cancelled");
|
|
880
|
-
}
|
|
881
|
-
this._pendingOperationBindings.delete(operation.operationId);
|
|
882
|
-
this.emitter.emit(SDK.OPERATION_ENDED, {
|
|
883
|
-
type: SDK.OPERATION_ENDED,
|
|
884
|
-
payload: { operationId: operation.operationId, reason }
|
|
885
|
-
});
|
|
886
|
-
if (reason === "timeout") {
|
|
887
|
-
void this._releaseOperationConnection(operation);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
728
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
892
729
|
this._sessions = /* @__PURE__ */ new Map();
|
|
893
|
-
this._pendingOperationBindings = /* @__PURE__ */ new Map();
|
|
894
|
-
this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
895
730
|
this._uiRegistry = new UiRequestRegistry();
|
|
896
731
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
897
732
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -905,31 +740,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
905
740
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
906
741
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
907
742
|
this._doConnectAbortController = null;
|
|
908
|
-
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
909
|
-
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
910
|
-
this._resetPromise = null;
|
|
911
|
-
this._stateGeneration = 0;
|
|
912
|
-
this._connectorTeardownTail = Promise.resolve();
|
|
913
|
-
this._pendingConnectorTeardowns = 0;
|
|
914
|
-
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
915
|
-
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
916
743
|
this._installProgressLastEmittedValue = -Infinity;
|
|
917
744
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
918
745
|
callChain: this.callChain.bind(this),
|
|
919
|
-
getChainFingerprint:
|
|
920
|
-
try {
|
|
921
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
922
|
-
chain,
|
|
923
|
-
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
924
|
-
);
|
|
925
|
-
return success(fingerprint);
|
|
926
|
-
} catch (error) {
|
|
927
|
-
return this.errorToFailure(error);
|
|
928
|
-
}
|
|
929
|
-
},
|
|
930
|
-
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
931
|
-
errorToFailure: (error) => this.errorToFailure(error),
|
|
932
|
-
createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
|
|
746
|
+
getChainFingerprint: (connectId, chain) => this.getChainFingerprint(connectId, "", chain)
|
|
933
747
|
});
|
|
934
748
|
// ---------------------------------------------------------------------------
|
|
935
749
|
// Private helpers
|
|
@@ -939,7 +753,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
939
753
|
*
|
|
940
754
|
* - If a session already exists for the given connectId, reuse it.
|
|
941
755
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
942
|
-
* - Otherwise: search →
|
|
756
|
+
* - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
|
|
943
757
|
*/
|
|
944
758
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
945
759
|
this._connectingPromise = null;
|
|
@@ -956,12 +770,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
956
770
|
});
|
|
957
771
|
};
|
|
958
772
|
this.deviceDisconnectHandler = (data) => {
|
|
959
|
-
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
960
|
-
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
961
|
-
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
962
|
-
} else {
|
|
963
|
-
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
964
|
-
}
|
|
965
773
|
this._discoveredDevices.delete(data.connectId);
|
|
966
774
|
this._sessions.delete(data.connectId);
|
|
967
775
|
this.emitter.emit(DEVICE.DISCONNECT, {
|
|
@@ -970,8 +778,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
970
778
|
});
|
|
971
779
|
};
|
|
972
780
|
// Forward connector `ui-event` to the public hw.emitter so consumers only
|
|
973
|
-
// need to subscribe in one place.
|
|
974
|
-
// sessionId
|
|
781
|
+
// need to subscribe in one place. For the AppInstallProgress variant we
|
|
782
|
+
// re-key sessionId → connectId via the live _sessions map; if no mapping
|
|
783
|
+
// exists (race during teardown) we drop. All other variants pass through
|
|
784
|
+
// unchanged.
|
|
975
785
|
this.uiEventForwarder = (event) => {
|
|
976
786
|
if (event.type === EConnectorInteraction.AppInstallProgress) {
|
|
977
787
|
let connectId;
|
|
@@ -1016,28 +826,11 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1016
826
|
this._jobQueue = new DeviceJobQueue();
|
|
1017
827
|
this.registerEventListeners();
|
|
1018
828
|
}
|
|
1019
|
-
_finishBleBinding(status) {
|
|
1020
|
-
const selectionRequestId = this._bindingSelectionRequestId;
|
|
1021
|
-
this._bindingSelectionRequestId = void 0;
|
|
1022
|
-
if (selectionRequestId)
|
|
1023
|
-
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
1024
|
-
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
1025
|
-
payload: { selectionRequestId, status }
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
_isBleConnection() {
|
|
1029
|
-
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
1030
|
-
}
|
|
1031
829
|
// Transport
|
|
1032
830
|
get activeTransport() {
|
|
1033
|
-
return this.
|
|
831
|
+
return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
|
|
1034
832
|
}
|
|
1035
833
|
getAvailableTransports() {
|
|
1036
|
-
if (this.connector.availableTransports) {
|
|
1037
|
-
return this.connector.availableTransports.map(
|
|
1038
|
-
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1041
834
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
1042
835
|
}
|
|
1043
836
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -1054,39 +847,21 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1054
847
|
* The next operation will re-discover and re-connect automatically.
|
|
1055
848
|
*/
|
|
1056
849
|
resetState() {
|
|
1057
|
-
void this._resetStateAndDisconnectSessions();
|
|
1058
|
-
}
|
|
1059
|
-
_resetStateAndDisconnectSessions() {
|
|
1060
|
-
if (this._resetPromise) return this._resetPromise;
|
|
1061
|
-
const sessionIds = new Set(this._sessions.values());
|
|
1062
|
-
this._stateGeneration += 1;
|
|
1063
|
-
this._finishBleBinding("cancelled");
|
|
1064
|
-
this._operations.endAll("runtime-reset");
|
|
1065
|
-
this._doConnectAbortController?.abort();
|
|
1066
850
|
this._discoveredDevices.clear();
|
|
1067
851
|
this._sessions.clear();
|
|
1068
|
-
this._verifiedBleReconnectTargets.clear();
|
|
1069
852
|
this._connectingPromise = null;
|
|
1070
|
-
this._doConnectAbortController = null;
|
|
1071
853
|
this._uiRegistry.reset();
|
|
1072
854
|
this._jobQueue.clear();
|
|
1073
855
|
this._btcHighIndexConfirmedThisSession = false;
|
|
1074
|
-
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1075
|
-
for (const sessionId of sessionIds) {
|
|
1076
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
1077
|
-
}
|
|
1078
|
-
});
|
|
1079
|
-
this._resetPromise = resetPromise;
|
|
1080
|
-
return resetPromise.finally(() => {
|
|
1081
|
-
if (this._resetPromise === resetPromise) {
|
|
1082
|
-
this._resetPromise = null;
|
|
1083
|
-
}
|
|
1084
|
-
});
|
|
1085
856
|
}
|
|
1086
857
|
async dispose() {
|
|
1087
|
-
|
|
858
|
+
this._uiRegistry.reset();
|
|
859
|
+
this._jobQueue.clear();
|
|
1088
860
|
this.unregisterEventListeners();
|
|
1089
861
|
this.connector.reset();
|
|
862
|
+
this._discoveredDevices.clear();
|
|
863
|
+
this._sessions.clear();
|
|
864
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
1090
865
|
this.emitter.removeAllListeners();
|
|
1091
866
|
}
|
|
1092
867
|
uiResponse(response) {
|
|
@@ -1096,32 +871,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1096
871
|
// Device management
|
|
1097
872
|
// ---------------------------------------------------------------------------
|
|
1098
873
|
async searchDevices(options) {
|
|
1099
|
-
return this._searchDevices(options);
|
|
1100
|
-
}
|
|
1101
|
-
async _searchDevices(options, signal) {
|
|
1102
874
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
1103
875
|
try {
|
|
1104
876
|
if (options?.resetSession) {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1111
|
-
const stateGeneration = this._stateGeneration;
|
|
1112
|
-
const devices = await this.connector.searchDevices(
|
|
1113
|
-
options?.transportType ? {
|
|
1114
|
-
transportType: options.transportType,
|
|
1115
|
-
waitForAll: options.waitForAllTransports
|
|
1116
|
-
} : void 0
|
|
1117
|
-
);
|
|
1118
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1119
|
-
if (stateGeneration !== this._stateGeneration) {
|
|
1120
|
-
throw createHwkError({
|
|
1121
|
-
code: HardwareErrorCode3.UserAborted,
|
|
1122
|
-
message: "Ledger discovery was reset"
|
|
1123
|
-
});
|
|
877
|
+
this._doConnectAbortController?.abort();
|
|
878
|
+
this._sessions.clear();
|
|
879
|
+
this._connectingPromise = null;
|
|
880
|
+
this._doConnectAbortController = null;
|
|
881
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
1124
882
|
}
|
|
883
|
+
await this._ensureDevicePermission();
|
|
884
|
+
const devices = await this.connector.searchDevices();
|
|
1125
885
|
this._discoveredDevices.clear();
|
|
1126
886
|
for (const d of devices) {
|
|
1127
887
|
if (d.connectId) {
|
|
@@ -1129,7 +889,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1129
889
|
}
|
|
1130
890
|
}
|
|
1131
891
|
if (this._discoveredDevices.size === 0) {
|
|
1132
|
-
await this._ensureDevicePermission(
|
|
892
|
+
await this._ensureDevicePermission();
|
|
1133
893
|
}
|
|
1134
894
|
const result = Array.from(this._discoveredDevices.values());
|
|
1135
895
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -1148,288 +908,41 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1148
908
|
throw err;
|
|
1149
909
|
}
|
|
1150
910
|
}
|
|
1151
|
-
async searchDeviceTargets(options) {
|
|
1152
|
-
const devices = await this.searchDevices(options);
|
|
1153
|
-
return devices.map((device) => ({
|
|
1154
|
-
searchTargetId: device.connectId,
|
|
1155
|
-
searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
|
|
1156
|
-
vendor: "ledger",
|
|
1157
|
-
connectionType: device.connectionType,
|
|
1158
|
-
kind: "physical",
|
|
1159
|
-
label: device.label,
|
|
1160
|
-
model: device.model,
|
|
1161
|
-
modelName: device.modelName,
|
|
1162
|
-
serialNumber: device.serialNumber
|
|
1163
|
-
}));
|
|
1164
|
-
}
|
|
1165
|
-
async listConnectionTargets(options) {
|
|
1166
|
-
const targets = await this.searchDeviceTargets(options);
|
|
1167
|
-
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1168
|
-
...target,
|
|
1169
|
-
targetId: searchTargetId
|
|
1170
|
-
}));
|
|
1171
|
-
}
|
|
1172
911
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
1173
|
-
async _evictAllSessions(
|
|
1174
|
-
this._operations.endAll("explicit", preserveOperationId);
|
|
912
|
+
async _evictAllSessions() {
|
|
1175
913
|
if (this._sessions.size === 0) return;
|
|
1176
914
|
const stale = [...this._sessions.values()];
|
|
1177
915
|
this._sessions.clear();
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
} catch {
|
|
1183
|
-
}
|
|
916
|
+
for (const sid of stale) {
|
|
917
|
+
try {
|
|
918
|
+
await this.connector.disconnect(sid);
|
|
919
|
+
} catch {
|
|
1184
920
|
}
|
|
1185
|
-
}
|
|
921
|
+
}
|
|
1186
922
|
}
|
|
1187
923
|
static _createDeviceBusyError(method) {
|
|
1188
924
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
1189
925
|
code: HardwareErrorCode3.DeviceBusy
|
|
1190
926
|
});
|
|
1191
927
|
}
|
|
1192
|
-
async connectDevice(
|
|
1193
|
-
try {
|
|
1194
|
-
return await this._jobQueue.enqueue(
|
|
1195
|
-
searchTargetId || "__ledger_connect__",
|
|
1196
|
-
async (signal) => {
|
|
1197
|
-
const connected = await this._connectTarget(searchTargetId, void 0, signal);
|
|
1198
|
-
if (!connected.success) return connected;
|
|
1199
|
-
return this._createOperation(searchTargetId, connected.payload);
|
|
1200
|
-
},
|
|
1201
|
-
{
|
|
1202
|
-
label: "connectDevice",
|
|
1203
|
-
rejectIfBusy: true,
|
|
1204
|
-
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1205
|
-
}
|
|
1206
|
-
);
|
|
1207
|
-
} catch (error) {
|
|
1208
|
-
return this.errorToFailure(error);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
async bindBleDevice(params) {
|
|
1212
|
-
if (params.identity.vendor !== "ledger" || !params.identity.value) {
|
|
1213
|
-
return failure2(HardwareErrorCode3.InvalidParams, "Ledger wallet identity is required");
|
|
1214
|
-
}
|
|
1215
|
-
const { chain, value: expectedFingerprint } = params.identity;
|
|
1216
|
-
try {
|
|
1217
|
-
return await this._jobQueue.enqueue(
|
|
1218
|
-
expectedFingerprint,
|
|
1219
|
-
async (signal) => {
|
|
1220
|
-
if (!this.getAvailableTransports().includes("ble")) {
|
|
1221
|
-
throw createHwkError({
|
|
1222
|
-
code: HardwareErrorCode3.TransportNotAvailable,
|
|
1223
|
-
message: "Ledger Bluetooth transport is not available"
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
1226
|
-
this._activeConnectionType = "ble";
|
|
1227
|
-
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1228
|
-
const attempt = {
|
|
1229
|
-
extra: params.extra,
|
|
1230
|
-
bindingReason: "manual-rebind"
|
|
1231
|
-
};
|
|
1232
|
-
try {
|
|
1233
|
-
for (; ; ) {
|
|
1234
|
-
const connectId = await this._connectFirstOrSelect(
|
|
1235
|
-
[],
|
|
1236
|
-
void 0,
|
|
1237
|
-
true,
|
|
1238
|
-
void 0,
|
|
1239
|
-
attempt,
|
|
1240
|
-
signal
|
|
1241
|
-
);
|
|
1242
|
-
const sessionId = this._sessions.get(connectId);
|
|
1243
|
-
if (!sessionId) {
|
|
1244
|
-
throw createHwkError({
|
|
1245
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
1246
|
-
message: "Selected Ledger connection ended"
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1249
|
-
let saved = false;
|
|
1250
|
-
try {
|
|
1251
|
-
const installContext = {
|
|
1252
|
-
connection: { connectId, sessionId }
|
|
1253
|
-
};
|
|
1254
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
1255
|
-
chain,
|
|
1256
|
-
(method, callParams) => this._runConnectorCall(
|
|
1257
|
-
connectId,
|
|
1258
|
-
method,
|
|
1259
|
-
callParams,
|
|
1260
|
-
signal,
|
|
1261
|
-
void 0,
|
|
1262
|
-
void 0,
|
|
1263
|
-
{ autoInstallApp: true },
|
|
1264
|
-
installContext
|
|
1265
|
-
)
|
|
1266
|
-
);
|
|
1267
|
-
if (fingerprint !== expectedFingerprint) {
|
|
1268
|
-
attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
1269
|
-
attempt.rejectedConnectIds.add(connectId);
|
|
1270
|
-
attempt.rejectedConnectId = connectId;
|
|
1271
|
-
} else {
|
|
1272
|
-
const persisted = await this._publishVerifiedBleBinding(
|
|
1273
|
-
connectId,
|
|
1274
|
-
chain,
|
|
1275
|
-
fingerprint,
|
|
1276
|
-
attempt,
|
|
1277
|
-
void 0,
|
|
1278
|
-
signal
|
|
1279
|
-
);
|
|
1280
|
-
if (!persisted) {
|
|
1281
|
-
throw createHwkError({
|
|
1282
|
-
code: HardwareErrorCode3.UnknownError,
|
|
1283
|
-
message: "Bluetooth binding could not be saved",
|
|
1284
|
-
origin: "host"
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
saved = true;
|
|
1288
|
-
return success(connectId);
|
|
1289
|
-
}
|
|
1290
|
-
} finally {
|
|
1291
|
-
if (!saved && this._sessions.get(connectId) === sessionId) {
|
|
1292
|
-
this._sessions.delete(connectId);
|
|
1293
|
-
const teardown = this._runConnectorTeardown(
|
|
1294
|
-
() => this.connector.disconnect(sessionId)
|
|
1295
|
-
).catch(() => void 0);
|
|
1296
|
-
if (!signal.aborted) await teardown;
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
} catch (error) {
|
|
1301
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1302
|
-
throw error;
|
|
1303
|
-
}
|
|
1304
|
-
},
|
|
1305
|
-
{
|
|
1306
|
-
label: "bindBleDevice",
|
|
1307
|
-
rejectIfBusy: true,
|
|
1308
|
-
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1309
|
-
}
|
|
1310
|
-
);
|
|
1311
|
-
} catch (error) {
|
|
1312
|
-
return this.errorToFailure(error);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
async acquireOperation(connectId, context) {
|
|
1316
|
-
try {
|
|
1317
|
-
return await this._jobQueue.enqueue(
|
|
1318
|
-
connectId || "__ledger_acquire__",
|
|
1319
|
-
async (signal) => {
|
|
1320
|
-
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1321
|
-
const hint = context.knownConnections?.find(
|
|
1322
|
-
(connection) => connection.transport === transport
|
|
1323
|
-
);
|
|
1324
|
-
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1325
|
-
await this._ensureDevicePermission(target, void 0, signal);
|
|
1326
|
-
const attempt = {
|
|
1327
|
-
...context,
|
|
1328
|
-
extra: context.extra ? { ...context.extra } : void 0
|
|
1329
|
-
};
|
|
1330
|
-
try {
|
|
1331
|
-
const resolvedConnectId = await this.ensureConnected(
|
|
1332
|
-
target,
|
|
1333
|
-
signal,
|
|
1334
|
-
true,
|
|
1335
|
-
void 0,
|
|
1336
|
-
attempt
|
|
1337
|
-
);
|
|
1338
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
1339
|
-
const result = this._createOperation(connectId, resolvedConnectId);
|
|
1340
|
-
if (result.success && attempt.selectedConnection) {
|
|
1341
|
-
this._pendingOperationBindings.set(result.payload, attempt);
|
|
1342
|
-
}
|
|
1343
|
-
return result;
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1346
|
-
throw error;
|
|
1347
|
-
}
|
|
1348
|
-
},
|
|
1349
|
-
{
|
|
1350
|
-
label: "acquireOperation",
|
|
1351
|
-
rejectIfBusy: true,
|
|
1352
|
-
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1353
|
-
}
|
|
1354
|
-
);
|
|
1355
|
-
} catch (error) {
|
|
1356
|
-
return this.errorToFailure(error);
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1360
|
-
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1361
|
-
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1362
|
-
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1363
|
-
const connectionType = this._isBleConnection() ? "ble" : "usb";
|
|
1364
|
-
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1365
|
-
vendor: "ledger",
|
|
1366
|
-
model: "unknown",
|
|
1367
|
-
firmwareVersion: "",
|
|
1368
|
-
deviceId: "",
|
|
1369
|
-
connectId: resolvedConnectId,
|
|
1370
|
-
connectionType
|
|
1371
|
-
};
|
|
1372
|
-
const operation = this._operations.create({
|
|
1373
|
-
searchTargetId,
|
|
1374
|
-
connectId: resolvedConnectId,
|
|
1375
|
-
device,
|
|
1376
|
-
connectionType,
|
|
1377
|
-
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1378
|
-
});
|
|
1379
|
-
return success(operation.operationId);
|
|
1380
|
-
}
|
|
1381
|
-
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
928
|
+
async connectDevice(connectId) {
|
|
1382
929
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
1383
930
|
try {
|
|
1384
|
-
this.
|
|
1385
|
-
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1386
|
-
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1387
|
-
this._activeConnectionType = discoveredType;
|
|
1388
|
-
}
|
|
1389
|
-
if (this._isBleConnection() && !connectId) {
|
|
931
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
1390
932
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1391
933
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1392
934
|
});
|
|
1393
935
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const isMultiTransport = (this.connector.availableTransports?.length ?? 0) > 1;
|
|
1397
|
-
if (!isMultiTransport && this._isBleConnection()) {
|
|
1398
|
-
const previousSessionId = this._sessions.get(connectId);
|
|
1399
|
-
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1400
|
-
if (previousSessionId) {
|
|
1401
|
-
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1402
|
-
this._sessions.delete(connectId);
|
|
1403
|
-
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1404
|
-
}
|
|
1405
|
-
} else {
|
|
1406
|
-
await this._evictAllSessions(preserveOperationId);
|
|
1407
|
-
}
|
|
1408
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1409
|
-
const stateGeneration = this._stateGeneration;
|
|
1410
|
-
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1411
|
-
let session;
|
|
1412
|
-
this._connectingConnectId = connectId;
|
|
1413
|
-
try {
|
|
1414
|
-
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1415
|
-
transportType: this._activeConnectionType ?? "usb"
|
|
1416
|
-
}) : await this.connector.connect(connectId);
|
|
1417
|
-
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1418
|
-
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1419
|
-
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1420
|
-
code: HardwareErrorCode3.UserAborted
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
} finally {
|
|
1424
|
-
if (this._connectingConnectId === connectId) this._connectingConnectId = void 0;
|
|
1425
|
-
releaseOperation();
|
|
936
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
937
|
+
await this._evictAllSessions();
|
|
1426
938
|
}
|
|
1427
|
-
|
|
1428
|
-
this.
|
|
939
|
+
await this._ensureDevicePermission(connectId);
|
|
940
|
+
const session = await this.connector.connect(connectId);
|
|
941
|
+
this._sessions.set(connectId, session.sessionId);
|
|
1429
942
|
if (session.deviceInfo) {
|
|
1430
|
-
this._discoveredDevices.set(
|
|
943
|
+
this._discoveredDevices.set(connectId, session.deviceInfo);
|
|
1431
944
|
}
|
|
1432
|
-
const result = success(
|
|
945
|
+
const result = success(connectId);
|
|
1433
946
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
1434
947
|
return result;
|
|
1435
948
|
} catch (err) {
|
|
@@ -1442,60 +955,30 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1442
955
|
return failureResult;
|
|
1443
956
|
}
|
|
1444
957
|
}
|
|
1445
|
-
async
|
|
1446
|
-
const operation = this._operations.find(operationId);
|
|
1447
|
-
if (!operation) {
|
|
1448
|
-
this._operations.resolve(operationId);
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1452
|
-
if (!endedOperation) return;
|
|
1453
|
-
const { connectId } = operation;
|
|
958
|
+
async disconnectDevice(connectId) {
|
|
1454
959
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1455
|
-
method: "
|
|
960
|
+
method: "disconnectDevice",
|
|
1456
961
|
connectId,
|
|
1457
962
|
params: { connectId }
|
|
1458
963
|
});
|
|
1459
964
|
try {
|
|
1460
|
-
|
|
1461
|
-
|
|
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 });
|
|
1462
971
|
} catch (err) {
|
|
1463
972
|
const e = err;
|
|
1464
973
|
debugLog("[LedgerAdapter][RES]", {
|
|
1465
|
-
method: "
|
|
974
|
+
method: "disconnectDevice",
|
|
1466
975
|
success: false,
|
|
1467
976
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1468
977
|
});
|
|
1469
978
|
throw err;
|
|
1470
979
|
}
|
|
1471
980
|
}
|
|
1472
|
-
async
|
|
1473
|
-
const sessionIds = /* @__PURE__ */ new Set();
|
|
1474
|
-
for (const [connectId, sessionId] of this._sessions) {
|
|
1475
|
-
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1476
|
-
this._sessions.delete(connectId);
|
|
1477
|
-
sessionIds.add(sessionId);
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
for (const sessionId of sessionIds) {
|
|
1481
|
-
await this._runConnectorTeardown(
|
|
1482
|
-
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1483
|
-
);
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
async _releaseLostOperationConnection(operationId) {
|
|
1487
|
-
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1488
|
-
if (!endedOperation) return;
|
|
1489
|
-
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1490
|
-
await this._releaseOperationConnection(endedOperation);
|
|
1491
|
-
}
|
|
1492
|
-
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1493
|
-
let connectId;
|
|
1494
|
-
try {
|
|
1495
|
-
connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1496
|
-
} catch (error) {
|
|
1497
|
-
return this.errorToFailure(error);
|
|
1498
|
-
}
|
|
981
|
+
async getDeviceInfo(connectId, deviceId) {
|
|
1499
982
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1500
983
|
method: "getDeviceInfo",
|
|
1501
984
|
connectId,
|
|
@@ -1530,9 +1013,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1530
1013
|
success: false,
|
|
1531
1014
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1532
1015
|
});
|
|
1533
|
-
|
|
1016
|
+
throw err;
|
|
1534
1017
|
}
|
|
1535
1018
|
}
|
|
1019
|
+
getSupportedChains() {
|
|
1020
|
+
return ["evm", "btc", "sol", "tron"];
|
|
1021
|
+
}
|
|
1536
1022
|
// ---------------------------------------------------------------------------
|
|
1537
1023
|
// Chain call helper
|
|
1538
1024
|
// ---------------------------------------------------------------------------
|
|
@@ -1567,23 +1053,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1567
1053
|
if (params && typeof params === "object") {
|
|
1568
1054
|
const {
|
|
1569
1055
|
autoInstallApp,
|
|
1570
|
-
operationId,
|
|
1571
1056
|
passphraseState: _passphraseState,
|
|
1572
1057
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1573
|
-
knownConnections,
|
|
1574
|
-
extra,
|
|
1575
|
-
allowDeviceSelection,
|
|
1576
|
-
supportedTransports,
|
|
1577
1058
|
...rest
|
|
1578
1059
|
} = params;
|
|
1579
1060
|
return {
|
|
1580
1061
|
commonParams: {
|
|
1581
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1582
|
-
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1583
|
-
knownConnections,
|
|
1584
|
-
extra,
|
|
1585
|
-
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0,
|
|
1586
|
-
supportedTransports
|
|
1062
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1587
1063
|
},
|
|
1588
1064
|
rest
|
|
1589
1065
|
};
|
|
@@ -1759,28 +1235,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1759
1235
|
);
|
|
1760
1236
|
}
|
|
1761
1237
|
// ---------------------------------------------------------------------------
|
|
1762
|
-
//
|
|
1763
|
-
//
|
|
1764
|
-
|
|
1765
|
-
return this.callChainWithMergedParams(
|
|
1766
|
-
connectId,
|
|
1767
|
-
deviceId,
|
|
1768
|
-
"zcash",
|
|
1769
|
-
"zcashGetFullViewingKey",
|
|
1770
|
-
params
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1774
|
-
return this.callChainWithMergedParams(
|
|
1775
|
-
connectId,
|
|
1776
|
-
deviceId,
|
|
1777
|
-
"zcash",
|
|
1778
|
-
"zcashGetShieldedAddress",
|
|
1779
|
-
params
|
|
1780
|
-
);
|
|
1781
|
-
}
|
|
1782
|
-
// ---------------------------------------------------------------------------
|
|
1783
|
-
// App management: OS-level app install/list, no fingerprint or chain dispatch.
|
|
1238
|
+
// App management — OS-level Ledger app install / list. Bypasses fingerprint
|
|
1239
|
+
// and chain-handler dispatch; installApp progress is forwarded to the adapter
|
|
1240
|
+
// emitter via 'ui-event' AppInstallProgress events.
|
|
1784
1241
|
// ---------------------------------------------------------------------------
|
|
1785
1242
|
async installApp(connectId, appName) {
|
|
1786
1243
|
try {
|
|
@@ -1866,7 +1323,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1866
1323
|
);
|
|
1867
1324
|
}
|
|
1868
1325
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1869
|
-
|
|
1326
|
+
this.resetState();
|
|
1870
1327
|
}
|
|
1871
1328
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1872
1329
|
if (!result.isGenuine) {
|
|
@@ -1892,10 +1349,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1892
1349
|
if (relayUrl) {
|
|
1893
1350
|
try {
|
|
1894
1351
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1895
|
-
|
|
1352
|
+
this.resetState();
|
|
1896
1353
|
} catch {
|
|
1897
1354
|
this.connector.reset();
|
|
1898
|
-
|
|
1355
|
+
this.resetState();
|
|
1899
1356
|
}
|
|
1900
1357
|
}
|
|
1901
1358
|
}
|
|
@@ -1907,20 +1364,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1907
1364
|
this.emitter.off(event, listener);
|
|
1908
1365
|
}
|
|
1909
1366
|
cancel(connectId) {
|
|
1910
|
-
const namedOperationIsLive = (id) => {
|
|
1911
|
-
try {
|
|
1912
|
-
this._operations.resolve(id);
|
|
1913
|
-
return true;
|
|
1914
|
-
} catch {
|
|
1915
|
-
return false;
|
|
1916
|
-
}
|
|
1917
|
-
};
|
|
1918
|
-
if (isHardwareOperationId(connectId) && !namedOperationIsLive(connectId)) {
|
|
1919
|
-
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1920
|
-
connectId
|
|
1921
|
-
});
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
1367
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1925
1368
|
code: HardwareErrorCode3.UserAborted,
|
|
1926
1369
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1931,50 +1374,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1931
1374
|
this._lastCancelReason = void 0;
|
|
1932
1375
|
}
|
|
1933
1376
|
}, 2e3);
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
operationId = connectId;
|
|
1938
|
-
} else if (!connectId && isHardwareOperationId(activeJobId)) {
|
|
1939
|
-
operationId = activeJobId;
|
|
1940
|
-
}
|
|
1941
|
-
let resolvedConnectId = connectId;
|
|
1942
|
-
if (operationId) {
|
|
1943
|
-
try {
|
|
1944
|
-
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1945
|
-
} catch {
|
|
1946
|
-
resolvedConnectId = void 0;
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1950
|
-
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1951
|
-
if (!connectId) {
|
|
1952
|
-
this._uiRegistry.cancel();
|
|
1953
|
-
} else if (pendingOperationId) {
|
|
1954
|
-
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1955
|
-
}
|
|
1956
|
-
this._finishBleBinding("cancelled");
|
|
1957
|
-
if (!connectId) this._pendingOperationBindings.clear();
|
|
1958
|
-
else if (pendingOperationId) {
|
|
1959
|
-
this._pendingOperationBindings.delete(pendingOperationId);
|
|
1960
|
-
}
|
|
1961
|
-
const queueKeys = /* @__PURE__ */ new Set();
|
|
1962
|
-
if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
|
|
1963
|
-
if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
|
|
1964
|
-
if (queueKeys.size) {
|
|
1965
|
-
let cancelledAnyJob = false;
|
|
1966
|
-
for (const key of queueKeys) {
|
|
1967
|
-
cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
|
|
1968
|
-
}
|
|
1969
|
-
debugLog("[LedgerAdapter] cancel routed to queue keys", {
|
|
1970
|
-
queueKeys: [...queueKeys],
|
|
1971
|
-
cancelledAnyJob
|
|
1972
|
-
});
|
|
1377
|
+
this._uiRegistry.cancel();
|
|
1378
|
+
if (connectId) {
|
|
1379
|
+
this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
|
|
1973
1380
|
} else {
|
|
1974
1381
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1975
1382
|
}
|
|
1976
|
-
if (
|
|
1977
|
-
const sessionId = this._sessions.get(
|
|
1383
|
+
if (connectId) {
|
|
1384
|
+
const sessionId = this._sessions.get(connectId) ?? connectId;
|
|
1978
1385
|
void this.connector.cancel(sessionId);
|
|
1979
1386
|
} else {
|
|
1980
1387
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1982,105 +1389,45 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1982
1389
|
if (this._connectingPromise) {
|
|
1983
1390
|
this._doConnectAbortController?.abort(userAbortReason);
|
|
1984
1391
|
}
|
|
1985
|
-
const connectingConnectId = this._connectingConnectId;
|
|
1986
|
-
if (connectingConnectId) void this.connector.cancel(connectingConnectId);
|
|
1987
1392
|
}
|
|
1988
1393
|
// ---------------------------------------------------------------------------
|
|
1989
1394
|
// Chain fingerprint
|
|
1990
1395
|
// ---------------------------------------------------------------------------
|
|
1991
|
-
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1992
1396
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1993
1397
|
try {
|
|
1994
1398
|
const fingerprint = await this._computeChainFingerprint(
|
|
1995
1399
|
chain,
|
|
1996
1400
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1997
1401
|
);
|
|
1998
|
-
if (deviceId) {
|
|
1999
|
-
if (fingerprint !== deviceId) {
|
|
2000
|
-
return failure2(
|
|
2001
|
-
HardwareErrorCode3.DeviceMismatch,
|
|
2002
|
-
formatDeviceMismatchError(deviceId, fingerprint)
|
|
2003
|
-
);
|
|
2004
|
-
}
|
|
2005
|
-
if (isHardwareOperationId(connectId)) {
|
|
2006
|
-
const operation = this._operations.resolve(connectId);
|
|
2007
|
-
await this._publishVerifiedBleBinding(
|
|
2008
|
-
operation.connectId,
|
|
2009
|
-
chain,
|
|
2010
|
-
fingerprint,
|
|
2011
|
-
void 0,
|
|
2012
|
-
connectId
|
|
2013
|
-
);
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
1402
|
return success(fingerprint);
|
|
2017
1403
|
} catch (err) {
|
|
2018
1404
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
2019
1405
|
return this.errorToFailure(err);
|
|
2020
1406
|
}
|
|
2021
1407
|
}
|
|
2022
|
-
/**
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
}
|
|
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 };
|
|
2028
1414
|
try {
|
|
2029
|
-
const
|
|
2030
|
-
|
|
2031
|
-
this.
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
|
|
2036
|
-
extra: binding.extra,
|
|
2037
|
-
operationId
|
|
2038
|
-
},
|
|
2039
|
-
signal
|
|
2040
|
-
);
|
|
2041
|
-
if (operationId) this._operations.resolve(operationId);
|
|
2042
|
-
if (!outcome.saved) {
|
|
2043
|
-
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
2044
|
-
connectId,
|
|
2045
|
-
reason: outcome.reason
|
|
2046
|
-
});
|
|
2047
|
-
}
|
|
2048
|
-
return outcome.saved;
|
|
2049
|
-
} catch (error) {
|
|
2050
|
-
if (operationId) {
|
|
2051
|
-
this._pendingOperationBindings.delete(operationId);
|
|
2052
|
-
this._operations.end(operationId, "explicit");
|
|
2053
|
-
}
|
|
2054
|
-
const sessionId = this._sessions.get(connectId);
|
|
2055
|
-
this._sessions.delete(connectId);
|
|
2056
|
-
if (sessionId) {
|
|
2057
|
-
const teardown = this._runConnectorTeardown(
|
|
2058
|
-
() => this.connector.disconnect(sessionId)
|
|
2059
|
-
).catch(() => void 0);
|
|
2060
|
-
if (!signal?.aborted) await teardown;
|
|
2061
|
-
}
|
|
2062
|
-
throw error;
|
|
2063
|
-
} finally {
|
|
2064
|
-
if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
|
|
2065
|
-
this._bindingSelectionRequestId = void 0;
|
|
1415
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1416
|
+
chain,
|
|
1417
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
1418
|
+
);
|
|
1419
|
+
if (fingerprint === deviceId) {
|
|
1420
|
+
return { success: true };
|
|
2066
1421
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
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 };
|
|
2069
1427
|
}
|
|
1428
|
+
throw err;
|
|
2070
1429
|
}
|
|
2071
1430
|
}
|
|
2072
|
-
/** Verify on the acquired session without re-entering the job queue. */
|
|
2073
|
-
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
2074
|
-
if (!deviceId) return { success: true };
|
|
2075
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
2076
|
-
chain,
|
|
2077
|
-
(method, params) => this._callConnector(sessionId, method, params)
|
|
2078
|
-
);
|
|
2079
|
-
if (fingerprint === deviceId) {
|
|
2080
|
-
return { success: true };
|
|
2081
|
-
}
|
|
2082
|
-
return { success: false, expected: deviceId, actual: fingerprint };
|
|
2083
|
-
}
|
|
2084
1431
|
/**
|
|
2085
1432
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
2086
1433
|
*
|
|
@@ -2108,23 +1455,11 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2108
1455
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
2109
1456
|
} else if (chain === "tron") {
|
|
2110
1457
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2111
|
-
} else if (chain === "zcash") {
|
|
2112
|
-
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
2113
1458
|
} else {
|
|
2114
1459
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
2115
1460
|
}
|
|
2116
1461
|
return deriveDeviceFingerprint(address);
|
|
2117
1462
|
}
|
|
2118
|
-
/**
|
|
2119
|
-
* Operation owning the running job, so a mid-call UI request can name it.
|
|
2120
|
-
* Undefined at cold start when no operation owns the job.
|
|
2121
|
-
*/
|
|
2122
|
-
_activeOperationId() {
|
|
2123
|
-
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
2124
|
-
if (!activeJobId) return void 0;
|
|
2125
|
-
if (isHardwareOperationId(activeJobId)) return activeJobId;
|
|
2126
|
-
return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
|
|
2127
|
-
}
|
|
2128
1463
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
2129
1464
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
2130
1465
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
@@ -2134,18 +1469,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2134
1469
|
if (signal?.aborted) {
|
|
2135
1470
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2136
1471
|
}
|
|
2137
|
-
const operationId = this._activeOperationId();
|
|
2138
1472
|
const waitPromise = this._uiRegistry.wait(
|
|
2139
|
-
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2140
|
-
{ operationId }
|
|
1473
|
+
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2141
1474
|
);
|
|
2142
1475
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
2143
1476
|
type: UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2144
1477
|
payload: {
|
|
2145
1478
|
vendor: "ledger",
|
|
2146
1479
|
reason: "device-not-found",
|
|
2147
|
-
message: "Please connect and unlock your Ledger device"
|
|
2148
|
-
operationId
|
|
1480
|
+
message: "Please connect and unlock your Ledger device"
|
|
2149
1481
|
}
|
|
2150
1482
|
});
|
|
2151
1483
|
let payload;
|
|
@@ -2206,18 +1538,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2206
1538
|
return { ...params, showOnDevice: true };
|
|
2207
1539
|
}
|
|
2208
1540
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2209
|
-
const operationId = this._activeOperationId();
|
|
2210
1541
|
const waitPromise = this._uiRegistry.wait(
|
|
2211
|
-
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2212
|
-
{ operationId }
|
|
1542
|
+
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2213
1543
|
);
|
|
2214
1544
|
this.emitter.emit(UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
2215
1545
|
type: UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2216
1546
|
payload: {
|
|
2217
1547
|
vendor: "ledger",
|
|
2218
1548
|
path,
|
|
2219
|
-
accountIndex
|
|
2220
|
-
operationId
|
|
1549
|
+
accountIndex
|
|
2221
1550
|
}
|
|
2222
1551
|
});
|
|
2223
1552
|
try {
|
|
@@ -2234,14 +1563,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2234
1563
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
2235
1564
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
2236
1565
|
async _waitForInstallAppConfirm(appName) {
|
|
2237
|
-
const operationId = this._activeOperationId();
|
|
2238
1566
|
const waitPromise = this._uiRegistry.wait(
|
|
2239
|
-
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2240
|
-
{ operationId }
|
|
1567
|
+
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2241
1568
|
);
|
|
2242
1569
|
this.emitter.emit(UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
2243
1570
|
type: UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2244
|
-
payload: { vendor: "ledger", appName
|
|
1571
|
+
payload: { vendor: "ledger", appName }
|
|
2245
1572
|
});
|
|
2246
1573
|
try {
|
|
2247
1574
|
const payload = await waitPromise;
|
|
@@ -2257,17 +1584,16 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2257
1584
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
2258
1585
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
2259
1586
|
// doesn't kill caller B's await.
|
|
2260
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false
|
|
1587
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2261
1588
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
2262
|
-
this.
|
|
2263
|
-
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1589
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
2264
1590
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
2265
1591
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2266
1592
|
});
|
|
2267
1593
|
}
|
|
2268
1594
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
2269
1595
|
if (!connectId && this._sessions.size > 0) {
|
|
2270
|
-
if (!this.
|
|
1596
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
|
|
2271
1597
|
throw Object.assign(
|
|
2272
1598
|
new Error(
|
|
2273
1599
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -2277,24 +1603,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2277
1603
|
}
|
|
2278
1604
|
return this._sessions.keys().next().value;
|
|
2279
1605
|
}
|
|
2280
|
-
if (!this._connectingPromise
|
|
2281
|
-
|
|
2282
|
-
this._doConnectAbortController
|
|
2283
|
-
const innerSignal = controller.signal;
|
|
1606
|
+
if (!this._connectingPromise) {
|
|
1607
|
+
this._doConnectAbortController = new AbortController();
|
|
1608
|
+
const innerSignal = this._doConnectAbortController.signal;
|
|
2284
1609
|
this._connectingPromise = (async () => {
|
|
2285
1610
|
try {
|
|
2286
|
-
return await this._doConnect(
|
|
2287
|
-
innerSignal,
|
|
2288
|
-
connectId,
|
|
2289
|
-
allowUsbEphemeralFallback,
|
|
2290
|
-
preserveOperationId,
|
|
2291
|
-
context
|
|
2292
|
-
);
|
|
1611
|
+
return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback);
|
|
2293
1612
|
} finally {
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
this._doConnectAbortController = null;
|
|
2297
|
-
}
|
|
1613
|
+
this._connectingPromise = null;
|
|
1614
|
+
this._doConnectAbortController = null;
|
|
2298
1615
|
}
|
|
2299
1616
|
})();
|
|
2300
1617
|
}
|
|
@@ -2303,57 +1620,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2303
1620
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
2304
1621
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
2305
1622
|
// throw DeviceNotFound so the user is kicked out of the loop.
|
|
2306
|
-
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
)
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
preserveOperationId,
|
|
2322
|
-
context,
|
|
2323
|
-
internalSignal
|
|
2324
|
-
);
|
|
2325
|
-
}
|
|
2326
|
-
if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
|
|
2327
|
-
throw createHwkError({
|
|
2328
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2329
|
-
message: "This Ledger model has no Bluetooth transport; connect it over USB"
|
|
2330
|
-
});
|
|
2331
|
-
}
|
|
2332
|
-
this._activeConnectionType = "ble";
|
|
2333
|
-
const knownBle = context?.knownConnections?.find(
|
|
2334
|
-
(connection) => connection.transport === "ble"
|
|
2335
|
-
);
|
|
2336
|
-
if (knownBle?.transport === "ble") {
|
|
2337
|
-
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2338
|
-
}
|
|
2339
|
-
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2340
|
-
throw createHwkError({
|
|
2341
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2342
|
-
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2343
|
-
});
|
|
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
|
+
}
|
|
2344
1638
|
}
|
|
2345
|
-
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2346
|
-
return this._connectFirstOrSelect(
|
|
2347
|
-
bleDevices,
|
|
2348
|
-
void 0,
|
|
2349
|
-
allowUsbEphemeralFallback,
|
|
2350
|
-
preserveOperationId,
|
|
2351
|
-
context,
|
|
2352
|
-
internalSignal
|
|
2353
|
-
);
|
|
2354
|
-
}
|
|
2355
|
-
if (this._isBleConnection() && targetConnectId) {
|
|
2356
|
-
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
2357
1639
|
}
|
|
2358
1640
|
let confirms = 0;
|
|
2359
1641
|
while (!internalSignal.aborted) {
|
|
@@ -2361,28 +1643,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2361
1643
|
type: EConnectorInteraction.Searching,
|
|
2362
1644
|
payload: { sessionId: "" }
|
|
2363
1645
|
});
|
|
2364
|
-
let devices = await this.
|
|
2365
|
-
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1646
|
+
let devices = await this.searchDevices();
|
|
2366
1647
|
if (devices.length === 0) {
|
|
2367
1648
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
2368
1649
|
await new Promise((resolve) => {
|
|
2369
1650
|
setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
2370
1651
|
});
|
|
2371
|
-
|
|
2372
|
-
devices = await this._searchDevices(void 0, internalSignal);
|
|
2373
|
-
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1652
|
+
devices = await this.searchDevices();
|
|
2374
1653
|
if (devices.length > 0) break;
|
|
2375
1654
|
}
|
|
2376
1655
|
}
|
|
2377
|
-
if (devices.length > 0
|
|
1656
|
+
if (devices.length > 0) {
|
|
2378
1657
|
try {
|
|
2379
1658
|
return await this._connectFirstOrSelect(
|
|
2380
1659
|
devices,
|
|
2381
1660
|
targetConnectId,
|
|
2382
|
-
allowUsbEphemeralFallback
|
|
2383
|
-
preserveOperationId,
|
|
2384
|
-
context,
|
|
2385
|
-
internalSignal
|
|
1661
|
+
allowUsbEphemeralFallback
|
|
2386
1662
|
);
|
|
2387
1663
|
} catch (err) {
|
|
2388
1664
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -2411,133 +1687,45 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2411
1687
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2412
1688
|
throw new Error("_doConnect aborted");
|
|
2413
1689
|
}
|
|
2414
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2415
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
1690
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
2416
1691
|
if (targetConnectId) {
|
|
2417
1692
|
const target = devices.find(
|
|
2418
1693
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
2419
1694
|
);
|
|
2420
1695
|
if (target) {
|
|
2421
|
-
return this._connectDeviceOrThrow(target.connectId
|
|
1696
|
+
return this._connectDeviceOrThrow(target.connectId);
|
|
2422
1697
|
}
|
|
2423
|
-
if (!this.
|
|
1698
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
2424
1699
|
debugLog(
|
|
2425
1700
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
2426
1701
|
);
|
|
2427
|
-
return this._connectDeviceOrThrow(devices[0].connectId
|
|
1702
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2428
1703
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2435
|
-
}
|
|
2436
|
-
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;
|
|
2437
1709
|
}
|
|
1710
|
+
throw err;
|
|
2438
1711
|
}
|
|
2439
|
-
|
|
2440
|
-
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1712
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
2441
1713
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
2442
1714
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2443
1715
|
});
|
|
2444
1716
|
}
|
|
2445
|
-
if (
|
|
2446
|
-
|
|
2447
|
-
if (context) context.bindingSessionId = bindingSessionId;
|
|
2448
|
-
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2449
|
-
const knownUsb = context?.knownConnections?.find(
|
|
2450
|
-
(connection) => connection.transport === "usb"
|
|
2451
|
-
);
|
|
2452
|
-
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2453
|
-
const { device, requestId } = await requestBleDeviceSelection({
|
|
2454
|
-
emitter: this.emitter,
|
|
2455
|
-
registry: this._uiRegistry,
|
|
2456
|
-
signal,
|
|
2457
|
-
allowUsbFallback,
|
|
2458
|
-
scan: async () => {
|
|
2459
|
-
if (allowUsbFallback) {
|
|
2460
|
-
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2461
|
-
const candidate = usbDevices.find(
|
|
2462
|
-
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2463
|
-
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2464
|
-
if (candidate) return [candidate];
|
|
2465
|
-
}
|
|
2466
|
-
return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
|
|
2467
|
-
},
|
|
2468
|
-
request: {
|
|
2469
|
-
devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
|
|
2470
|
-
bindingSessionId,
|
|
2471
|
-
rejectedConnectId: context?.rejectedConnectId,
|
|
2472
|
-
context: {
|
|
2473
|
-
kind: "bind-connection",
|
|
2474
|
-
transport: "ble",
|
|
2475
|
-
reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
|
|
2476
|
-
},
|
|
2477
|
-
extra: context?.extra,
|
|
2478
|
-
operationId: preserveOperationId
|
|
2479
|
-
}
|
|
2480
|
-
});
|
|
2481
|
-
if (device.connectionType === "usb") {
|
|
2482
|
-
this._activeConnectionType = "usb";
|
|
2483
|
-
if (context) context.selectedConnection = void 0;
|
|
2484
|
-
if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
|
|
2485
|
-
this._bindingSelectionRequestId = requestId;
|
|
2486
|
-
this._finishBleBinding("cancelled");
|
|
2487
|
-
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2488
|
-
}
|
|
2489
|
-
if (context) context.selectedConnection = { connectId: device.connectId, requestId };
|
|
2490
|
-
this._bindingSelectionRequestId = requestId;
|
|
2491
|
-
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2492
|
-
}
|
|
2493
|
-
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2494
|
-
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2495
|
-
throw createHwkError({
|
|
2496
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2497
|
-
message: "Select a Ledger device before continuing"
|
|
2498
|
-
});
|
|
2499
|
-
}
|
|
2500
|
-
const requestId = this._uiRegistry.createRequestId();
|
|
2501
|
-
const operationId = this._activeOperationId();
|
|
2502
|
-
const waitPromise = this._uiRegistry.wait(
|
|
2503
|
-
UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2504
|
-
{ requestId, operationId }
|
|
2505
|
-
);
|
|
2506
|
-
this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2507
|
-
type: UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2508
|
-
payload: {
|
|
2509
|
-
devices,
|
|
2510
|
-
requestId,
|
|
2511
|
-
operationId,
|
|
2512
|
-
context: requiresBleSelection ? {
|
|
2513
|
-
kind: "bind-connection",
|
|
2514
|
-
transport: "ble",
|
|
2515
|
-
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2516
|
-
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2517
|
-
extra: context?.extra
|
|
2518
|
-
}
|
|
2519
|
-
});
|
|
2520
|
-
const { sdkConnectId } = await this._abortable(signal, waitPromise);
|
|
2521
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
2522
|
-
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2523
|
-
if (!selected) {
|
|
2524
|
-
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2525
|
-
code: HardwareErrorCode3.DeviceNotFound
|
|
2526
|
-
});
|
|
2527
|
-
}
|
|
2528
|
-
if (context && requiresBleSelection)
|
|
2529
|
-
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2530
|
-
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1717
|
+
if (devices.length > 1) {
|
|
1718
|
+
throw createMultipleUsbLedgerDevicesError();
|
|
2531
1719
|
}
|
|
2532
1720
|
if (devices.length !== 1) {
|
|
2533
1721
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
2534
1722
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2535
1723
|
});
|
|
2536
1724
|
}
|
|
2537
|
-
return this._connectDeviceOrThrow(devices[0].connectId
|
|
1725
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2538
1726
|
}
|
|
2539
|
-
async _connectDeviceOrThrow(chosenConnectId
|
|
2540
|
-
const result = await this.
|
|
1727
|
+
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1728
|
+
const result = await this.connectDevice(chosenConnectId);
|
|
2541
1729
|
if (!result.success) {
|
|
2542
1730
|
const payload = result.payload;
|
|
2543
1731
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -2548,7 +1736,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2548
1736
|
}
|
|
2549
1737
|
throw rethrow;
|
|
2550
1738
|
}
|
|
2551
|
-
return
|
|
1739
|
+
return chosenConnectId;
|
|
2552
1740
|
}
|
|
2553
1741
|
/**
|
|
2554
1742
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -2578,133 +1766,33 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2578
1766
|
* lives in one place.
|
|
2579
1767
|
*/
|
|
2580
1768
|
async _callConnector(sessionId, method, params, signal) {
|
|
2581
|
-
this.
|
|
2582
|
-
if (signal?.aborted) throw this._abortReason(signal);
|
|
2583
|
-
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2584
|
-
let promise;
|
|
2585
|
-
try {
|
|
2586
|
-
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2587
|
-
} catch (error) {
|
|
2588
|
-
releaseOperation();
|
|
2589
|
-
throw error;
|
|
2590
|
-
}
|
|
1769
|
+
const promise = this.connector.call(sessionId, method, params);
|
|
2591
1770
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
2592
1771
|
return this._unwrapConnectorResult(result);
|
|
2593
1772
|
}
|
|
2594
|
-
_assertConnectorReady(method) {
|
|
2595
|
-
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2596
|
-
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
_runConnectorTeardown(task) {
|
|
2600
|
-
const previous = this._connectorTeardownTail;
|
|
2601
|
-
let releaseTail = () => void 0;
|
|
2602
|
-
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2603
|
-
releaseTail = resolve;
|
|
2604
|
-
});
|
|
2605
|
-
this._pendingConnectorTeardowns += 1;
|
|
2606
|
-
return (async () => {
|
|
2607
|
-
try {
|
|
2608
|
-
await previous;
|
|
2609
|
-
await this._waitForConnectorOperationsToDrain();
|
|
2610
|
-
await task();
|
|
2611
|
-
} finally {
|
|
2612
|
-
this._pendingConnectorTeardowns -= 1;
|
|
2613
|
-
releaseTail();
|
|
2614
|
-
}
|
|
2615
|
-
})();
|
|
2616
|
-
}
|
|
2617
|
-
_waitForConnectorOperationsToDrain() {
|
|
2618
|
-
if (this._unsettledConnectorOperations.size === 0) {
|
|
2619
|
-
return Promise.resolve();
|
|
2620
|
-
}
|
|
2621
|
-
return new Promise((resolve) => {
|
|
2622
|
-
this._connectorIdleWaiters.add(resolve);
|
|
2623
|
-
});
|
|
2624
|
-
}
|
|
2625
|
-
_retainConnectorOperation(key) {
|
|
2626
|
-
this._unsettledConnectorOperations.set(
|
|
2627
|
-
key,
|
|
2628
|
-
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2629
|
-
);
|
|
2630
|
-
let released = false;
|
|
2631
|
-
return () => {
|
|
2632
|
-
if (released) return;
|
|
2633
|
-
released = true;
|
|
2634
|
-
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2635
|
-
if (remaining > 0) {
|
|
2636
|
-
this._unsettledConnectorOperations.set(key, remaining);
|
|
2637
|
-
} else {
|
|
2638
|
-
this._unsettledConnectorOperations.delete(key);
|
|
2639
|
-
}
|
|
2640
|
-
if (this._unsettledConnectorOperations.size === 0) {
|
|
2641
|
-
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2642
|
-
this._connectorIdleWaiters.clear();
|
|
2643
|
-
}
|
|
2644
|
-
};
|
|
2645
|
-
}
|
|
2646
1773
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
throw createHwkError({
|
|
2650
|
-
code: HardwareErrorCode3.InvalidParams,
|
|
2651
|
-
message: "Conflicting Ledger operation ids",
|
|
2652
|
-
params: {
|
|
2653
|
-
positionalOperationId,
|
|
2654
|
-
commonOperationId: commonParams.operationId
|
|
2655
|
-
}
|
|
2656
|
-
});
|
|
2657
|
-
}
|
|
2658
|
-
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2659
|
-
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2660
|
-
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2661
|
-
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2662
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
2663
|
-
method,
|
|
2664
|
-
connectId: effectiveConnectId || "(empty)",
|
|
2665
|
-
params
|
|
2666
|
-
});
|
|
2667
|
-
const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
|
|
1774
|
+
debugLog("[LedgerAdapter][REQ]", { method, connectId: connectId || "(empty)", params });
|
|
1775
|
+
const queueKey = connectId || "__ledger_default__";
|
|
2668
1776
|
try {
|
|
2669
1777
|
const result = await this._jobQueue.enqueue(
|
|
2670
1778
|
queueKey,
|
|
2671
|
-
async (signal) =>
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
commonParams,
|
|
2682
|
-
installContext ?? {},
|
|
2683
|
-
operationId
|
|
2684
|
-
);
|
|
2685
|
-
} catch (error) {
|
|
2686
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
2687
|
-
throw error;
|
|
2688
|
-
} finally {
|
|
2689
|
-
if (operationId) {
|
|
2690
|
-
this._activeOperationJobs.delete(operationId);
|
|
2691
|
-
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2692
|
-
await this._releaseLostOperationConnection(operationId);
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
},
|
|
1779
|
+
async (signal) => this._runConnectorCall(
|
|
1780
|
+
connectId,
|
|
1781
|
+
method,
|
|
1782
|
+
params,
|
|
1783
|
+
signal,
|
|
1784
|
+
fingerprint,
|
|
1785
|
+
permissionDeviceId,
|
|
1786
|
+
commonParams,
|
|
1787
|
+
installContext
|
|
1788
|
+
),
|
|
2697
1789
|
{
|
|
2698
1790
|
label: method,
|
|
2699
1791
|
rejectIfBusy: true,
|
|
2700
1792
|
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
2701
1793
|
}
|
|
2702
1794
|
);
|
|
2703
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
2704
|
-
method,
|
|
2705
|
-
success: true,
|
|
2706
|
-
payload: redactResultForLog(result)
|
|
2707
|
-
});
|
|
1795
|
+
debugLog("[LedgerAdapter][RES]", { method, success: true, payload: result });
|
|
2708
1796
|
return result;
|
|
2709
1797
|
} catch (err) {
|
|
2710
1798
|
const e = err;
|
|
@@ -2718,22 +1806,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2718
1806
|
}
|
|
2719
1807
|
});
|
|
2720
1808
|
throw err;
|
|
2721
|
-
} finally {
|
|
2722
|
-
releaseOperationRetention?.();
|
|
2723
1809
|
}
|
|
2724
1810
|
}
|
|
2725
|
-
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2726
|
-
_abortReason(signal) {
|
|
2727
|
-
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2728
|
-
}
|
|
2729
1811
|
/**
|
|
2730
1812
|
* Race a promise against an abort signal. On abort, rejects with
|
|
2731
1813
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
2732
1814
|
*/
|
|
2733
1815
|
_abortable(signal, promise) {
|
|
2734
|
-
const getAbortReason = () => this.
|
|
1816
|
+
const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2735
1817
|
if (signal.aborted) {
|
|
2736
|
-
void promise.catch(() => void 0);
|
|
2737
1818
|
return Promise.reject(getAbortReason());
|
|
2738
1819
|
}
|
|
2739
1820
|
return new Promise((resolve, reject) => {
|
|
@@ -2760,15 +1841,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2760
1841
|
}
|
|
2761
1842
|
}
|
|
2762
1843
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
2763
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext
|
|
1844
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2764
1845
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
);
|
|
2771
|
-
}
|
|
1846
|
+
await this._ensureDevicePermission(
|
|
1847
|
+
connectId,
|
|
1848
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
1849
|
+
signal
|
|
1850
|
+
);
|
|
2772
1851
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2773
1852
|
let effectiveParams = params;
|
|
2774
1853
|
if (method === "btcGetPublicKey") {
|
|
@@ -2782,135 +1861,36 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2782
1861
|
effectiveParams = gatedParams;
|
|
2783
1862
|
}
|
|
2784
1863
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2788
|
-
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2789
|
-
(connection) => connection.transport === knownTransport
|
|
2790
|
-
);
|
|
2791
|
-
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2792
|
-
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
|
|
2793
|
-
const connectionAttempt = { ...commonParams };
|
|
2794
|
-
const bundleConnection = installContext?.connection;
|
|
2795
|
-
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2796
|
-
throw createHwkError({
|
|
2797
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2798
|
-
message: "Ledger all-network connection ended"
|
|
2799
|
-
});
|
|
2800
|
-
}
|
|
2801
|
-
let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2802
|
-
preferredConnectId,
|
|
1864
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1865
|
+
connectId,
|
|
2803
1866
|
signal,
|
|
2804
|
-
allowUsbEphemeralFallback
|
|
2805
|
-
void 0,
|
|
2806
|
-
connectionAttempt
|
|
1867
|
+
allowUsbEphemeralFallback
|
|
2807
1868
|
);
|
|
2808
|
-
|
|
2809
|
-
if (sessionId && installContext && !installContext.connection) {
|
|
2810
|
-
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2811
|
-
}
|
|
1869
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
2812
1870
|
if (!sessionId) {
|
|
2813
|
-
if (operationId) {
|
|
2814
|
-
this._operations.end(operationId, "disconnect");
|
|
2815
|
-
throw createHwkError({
|
|
2816
|
-
code: HardwareErrorCode3.OperationEnded,
|
|
2817
|
-
message: "Ledger operation connection is no longer active",
|
|
2818
|
-
params: { operationId, reason: "disconnect" }
|
|
2819
|
-
});
|
|
2820
|
-
}
|
|
2821
1871
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
2822
1872
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
2823
1873
|
});
|
|
2824
1874
|
}
|
|
2825
1875
|
try {
|
|
2826
1876
|
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
)
|
|
2835
|
-
);
|
|
2836
|
-
if (fp.success) break;
|
|
2837
|
-
const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
|
|
2838
|
-
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
|
|
2839
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2840
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2841
|
-
});
|
|
2842
|
-
}
|
|
2843
|
-
binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
2844
|
-
binding.rejectedConnectIds.add(resolvedConnectId);
|
|
2845
|
-
binding.rejectedConnectId = resolvedConnectId;
|
|
2846
|
-
this._sessions.delete(resolvedConnectId);
|
|
2847
|
-
await this.connector.disconnect(sessionId);
|
|
2848
|
-
if (operationId) this._pendingOperationDisconnects.delete(operationId);
|
|
2849
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
2850
|
-
resolvedConnectId = await this._connectFirstOrSelect(
|
|
2851
|
-
[],
|
|
2852
|
-
void 0,
|
|
2853
|
-
true,
|
|
2854
|
-
operationId,
|
|
2855
|
-
binding,
|
|
2856
|
-
signal
|
|
2857
|
-
);
|
|
2858
|
-
const selectedSession = this._sessions.get(resolvedConnectId);
|
|
2859
|
-
const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
|
|
2860
|
-
if (!selectedSession || !selectedDevice)
|
|
2861
|
-
throw createHwkError({
|
|
2862
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2863
|
-
message: "Selected Ledger connection ended"
|
|
2864
|
-
});
|
|
2865
|
-
sessionId = selectedSession;
|
|
2866
|
-
if (operationId)
|
|
2867
|
-
this._operations.rebind(operationId, {
|
|
2868
|
-
connectId: resolvedConnectId,
|
|
2869
|
-
device: selectedDevice,
|
|
2870
|
-
// Same source as `_createOperation`: the selected transport, not
|
|
2871
|
-
// the device snapshot a session connect overwrote.
|
|
2872
|
-
connectionType: this._isBleConnection() ? "ble" : "usb",
|
|
2873
|
-
connectionKeys: [sessionId]
|
|
2874
|
-
});
|
|
2875
|
-
if (installContext)
|
|
2876
|
-
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2877
|
-
}
|
|
2878
|
-
await this._publishVerifiedBleBinding(
|
|
2879
|
-
resolvedConnectId,
|
|
2880
|
-
fingerprint.chain,
|
|
2881
|
-
fingerprint.deviceId,
|
|
2882
|
-
connectionAttempt,
|
|
2883
|
-
operationId,
|
|
2884
|
-
signal
|
|
1877
|
+
const fp = await this._abortable(
|
|
1878
|
+
signal,
|
|
1879
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1880
|
+
sessionId,
|
|
1881
|
+
fingerprint.deviceId,
|
|
1882
|
+
fingerprint.chain
|
|
1883
|
+
)
|
|
2885
1884
|
);
|
|
2886
|
-
if (!
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
chain: fingerprint.chain,
|
|
2890
|
-
fingerprint: fingerprint.deviceId
|
|
1885
|
+
if (!fp.success) {
|
|
1886
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1887
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2891
1888
|
});
|
|
2892
1889
|
}
|
|
2893
1890
|
}
|
|
2894
|
-
businessCallStarted = true;
|
|
2895
1891
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2896
1892
|
} catch (err) {
|
|
2897
1893
|
if (signal.aborted) throw err;
|
|
2898
|
-
if (isLostConnectionError(err)) {
|
|
2899
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2900
|
-
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2901
|
-
else {
|
|
2902
|
-
this._sessions.delete(resolvedConnectId);
|
|
2903
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2904
|
-
}
|
|
2905
|
-
const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2906
|
-
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2907
|
-
throw createHwkError({
|
|
2908
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(err).code,
|
|
2909
|
-
message: "Ledger operation connection was lost; start a new operation",
|
|
2910
|
-
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId }) : interactionParams,
|
|
2911
|
-
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2912
|
-
});
|
|
2913
|
-
}
|
|
2914
1894
|
const errObj = err;
|
|
2915
1895
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
2916
1896
|
message: errObj?.message,
|
|
@@ -2922,60 +1902,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2922
1902
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
2923
1903
|
isStuckApp: isStuckAppStateError(err)
|
|
2924
1904
|
});
|
|
2925
|
-
const assertSessionCurrent = (message) => {
|
|
2926
|
-
if (this._sessions.get(resolvedConnectId) === sessionId) return;
|
|
2927
|
-
throw createHwkError({
|
|
2928
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2929
|
-
message
|
|
2930
|
-
});
|
|
2931
|
-
};
|
|
2932
|
-
if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2933
|
-
await this._waitForDeviceConnect(signal);
|
|
2934
|
-
assertSessionCurrent("Ledger connection ended while waiting for unlock");
|
|
2935
|
-
return this._runConnectorCall(
|
|
2936
|
-
resolvedConnectId,
|
|
2937
|
-
method,
|
|
2938
|
-
effectiveParams,
|
|
2939
|
-
signal,
|
|
2940
|
-
fingerprint,
|
|
2941
|
-
permissionDeviceId,
|
|
2942
|
-
commonParams,
|
|
2943
|
-
installContext,
|
|
2944
|
-
operationId,
|
|
2945
|
-
lockedRetryBudget - 1
|
|
2946
|
-
);
|
|
2947
|
-
}
|
|
2948
|
-
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2949
|
-
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2950
|
-
assertSessionCurrent("Ledger connection ended during the app transition");
|
|
2951
|
-
try {
|
|
2952
|
-
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2953
|
-
} catch (retryErr) {
|
|
2954
|
-
if (isStuckAppStateError(retryErr)) throw err;
|
|
2955
|
-
if (isLostConnectionError(retryErr)) {
|
|
2956
|
-
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2957
|
-
else {
|
|
2958
|
-
this._sessions.delete(resolvedConnectId);
|
|
2959
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2960
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2961
|
-
}
|
|
2962
|
-
const ambiguous = !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2963
|
-
throw createHwkError({
|
|
2964
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(retryErr).code,
|
|
2965
|
-
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2966
|
-
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId, reason: "disconnect" }) : { operationId, reason: "disconnect" },
|
|
2967
|
-
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2968
|
-
});
|
|
2969
|
-
}
|
|
2970
|
-
throw retryErr;
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
if (!operationId && errObj?.code === HardwareErrorCode3.DeviceMismatch) {
|
|
2974
|
-
this._sessions.delete(resolvedConnectId);
|
|
2975
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2976
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2977
|
-
throw err;
|
|
2978
|
-
}
|
|
2979
1905
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
2980
1906
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
|
|
2981
1907
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -3031,14 +1957,157 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3031
1957
|
fingerprint,
|
|
3032
1958
|
permissionDeviceId,
|
|
3033
1959
|
commonParams,
|
|
3034
|
-
installContext
|
|
3035
|
-
|
|
1960
|
+
installContext
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
if (isStuckAppStateError(err)) {
|
|
1965
|
+
try {
|
|
1966
|
+
this._sessions.delete(resolvedConnectId);
|
|
1967
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1968
|
+
this.connector.reset?.();
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1971
|
+
debugLog(
|
|
1972
|
+
"[LedgerAdapter] stuck-app retry: method=",
|
|
1973
|
+
method,
|
|
1974
|
+
"delayMs=",
|
|
1975
|
+
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
|
|
1976
|
+
"_tag=",
|
|
1977
|
+
err?._tag
|
|
1978
|
+
);
|
|
1979
|
+
try {
|
|
1980
|
+
const retryResult = await this._retryAfterStuckApp(
|
|
1981
|
+
resolvedConnectId,
|
|
1982
|
+
method,
|
|
1983
|
+
effectiveParams,
|
|
1984
|
+
signal,
|
|
1985
|
+
err,
|
|
1986
|
+
fingerprint
|
|
3036
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
|
+
}
|
|
3037
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
|
+
});
|
|
3038
2073
|
}
|
|
3039
2074
|
throw err;
|
|
3040
2075
|
}
|
|
3041
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
|
+
}
|
|
3042
2111
|
_sleepAbortable(ms, signal) {
|
|
3043
2112
|
return new Promise((resolve, reject) => {
|
|
3044
2113
|
if (signal.aborted) {
|
|
@@ -3057,22 +2126,111 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3057
2126
|
});
|
|
3058
2127
|
}
|
|
3059
2128
|
/**
|
|
3060
|
-
*
|
|
3061
|
-
*
|
|
2129
|
+
* Clear stale session, reconnect, and retry the call.
|
|
2130
|
+
*
|
|
2131
|
+
* Timeout recovery starts with a full connector reset. After an APDU
|
|
2132
|
+
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
2133
|
+
* on the same DMK can poison the next chain switch.
|
|
2134
|
+
*/
|
|
2135
|
+
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2136
|
+
this.connector.reset();
|
|
2137
|
+
this._sessions.clear();
|
|
2138
|
+
this._discoveredDevices.clear();
|
|
2139
|
+
this._connectingPromise = null;
|
|
2140
|
+
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2141
|
+
const retryConnectId = await this.ensureConnected(
|
|
2142
|
+
targetConnectId,
|
|
2143
|
+
signal,
|
|
2144
|
+
allowUsbEphemeralFallback
|
|
2145
|
+
);
|
|
2146
|
+
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2147
|
+
if (!retrySessionId) {
|
|
2148
|
+
throw originalErr;
|
|
2149
|
+
}
|
|
2150
|
+
try {
|
|
2151
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2152
|
+
const fp = await this._abortable(
|
|
2153
|
+
signal,
|
|
2154
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2155
|
+
retrySessionId,
|
|
2156
|
+
fingerprint.deviceId,
|
|
2157
|
+
fingerprint.chain
|
|
2158
|
+
)
|
|
2159
|
+
);
|
|
2160
|
+
if (!fp.success) {
|
|
2161
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2162
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return await this._callConnector(retrySessionId, method, params, signal);
|
|
2167
|
+
} catch (retryErr) {
|
|
2168
|
+
if (signal.aborted) throw retryErr;
|
|
2169
|
+
this.connector.reset();
|
|
2170
|
+
this._sessions.clear();
|
|
2171
|
+
this._discoveredDevices.clear();
|
|
2172
|
+
this._connectingPromise = null;
|
|
2173
|
+
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
2174
|
+
throw retryErr;
|
|
2175
|
+
}
|
|
2176
|
+
debugLog(
|
|
2177
|
+
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
2178
|
+
);
|
|
2179
|
+
this.connector.reset();
|
|
2180
|
+
this._sessions.clear();
|
|
2181
|
+
this._discoveredDevices.clear();
|
|
2182
|
+
this._connectingPromise = null;
|
|
2183
|
+
const finalConnectId = await this.ensureConnected(
|
|
2184
|
+
targetConnectId,
|
|
2185
|
+
signal,
|
|
2186
|
+
allowUsbEphemeralFallback
|
|
2187
|
+
);
|
|
2188
|
+
const finalSessionId = this._sessions.get(finalConnectId);
|
|
2189
|
+
if (!finalSessionId) {
|
|
2190
|
+
throw originalErr;
|
|
2191
|
+
}
|
|
2192
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2193
|
+
const fp = await this._abortable(
|
|
2194
|
+
signal,
|
|
2195
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2196
|
+
finalSessionId,
|
|
2197
|
+
fingerprint.deviceId,
|
|
2198
|
+
fingerprint.chain
|
|
2199
|
+
)
|
|
2200
|
+
);
|
|
2201
|
+
if (!fp.success) {
|
|
2202
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2203
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return this._callConnector(finalSessionId, method, params, signal);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
2212
|
+
*
|
|
2213
|
+
* Emits `REQUEST_DEVICE_PERMISSION` and awaits the consumer's
|
|
2214
|
+
* `RECEIVE_DEVICE_PERMISSION` reply (60s budget covers "probe → system
|
|
2215
|
+
* prompt → user tap" plus a generous margin). If the consumer never wires
|
|
2216
|
+
* a handler or never replies, the wait times out and the operation fails
|
|
2217
|
+
* fast so scanners/callers don't hang silently.
|
|
2218
|
+
*
|
|
2219
|
+
* - No connectId (searchDevices): environment-level permission
|
|
2220
|
+
* - With connectId (business methods): device-level permission
|
|
3062
2221
|
*/
|
|
3063
2222
|
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
3064
2223
|
if (signal?.aborted) {
|
|
3065
2224
|
_LedgerAdapter._throwIfAborted(signal);
|
|
3066
2225
|
}
|
|
3067
2226
|
const transportType = this.activeTransport ?? "hid";
|
|
3068
|
-
const operationId = this._activeOperationId();
|
|
3069
2227
|
const waitPromise = this._uiRegistry.wait(
|
|
3070
2228
|
UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3071
|
-
{ timeoutMs: 6e4
|
|
2229
|
+
{ timeoutMs: 6e4 }
|
|
3072
2230
|
);
|
|
3073
2231
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
3074
2232
|
type: UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3075
|
-
payload: { transportType, connectId, deviceId
|
|
2233
|
+
payload: { transportType, connectId, deviceId }
|
|
3076
2234
|
});
|
|
3077
2235
|
let response;
|
|
3078
2236
|
const onAbort = () => {
|
|
@@ -3118,15 +2276,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3118
2276
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
3119
2277
|
const e = err;
|
|
3120
2278
|
const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
3121
|
-
return ledgerFailure(
|
|
3122
|
-
e.code,
|
|
3123
|
-
e.message ?? "Unknown error",
|
|
3124
|
-
e.appName,
|
|
3125
|
-
tag,
|
|
3126
|
-
params,
|
|
3127
|
-
void 0,
|
|
3128
|
-
isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
|
|
3129
|
-
);
|
|
2279
|
+
return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
|
|
3130
2280
|
}
|
|
3131
2281
|
const mapped = mapLedgerError(err);
|
|
3132
2282
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -3153,7 +2303,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3153
2303
|
deviceId: device.deviceId,
|
|
3154
2304
|
connectId: device.connectId,
|
|
3155
2305
|
label: device.name,
|
|
3156
|
-
connectionType:
|
|
2306
|
+
connectionType: this.connector.connectionType,
|
|
3157
2307
|
rssi: device.rssi,
|
|
3158
2308
|
isConnectable: device.isConnectable,
|
|
3159
2309
|
serialNumber: device.serialNumber,
|
|
@@ -3179,8 +2329,8 @@ var LedgerAdapter = _LedgerAdapter;
|
|
|
3179
2329
|
|
|
3180
2330
|
// src/connector/LedgerConnectorBase.ts
|
|
3181
2331
|
import {
|
|
3182
|
-
EConnectorInteraction as
|
|
3183
|
-
HardwareErrorCode as
|
|
2332
|
+
EConnectorInteraction as EConnectorInteraction4,
|
|
2333
|
+
HardwareErrorCode as HardwareErrorCode8,
|
|
3184
2334
|
isKnownNonTargetHardwareVendor,
|
|
3185
2335
|
serializeConnectorError
|
|
3186
2336
|
} from "@onekeyfe/hwk-adapter-core";
|
|
@@ -3453,7 +2603,7 @@ var LedgerDeviceManager = class {
|
|
|
3453
2603
|
|
|
3454
2604
|
// src/signer/SignerManager.ts
|
|
3455
2605
|
import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
|
|
3456
|
-
import { ContextModuleBuilder
|
|
2606
|
+
import { ContextModuleBuilder } from "@ledgerhq/context-module";
|
|
3457
2607
|
|
|
3458
2608
|
// src/signer/SignerEth.ts
|
|
3459
2609
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
@@ -3719,7 +2869,7 @@ var SignerManager = class _SignerManager {
|
|
|
3719
2869
|
return (args) => new SignerEthBuilder(args);
|
|
3720
2870
|
}
|
|
3721
2871
|
static _createContextModule() {
|
|
3722
|
-
const contextModule = new ContextModuleBuilder({}).
|
|
2872
|
+
const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3723
2873
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
3724
2874
|
}
|
|
3725
2875
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -3742,40 +2892,19 @@ import { HardwareErrorCode as HardwareErrorCode4, padHex64, stripHex } from "@on
|
|
|
3742
2892
|
|
|
3743
2893
|
// src/connector/chains/utils.ts
|
|
3744
2894
|
import { EConnectorInteraction as EConnectorInteraction2 } from "@onekeyfe/hwk-adapter-core";
|
|
3745
|
-
function normalizePath(path) {
|
|
3746
|
-
return path.startsWith("m/") ? path.slice(2) : path;
|
|
3747
|
-
}
|
|
3748
|
-
function collapseSignerInteraction(interaction) {
|
|
3749
|
-
switch (interaction) {
|
|
3750
|
-
case "confirm-open-app":
|
|
3751
|
-
return EConnectorInteraction2.ConfirmOpenApp;
|
|
3752
|
-
case "unlock-device":
|
|
3753
|
-
return EConnectorInteraction2.UnlockDevice;
|
|
3754
|
-
case "interaction-complete":
|
|
3755
|
-
return EConnectorInteraction2.InteractionComplete;
|
|
3756
|
-
default:
|
|
3757
|
-
return EConnectorInteraction2.ConfirmOnDevice;
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
function wireSignerToSession(ctx, sessionId, chain, signer) {
|
|
3761
|
-
signer.onInteraction = (interaction) => {
|
|
3762
|
-
debugLog(`[LedgerConnector] ${chain}.onInteraction:`, interaction);
|
|
3763
|
-
ctx.emit("ui-event", {
|
|
3764
|
-
type: collapseSignerInteraction(interaction),
|
|
3765
|
-
payload: { sessionId }
|
|
3766
|
-
});
|
|
3767
|
-
};
|
|
3768
|
-
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
3769
|
-
return signer;
|
|
3770
|
-
}
|
|
3771
|
-
async function runSignerCall(ctx, sessionId, call) {
|
|
3772
|
-
try {
|
|
3773
|
-
return await call();
|
|
3774
|
-
} catch (err) {
|
|
3775
|
-
ctx.invalidateSession(sessionId);
|
|
3776
|
-
throw ctx.wrapError(err);
|
|
3777
|
-
} finally {
|
|
3778
|
-
ctx.clearCanceller(sessionId);
|
|
2895
|
+
function normalizePath(path) {
|
|
2896
|
+
return path.startsWith("m/") ? path.slice(2) : path;
|
|
2897
|
+
}
|
|
2898
|
+
function collapseSignerInteraction(interaction) {
|
|
2899
|
+
switch (interaction) {
|
|
2900
|
+
case "confirm-open-app":
|
|
2901
|
+
return EConnectorInteraction2.ConfirmOpenApp;
|
|
2902
|
+
case "unlock-device":
|
|
2903
|
+
return EConnectorInteraction2.UnlockDevice;
|
|
2904
|
+
case "interaction-complete":
|
|
2905
|
+
return EConnectorInteraction2.InteractionComplete;
|
|
2906
|
+
default:
|
|
2907
|
+
return EConnectorInteraction2.ConfirmOnDevice;
|
|
3779
2908
|
}
|
|
3780
2909
|
}
|
|
3781
2910
|
|
|
@@ -4210,8 +3339,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
4210
3339
|
}
|
|
4211
3340
|
|
|
4212
3341
|
// src/connector/chains/sol.ts
|
|
4213
|
-
import { bytesToHex, hexToBytes as hexToBytes3
|
|
4214
|
-
import bs58 from "bs58";
|
|
3342
|
+
import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
|
|
4215
3343
|
|
|
4216
3344
|
// src/signer/SignerSol.ts
|
|
4217
3345
|
var SignerSol = class {
|
|
@@ -4295,21 +3423,8 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
4295
3423
|
const path = normalizePath(params.path);
|
|
4296
3424
|
const messageBytes = hexToBytes3(params.message);
|
|
4297
3425
|
try {
|
|
4298
|
-
if (params.messageVersion === 1) {
|
|
4299
|
-
const preparedMessage = prepareSolanaOffchainMessageV1({
|
|
4300
|
-
message: messageBytes,
|
|
4301
|
-
requiredSigners: params.requiredSigners
|
|
4302
|
-
});
|
|
4303
|
-
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4304
|
-
"@ledgerhq/device-signer-kit-solana"
|
|
4305
|
-
);
|
|
4306
|
-
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4307
|
-
version: SignMessageVersion.Raw
|
|
4308
|
-
});
|
|
4309
|
-
return { signature: decodeBase58Signature(result2.signature) };
|
|
4310
|
-
}
|
|
4311
3426
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
4312
|
-
return { signature:
|
|
3427
|
+
return { signature: result.signature };
|
|
4313
3428
|
} catch (err) {
|
|
4314
3429
|
ctx.invalidateSession(sessionId);
|
|
4315
3430
|
throw ctx.wrapError(err);
|
|
@@ -4317,27 +3432,11 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
4317
3432
|
ctx.clearCanceller(sessionId);
|
|
4318
3433
|
}
|
|
4319
3434
|
}
|
|
4320
|
-
function decodeBase58Signature(signature) {
|
|
4321
|
-
const bytes = bs58.decode(signature);
|
|
4322
|
-
if (bytes.length !== 64) {
|
|
4323
|
-
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4324
|
-
}
|
|
4325
|
-
return bytesToHex(bytes);
|
|
4326
|
-
}
|
|
4327
|
-
function decodeBase58EnvelopeSignature(envelope) {
|
|
4328
|
-
const bytes = bs58.decode(envelope);
|
|
4329
|
-
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4330
|
-
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4331
|
-
}
|
|
4332
|
-
return bytesToHex(bytes.subarray(1, 65));
|
|
4333
|
-
}
|
|
4334
3435
|
async function _createSolSigner(ctx, sessionId) {
|
|
4335
3436
|
const dmk = await ctx.getOrCreateDmk();
|
|
4336
|
-
const { ContextModuleBuilder: ContextModuleBuilder2
|
|
4337
|
-
"@ledgerhq/context-module"
|
|
4338
|
-
);
|
|
3437
|
+
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit("@ledgerhq/context-module");
|
|
4339
3438
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
4340
|
-
const contextModule = new ContextModuleBuilder2({}).
|
|
3439
|
+
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4341
3440
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
4342
3441
|
const signer = new SignerSol(sdkSigner);
|
|
4343
3442
|
signer.onInteraction = (interaction) => {
|
|
@@ -4353,147 +3452,361 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
4353
3452
|
return signer;
|
|
4354
3453
|
}
|
|
4355
3454
|
|
|
4356
|
-
// src/connector/chains/
|
|
4357
|
-
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";
|
|
4358
3458
|
|
|
4359
|
-
// src/
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
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;
|
|
4364
3486
|
}
|
|
4365
|
-
/**
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
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
|
+
}
|
|
4373
3565
|
);
|
|
4374
3566
|
}
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
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
|
|
3626
|
+
);
|
|
3627
|
+
throw new Error(
|
|
3628
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
4383
3629
|
);
|
|
4384
3630
|
}
|
|
3631
|
+
_isDashboard(appName) {
|
|
3632
|
+
return appName === DASHBOARD_APP_NAME;
|
|
3633
|
+
}
|
|
3634
|
+
_wait() {
|
|
3635
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3636
|
+
}
|
|
4385
3637
|
};
|
|
4386
3638
|
|
|
4387
|
-
// src/connector/chains/
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
const path = normalizePath(params.path);
|
|
4391
|
-
const mode = params.mode ?? "ufvk";
|
|
4392
|
-
return runSignerCall(ctx, sessionId, async () => {
|
|
4393
|
-
const result = await signer.getFullViewingKey(path, { mode });
|
|
4394
|
-
if (result.mode === "ufvk") {
|
|
4395
|
-
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
4396
|
-
}
|
|
4397
|
-
return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
|
|
4398
|
-
});
|
|
3639
|
+
// src/connector/chains/legacyChainCall.ts
|
|
3640
|
+
function isLegacyWrongAppError(err, _appName) {
|
|
3641
|
+
return isWrongAppError(err);
|
|
4399
3642
|
}
|
|
4400
|
-
async function
|
|
4401
|
-
const
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
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 }
|
|
4406
3651
|
});
|
|
4407
|
-
|
|
4408
|
-
|
|
3652
|
+
};
|
|
3653
|
+
const closeOpenAppUiIfShown = () => {
|
|
3654
|
+
if (openAppPromptShown) {
|
|
3655
|
+
ctx.emit("ui-event", {
|
|
3656
|
+
type: EConnectorInteraction3.InteractionComplete,
|
|
3657
|
+
payload: { sessionId }
|
|
3658
|
+
});
|
|
3659
|
+
openAppPromptShown = false;
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
try {
|
|
3663
|
+
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3664
|
+
} catch (err) {
|
|
3665
|
+
debugLog(
|
|
3666
|
+
"[LegacyChainCall] pre-flight ensureAppOpen failed:",
|
|
3667
|
+
appName,
|
|
3668
|
+
err?.message
|
|
3669
|
+
);
|
|
3670
|
+
closeOpenAppUiIfShown();
|
|
3671
|
+
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3672
|
+
}
|
|
3673
|
+
const runOnce = async () => {
|
|
3674
|
+
let confirmEmitted = false;
|
|
3675
|
+
if (needsConfirmation) {
|
|
3676
|
+
ctx.emit("ui-event", {
|
|
3677
|
+
type: EConnectorInteraction3.ConfirmOnDevice,
|
|
3678
|
+
payload: { sessionId }
|
|
3679
|
+
});
|
|
3680
|
+
confirmEmitted = true;
|
|
3681
|
+
}
|
|
3682
|
+
try {
|
|
3683
|
+
return await action(sessionId);
|
|
3684
|
+
} finally {
|
|
3685
|
+
if (confirmEmitted || openAppPromptShown) {
|
|
3686
|
+
ctx.emit("ui-event", {
|
|
3687
|
+
type: EConnectorInteraction3.InteractionComplete,
|
|
3688
|
+
payload: { sessionId }
|
|
3689
|
+
});
|
|
3690
|
+
openAppPromptShown = false;
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
try {
|
|
3695
|
+
return await runOnce();
|
|
3696
|
+
} catch (err) {
|
|
3697
|
+
if (!isLegacyWrongAppError(err, appName)) {
|
|
3698
|
+
debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
|
|
3699
|
+
ctx.invalidateSession(sessionId);
|
|
3700
|
+
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3701
|
+
}
|
|
3702
|
+
debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
|
|
3703
|
+
try {
|
|
3704
|
+
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3705
|
+
} catch (switchErr) {
|
|
3706
|
+
debugLog(
|
|
3707
|
+
"[LegacyChainCall] retry ensureAppOpen failed:",
|
|
3708
|
+
appName,
|
|
3709
|
+
switchErr?.message
|
|
3710
|
+
);
|
|
3711
|
+
closeOpenAppUiIfShown();
|
|
3712
|
+
throw ctx.wrapError(switchErr, { defaultAppName: appName });
|
|
3713
|
+
}
|
|
3714
|
+
ctx.clearAllSigners();
|
|
3715
|
+
const result = await runOnce();
|
|
3716
|
+
debugLog("[LegacyChainCall] retry succeeded:", appName);
|
|
3717
|
+
return result;
|
|
3718
|
+
}
|
|
4409
3719
|
}
|
|
4410
|
-
async function
|
|
3720
|
+
async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
|
|
4411
3721
|
const dmk = await ctx.getOrCreateDmk();
|
|
4412
|
-
const
|
|
4413
|
-
|
|
4414
|
-
return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
|
|
3722
|
+
const appManager = new AppManager(dmk);
|
|
3723
|
+
await appManager.ensureAppOpen(sessionId, appName, onPrompt);
|
|
4415
3724
|
}
|
|
4416
3725
|
|
|
4417
|
-
// src/
|
|
4418
|
-
import
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
this._sdk = _sdk;
|
|
4425
|
-
}
|
|
4426
|
-
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4427
|
-
async getAddress(derivationPath, options) {
|
|
4428
|
-
const action = this._sdk.getAddress(derivationPath, options);
|
|
4429
|
-
const result = await deviceActionToPromise(
|
|
4430
|
-
action,
|
|
4431
|
-
this.onInteraction,
|
|
4432
|
-
void 0,
|
|
4433
|
-
this.onRegisterCanceller
|
|
4434
|
-
);
|
|
4435
|
-
return { address: result.address, publicKey: result.publicKey };
|
|
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;
|
|
4436
3733
|
}
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
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;
|
|
4446
3746
|
}
|
|
4447
|
-
|
|
4448
|
-
async signPersonalMessage(derivationPath, message, options) {
|
|
4449
|
-
const action = this._sdk.signPersonalMessage(derivationPath, message, options);
|
|
4450
|
-
return deviceActionToPromise(
|
|
4451
|
-
action,
|
|
4452
|
-
this.onInteraction,
|
|
4453
|
-
void 0,
|
|
4454
|
-
this.onRegisterCanceller
|
|
4455
|
-
);
|
|
3747
|
+
async close() {
|
|
4456
3748
|
}
|
|
4457
3749
|
};
|
|
4458
3750
|
|
|
4459
3751
|
// src/connector/chains/tron.ts
|
|
4460
3752
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4461
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4462
3753
|
const path = normalizePath(params.path);
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
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
|
+
);
|
|
4469
3770
|
}
|
|
4470
3771
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
4471
3772
|
if (!params.rawTxHex) {
|
|
4472
3773
|
throw Object.assign(
|
|
4473
3774
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
4474
|
-
{ code:
|
|
3775
|
+
{ code: HardwareErrorCode7.InvalidParams }
|
|
4475
3776
|
);
|
|
4476
3777
|
}
|
|
4477
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4478
3778
|
const path = normalizePath(params.path);
|
|
4479
|
-
return
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
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
|
+
);
|
|
4483
3793
|
}
|
|
4484
3794
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4485
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4486
3795
|
const path = normalizePath(params.path);
|
|
4487
|
-
return
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
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
|
+
);
|
|
4491
3806
|
}
|
|
4492
|
-
async function
|
|
3807
|
+
async function _createTrx(ctx, sessionId) {
|
|
4493
3808
|
const dmk = await ctx.getOrCreateDmk();
|
|
4494
|
-
|
|
4495
|
-
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4496
|
-
return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
|
|
3809
|
+
return new Trx(new DmkTransport(dmk, sessionId));
|
|
4497
3810
|
}
|
|
4498
3811
|
|
|
4499
3812
|
// src/device-apps/customActions.ts
|
|
@@ -4672,7 +3985,7 @@ var DeviceApps = class {
|
|
|
4672
3985
|
seTargetId: v.seTargetId,
|
|
4673
3986
|
mcuTargetId: v.mcuTargetId,
|
|
4674
3987
|
seVersion: v.seVersion,
|
|
4675
|
-
seFlagsHex:
|
|
3988
|
+
seFlagsHex: bytesToHex2(v.seFlags),
|
|
4676
3989
|
mcuSephVersion: v.mcuSephVersion,
|
|
4677
3990
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
4678
3991
|
hwVersion: v.hwVersion
|
|
@@ -4746,7 +4059,7 @@ var DeviceApps = class {
|
|
|
4746
4059
|
}
|
|
4747
4060
|
}
|
|
4748
4061
|
};
|
|
4749
|
-
function
|
|
4062
|
+
function bytesToHex2(bytes) {
|
|
4750
4063
|
if (!bytes) return "";
|
|
4751
4064
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4752
4065
|
}
|
|
@@ -4789,11 +4102,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4789
4102
|
evm: "Ethereum",
|
|
4790
4103
|
btc: "Bitcoin",
|
|
4791
4104
|
sol: "Solana",
|
|
4792
|
-
tron: "Tron"
|
|
4793
|
-
zcash: "Zcash"
|
|
4105
|
+
tron: "Tron"
|
|
4794
4106
|
};
|
|
4795
4107
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4796
|
-
Object.values(
|
|
4108
|
+
Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
|
|
4797
4109
|
);
|
|
4798
4110
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4799
4111
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4830,10 +4142,6 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4830
4142
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4831
4143
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4832
4144
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4833
|
-
case "@ledgerhq/device-signer-kit-tron":
|
|
4834
|
-
return import("@ledgerhq/device-signer-kit-tron");
|
|
4835
|
-
case "@ledgerhq/device-signer-kit-zcash":
|
|
4836
|
-
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4837
4145
|
case "@ledgerhq/context-module":
|
|
4838
4146
|
return import("@ledgerhq/context-module");
|
|
4839
4147
|
default:
|
|
@@ -4893,7 +4201,6 @@ var LedgerConnectorBase = class {
|
|
|
4893
4201
|
this._ctx = {
|
|
4894
4202
|
emit: (event, data) => this._emit(event, data),
|
|
4895
4203
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4896
|
-
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4897
4204
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4898
4205
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4899
4206
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -4993,7 +4300,7 @@ var LedgerConnectorBase = class {
|
|
|
4993
4300
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
4994
4301
|
);
|
|
4995
4302
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
4996
|
-
err.code =
|
|
4303
|
+
err.code = HardwareErrorCode8.BlePairingTimeout;
|
|
4997
4304
|
reject(err);
|
|
4998
4305
|
}, HANG_CEILING_MS);
|
|
4999
4306
|
});
|
|
@@ -5026,7 +4333,7 @@ var LedgerConnectorBase = class {
|
|
|
5026
4333
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
5027
4334
|
);
|
|
5028
4335
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
5029
|
-
err.code =
|
|
4336
|
+
err.code = HardwareErrorCode8.DeviceNotFound;
|
|
5030
4337
|
throw err;
|
|
5031
4338
|
};
|
|
5032
4339
|
const doConnect = async (path) => {
|
|
@@ -5083,14 +4390,14 @@ var LedgerConnectorBase = class {
|
|
|
5083
4390
|
this._resetSignersAndSessions();
|
|
5084
4391
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
5085
4392
|
const tag = err?._tag;
|
|
5086
|
-
if (isKnownConnectionTag(tag)
|
|
4393
|
+
if (isKnownConnectionTag(tag)) {
|
|
5087
4394
|
throw err;
|
|
5088
4395
|
}
|
|
5089
4396
|
const wrapped = new Error(
|
|
5090
4397
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
5091
4398
|
);
|
|
5092
4399
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
5093
|
-
wrapped.code =
|
|
4400
|
+
wrapped.code = HardwareErrorCode8.BlePairingTimeout;
|
|
5094
4401
|
wrapped.originalError = err;
|
|
5095
4402
|
throw wrapped;
|
|
5096
4403
|
}
|
|
@@ -5168,7 +4475,7 @@ var LedgerConnectorBase = class {
|
|
|
5168
4475
|
this._unwatchSessionState(sessionId);
|
|
5169
4476
|
this._signerManager?.invalidate(sessionId);
|
|
5170
4477
|
this._cancellers.get(sessionId)?.({
|
|
5171
|
-
code:
|
|
4478
|
+
code: HardwareErrorCode8.DeviceDisconnected,
|
|
5172
4479
|
tag: "DeviceDisconnected",
|
|
5173
4480
|
message: "Device disconnected"
|
|
5174
4481
|
});
|
|
@@ -5194,7 +4501,7 @@ var LedgerConnectorBase = class {
|
|
|
5194
4501
|
success: false,
|
|
5195
4502
|
error: serializeConnectorError(
|
|
5196
4503
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
5197
|
-
code:
|
|
4504
|
+
code: HardwareErrorCode8.DeviceAppStuck,
|
|
5198
4505
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
5199
4506
|
originalError: err
|
|
5200
4507
|
})
|
|
@@ -5206,7 +4513,7 @@ var LedgerConnectorBase = class {
|
|
|
5206
4513
|
success: false,
|
|
5207
4514
|
error: serializeConnectorError(
|
|
5208
4515
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
5209
|
-
code:
|
|
4516
|
+
code: HardwareErrorCode8.TransportError,
|
|
5210
4517
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
5211
4518
|
originalError: err
|
|
5212
4519
|
})
|
|
@@ -5252,11 +4559,6 @@ var LedgerConnectorBase = class {
|
|
|
5252
4559
|
return solSignTransaction(ctx, sessionId, params);
|
|
5253
4560
|
case "solSignMessage":
|
|
5254
4561
|
return solSignMessage(ctx, sessionId, params);
|
|
5255
|
-
// ZCASH
|
|
5256
|
-
case "zcashGetFullViewingKey":
|
|
5257
|
-
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5258
|
-
case "zcashGetShieldedAddress":
|
|
5259
|
-
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
5260
4562
|
// TRON
|
|
5261
4563
|
case "tronGetAddress":
|
|
5262
4564
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -5281,7 +4583,7 @@ var LedgerConnectorBase = class {
|
|
|
5281
4583
|
try {
|
|
5282
4584
|
return await apps.install(p.appName, ({ progress }) => {
|
|
5283
4585
|
ctx.emit("ui-event", {
|
|
5284
|
-
type:
|
|
4586
|
+
type: EConnectorInteraction4.AppInstallProgress,
|
|
5285
4587
|
payload: {
|
|
5286
4588
|
sessionId,
|
|
5287
4589
|
appName: p.appName,
|
|
@@ -5290,7 +4592,7 @@ var LedgerConnectorBase = class {
|
|
|
5290
4592
|
});
|
|
5291
4593
|
});
|
|
5292
4594
|
} catch (err) {
|
|
5293
|
-
ctx.
|
|
4595
|
+
ctx.invalidateSession(sessionId);
|
|
5294
4596
|
throw ctx.wrapError(err);
|
|
5295
4597
|
} finally {
|
|
5296
4598
|
ctx.clearCanceller(sessionId);
|
|
@@ -5380,7 +4682,7 @@ var LedgerConnectorBase = class {
|
|
|
5380
4682
|
);
|
|
5381
4683
|
return { isGenuine: output.isGenuine, deviceId };
|
|
5382
4684
|
} catch (err) {
|
|
5383
|
-
ctx.
|
|
4685
|
+
ctx.invalidateSession(sessionId);
|
|
5384
4686
|
throw ctx.wrapError(err);
|
|
5385
4687
|
} finally {
|
|
5386
4688
|
ctx.clearCanceller(sessionId);
|
|
@@ -5499,26 +4801,9 @@ var LedgerConnectorBase = class {
|
|
|
5499
4801
|
}
|
|
5500
4802
|
return this._deviceAppsManager;
|
|
5501
4803
|
}
|
|
5502
|
-
// DeviceAppsManager is a per-call factory with no cached session state, so
|
|
5503
|
-
// there is nothing to invalidate for it here.
|
|
5504
4804
|
_invalidateSession(sessionId) {
|
|
5505
4805
|
this._signerManager?.invalidate(sessionId);
|
|
5506
4806
|
}
|
|
5507
|
-
/**
|
|
5508
|
-
* Fire the canceller so DMK closes the secure-channel WebSocket. The DMK device
|
|
5509
|
-
* session is kept: unlock/retry recovery reuses it.
|
|
5510
|
-
*/
|
|
5511
|
-
_teardownSecureChannelSession(sessionId) {
|
|
5512
|
-
const cancel = this._cancellers.get(sessionId);
|
|
5513
|
-
this._cancellers.delete(sessionId);
|
|
5514
|
-
if (cancel) {
|
|
5515
|
-
try {
|
|
5516
|
-
cancel();
|
|
5517
|
-
} catch {
|
|
5518
|
-
}
|
|
5519
|
-
}
|
|
5520
|
-
this._invalidateSession(sessionId);
|
|
5521
|
-
}
|
|
5522
4807
|
/**
|
|
5523
4808
|
* Replace an old session with a new one after app switch.
|
|
5524
4809
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -5603,7 +4888,7 @@ var LedgerConnectorBase = class {
|
|
|
5603
4888
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
5604
4889
|
*/
|
|
5605
4890
|
_ctxForMethod(method) {
|
|
5606
|
-
const prefix = /^(evm|btc|sol|tron
|
|
4891
|
+
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5607
4892
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
5608
4893
|
if (!defaultAppName) return this._ctx;
|
|
5609
4894
|
return {
|
|
@@ -5637,209 +4922,6 @@ var LedgerConnectorBase = class {
|
|
|
5637
4922
|
return error;
|
|
5638
4923
|
}
|
|
5639
4924
|
};
|
|
5640
|
-
|
|
5641
|
-
// src/transport/DmkTransport.ts
|
|
5642
|
-
import Transport from "@ledgerhq/hw-transport";
|
|
5643
|
-
var DmkTransport = class extends Transport {
|
|
5644
|
-
constructor(dmk, sessionId) {
|
|
5645
|
-
super();
|
|
5646
|
-
this._dmk = dmk;
|
|
5647
|
-
this._sessionId = sessionId;
|
|
5648
|
-
}
|
|
5649
|
-
async exchange(apdu) {
|
|
5650
|
-
const response = await this._dmk.sendApdu({
|
|
5651
|
-
sessionId: this._sessionId,
|
|
5652
|
-
apdu: new Uint8Array(apdu)
|
|
5653
|
-
});
|
|
5654
|
-
const { data, statusCode } = response;
|
|
5655
|
-
const result = Buffer.alloc(data.length + 2);
|
|
5656
|
-
if (data.length > 0) {
|
|
5657
|
-
result.set(data, 0);
|
|
5658
|
-
}
|
|
5659
|
-
result.set(statusCode, data.length);
|
|
5660
|
-
return result;
|
|
5661
|
-
}
|
|
5662
|
-
async close() {
|
|
5663
|
-
}
|
|
5664
|
-
};
|
|
5665
|
-
|
|
5666
|
-
// src/app/AppManager.ts
|
|
5667
|
-
import {
|
|
5668
|
-
CloseAppCommand,
|
|
5669
|
-
GetAppAndVersionCommand,
|
|
5670
|
-
OpenAppCommand,
|
|
5671
|
-
isSuccessCommandResult
|
|
5672
|
-
} from "@ledgerhq/device-management-kit";
|
|
5673
|
-
import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
|
|
5674
|
-
var APP_NAME_MAP = {
|
|
5675
|
-
ETH: "Ethereum",
|
|
5676
|
-
BTC: "Bitcoin",
|
|
5677
|
-
SOL: "Solana",
|
|
5678
|
-
TRX: "Tron",
|
|
5679
|
-
ZEC: "Zcash",
|
|
5680
|
-
XRP: "XRP",
|
|
5681
|
-
ADA: "Cardano",
|
|
5682
|
-
DOT: "Polkadot",
|
|
5683
|
-
ATOM: "Cosmos"
|
|
5684
|
-
};
|
|
5685
|
-
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5686
|
-
var AppManager = class {
|
|
5687
|
-
constructor(dmk, options) {
|
|
5688
|
-
this._dmk = dmk;
|
|
5689
|
-
this._waitMs = options?.waitMs ?? 1e3;
|
|
5690
|
-
this._maxRetries = options?.maxRetries ?? 10;
|
|
5691
|
-
}
|
|
5692
|
-
/**
|
|
5693
|
-
* Return the Ledger app name for a given chain ticker,
|
|
5694
|
-
* or undefined if the chain is not supported.
|
|
5695
|
-
*/
|
|
5696
|
-
static getAppName(chain) {
|
|
5697
|
-
return APP_NAME_MAP[chain];
|
|
5698
|
-
}
|
|
5699
|
-
/**
|
|
5700
|
-
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5701
|
-
*
|
|
5702
|
-
* Flow:
|
|
5703
|
-
* 1. Check the currently running app.
|
|
5704
|
-
* 2. If it is already the target, return immediately.
|
|
5705
|
-
* 3. If a different app is running (not dashboard), close it first.
|
|
5706
|
-
* 4. Open the target app.
|
|
5707
|
-
* 5. Poll until the device confirms the target app is running.
|
|
5708
|
-
*/
|
|
5709
|
-
/**
|
|
5710
|
-
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5711
|
-
* device is about to display "Open <app>" on screen and wait for the
|
|
5712
|
-
* user's button press. UI consumers should show their "open app" prompt
|
|
5713
|
-
* in response. NOT called when the target app is already open (no user
|
|
5714
|
-
* interaction needed in that case).
|
|
5715
|
-
*
|
|
5716
|
-
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5717
|
-
* has physically confirmed on the device, so anything that runs AFTER
|
|
5718
|
-
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5719
|
-
* Hence the callback must fire BEFORE that await.
|
|
5720
|
-
*/
|
|
5721
|
-
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5722
|
-
const currentApp = await this._getCurrentApp(sessionId);
|
|
5723
|
-
if (currentApp === targetAppName) {
|
|
5724
|
-
return;
|
|
5725
|
-
}
|
|
5726
|
-
if (!this._isDashboard(currentApp)) {
|
|
5727
|
-
await this._closeCurrentApp(sessionId);
|
|
5728
|
-
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5729
|
-
}
|
|
5730
|
-
onConfirmOnDevice?.();
|
|
5731
|
-
await this._openApp(sessionId, targetAppName);
|
|
5732
|
-
await this._waitForApp(sessionId, targetAppName);
|
|
5733
|
-
}
|
|
5734
|
-
// ---------------------------------------------------------------------------
|
|
5735
|
-
// Private helpers
|
|
5736
|
-
// ---------------------------------------------------------------------------
|
|
5737
|
-
async _getCurrentApp(sessionId) {
|
|
5738
|
-
const result = await this._dmk.sendCommand({
|
|
5739
|
-
sessionId,
|
|
5740
|
-
command: new GetAppAndVersionCommand()
|
|
5741
|
-
});
|
|
5742
|
-
if (isSuccessCommandResult(result)) {
|
|
5743
|
-
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5744
|
-
return result.data.name;
|
|
5745
|
-
}
|
|
5746
|
-
const errResult = result;
|
|
5747
|
-
const dmkErr = errResult.error ?? {};
|
|
5748
|
-
const original = dmkErr.originalError;
|
|
5749
|
-
debugLog(
|
|
5750
|
-
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5751
|
-
sessionId,
|
|
5752
|
-
"tag=",
|
|
5753
|
-
dmkErr._tag,
|
|
5754
|
-
"errorCode=",
|
|
5755
|
-
dmkErr.errorCode,
|
|
5756
|
-
"message=",
|
|
5757
|
-
dmkErr.message,
|
|
5758
|
-
"originalErrorMessage=",
|
|
5759
|
-
original?.message ?? String(original ?? "")
|
|
5760
|
-
);
|
|
5761
|
-
throw Object.assign(
|
|
5762
|
-
new Error(
|
|
5763
|
-
dmkErr.message ?? "Failed to get current app from device"
|
|
5764
|
-
),
|
|
5765
|
-
{
|
|
5766
|
-
_tag: dmkErr._tag,
|
|
5767
|
-
errorCode: dmkErr.errorCode,
|
|
5768
|
-
originalError: original
|
|
5769
|
-
}
|
|
5770
|
-
);
|
|
5771
|
-
}
|
|
5772
|
-
async _openApp(sessionId, appName) {
|
|
5773
|
-
const result = await this._dmk.sendCommand({
|
|
5774
|
-
sessionId,
|
|
5775
|
-
command: new OpenAppCommand({ appName })
|
|
5776
|
-
});
|
|
5777
|
-
if (!isSuccessCommandResult(result)) {
|
|
5778
|
-
const dmkErr = result.error;
|
|
5779
|
-
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5780
|
-
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5781
|
-
debugLog(
|
|
5782
|
-
"[AppManager] openApp failed:",
|
|
5783
|
-
appName,
|
|
5784
|
-
"errorCode:",
|
|
5785
|
-
errorCode,
|
|
5786
|
-
"tag:",
|
|
5787
|
-
dmkErr._tag
|
|
5788
|
-
);
|
|
5789
|
-
let code;
|
|
5790
|
-
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5791
|
-
code = HardwareErrorCode8.AppNotInstalled;
|
|
5792
|
-
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5793
|
-
code = HardwareErrorCode8.UserRejected;
|
|
5794
|
-
}
|
|
5795
|
-
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5796
|
-
_tag: ERROR_TAG.OpenAppCommand,
|
|
5797
|
-
code,
|
|
5798
|
-
errorCode,
|
|
5799
|
-
statusCode: errorCode,
|
|
5800
|
-
appName,
|
|
5801
|
-
originalError: dmkErr
|
|
5802
|
-
});
|
|
5803
|
-
}
|
|
5804
|
-
}
|
|
5805
|
-
async _closeCurrentApp(sessionId) {
|
|
5806
|
-
debugLog("[AppManager] closeCurrentApp");
|
|
5807
|
-
await this._dmk.sendCommand({
|
|
5808
|
-
sessionId,
|
|
5809
|
-
command: new CloseAppCommand()
|
|
5810
|
-
});
|
|
5811
|
-
}
|
|
5812
|
-
/**
|
|
5813
|
-
* Poll the device until the expected app is reported as running,
|
|
5814
|
-
* or throw after `_maxRetries` attempts.
|
|
5815
|
-
*/
|
|
5816
|
-
async _waitForApp(sessionId, expectedAppName) {
|
|
5817
|
-
let lastSeen = "";
|
|
5818
|
-
for (let i = 0; i < this._maxRetries; i++) {
|
|
5819
|
-
await this._wait();
|
|
5820
|
-
const current = await this._getCurrentApp(sessionId);
|
|
5821
|
-
lastSeen = current;
|
|
5822
|
-
if (current === expectedAppName) {
|
|
5823
|
-
return;
|
|
5824
|
-
}
|
|
5825
|
-
}
|
|
5826
|
-
debugLog(
|
|
5827
|
-
"[AppManager] waitForApp exhausted: expected=",
|
|
5828
|
-
expectedAppName,
|
|
5829
|
-
"lastSeen=",
|
|
5830
|
-
lastSeen
|
|
5831
|
-
);
|
|
5832
|
-
throw new Error(
|
|
5833
|
-
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5834
|
-
);
|
|
5835
|
-
}
|
|
5836
|
-
_isDashboard(appName) {
|
|
5837
|
-
return appName === DASHBOARD_APP_NAME;
|
|
5838
|
-
}
|
|
5839
|
-
_wait() {
|
|
5840
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5841
|
-
}
|
|
5842
|
-
};
|
|
5843
4925
|
export {
|
|
5844
4926
|
AppManager,
|
|
5845
4927
|
DmkTransport,
|
|
@@ -5850,7 +4932,6 @@ export {
|
|
|
5850
4932
|
SignerEth,
|
|
5851
4933
|
SignerManager,
|
|
5852
4934
|
SignerSol,
|
|
5853
|
-
SignerZcash,
|
|
5854
4935
|
debugLog,
|
|
5855
4936
|
deviceActionToPromise,
|
|
5856
4937
|
isDeviceLockedError,
|