@onekeyfe/hwk-ledger-adapter 1.1.26-alpha.9 → 1.1.26-patch.1
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 +253 -176
- package/dist/index.d.ts +253 -176
- package/dist/index.js +1396 -447
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1397 -449
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -39,13 +39,16 @@ __export(index_exports, {
|
|
|
39
39
|
SignerEth: () => SignerEth,
|
|
40
40
|
SignerManager: () => SignerManager,
|
|
41
41
|
SignerSol: () => SignerSol,
|
|
42
|
+
debugLog: () => debugLog,
|
|
42
43
|
deviceActionToPromise: () => deviceActionToPromise,
|
|
43
|
-
extractBleHexId: () => extractBleHexId,
|
|
44
|
-
isDebugEnabled: () => isDebugEnabled,
|
|
45
44
|
isDeviceLockedError: () => isDeviceLockedError,
|
|
45
|
+
isLedgerBleConnectionType: () => isLedgerBleConnectionType,
|
|
46
|
+
isLedgerBleDescriptor: () => isLedgerBleDescriptor,
|
|
47
|
+
isLedgerDmkBleTransport: () => isLedgerDmkBleTransport,
|
|
46
48
|
ledgerFailure: () => ledgerFailure,
|
|
47
49
|
mapLedgerError: () => mapLedgerError,
|
|
48
|
-
|
|
50
|
+
offSdkEvent: () => offSdkEvent,
|
|
51
|
+
onSdkEvent: () => onSdkEvent
|
|
49
52
|
});
|
|
50
53
|
module.exports = __toCommonJS(index_exports);
|
|
51
54
|
|
|
@@ -54,24 +57,32 @@ var import_hwk_adapter_core2 = require("@onekeyfe/hwk-adapter-core");
|
|
|
54
57
|
|
|
55
58
|
// src/errors.ts
|
|
56
59
|
var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
|
|
57
|
-
|
|
60
|
+
var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
|
|
61
|
+
function createMultipleUsbLedgerDevicesError() {
|
|
62
|
+
return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
|
|
63
|
+
code: import_hwk_adapter_core.HardwareErrorCode.DeviceOneDeviceOnly
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function ledgerFailure(code, error, appName, tag, params) {
|
|
58
67
|
const payload = { error, code };
|
|
59
68
|
if (appName !== void 0) payload.appName = appName;
|
|
69
|
+
if (tag !== void 0) payload._tag = tag;
|
|
70
|
+
if (params !== void 0) payload.params = params;
|
|
60
71
|
return { success: false, payload };
|
|
61
72
|
}
|
|
62
73
|
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
63
74
|
var USER_REJECTED_CODES = /* @__PURE__ */ new Set(["6985", "27013"]);
|
|
64
75
|
var WRONG_APP_CODES = /* @__PURE__ */ new Set(["6e00", "28160", "6d00", "27904", "6a83", "27267"]);
|
|
65
76
|
var APP_NOT_INSTALLED_CODES = /* @__PURE__ */ new Set(["6807", "26631"]);
|
|
66
|
-
var
|
|
77
|
+
var STEP_BLIND_SIGN_TRANSACTION_FALLBACK = "signer.eth.steps.blindSignTransactionFallback";
|
|
67
78
|
function getEthAppErrorCode(err) {
|
|
68
79
|
if (!err || typeof err !== "object") return null;
|
|
69
80
|
const e = err;
|
|
70
|
-
if (e._tag ===
|
|
81
|
+
if (e._tag === ERROR_TAG.EthAppCommand && typeof e.errorCode === "string") {
|
|
71
82
|
return e.errorCode.toLowerCase();
|
|
72
83
|
}
|
|
73
84
|
const orig = e.originalError;
|
|
74
|
-
if (orig?._tag ===
|
|
85
|
+
if (orig?._tag === ERROR_TAG.EthAppCommand && typeof orig.errorCode === "string") {
|
|
75
86
|
return orig.errorCode.toLowerCase();
|
|
76
87
|
}
|
|
77
88
|
return null;
|
|
@@ -128,13 +139,8 @@ function mapBtcAppError(hex) {
|
|
|
128
139
|
return null;
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
|
-
function
|
|
142
|
+
function mapEthAppErrorCode(ethCode) {
|
|
132
143
|
switch (ethCode) {
|
|
133
|
-
case "6a80":
|
|
134
|
-
if (lastStep === STEP_BLIND_SIGN_FALLBACK) {
|
|
135
|
-
return import_hwk_adapter_core.HardwareErrorCode.EvmBlindSigningRequired;
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
144
|
case "6984":
|
|
139
145
|
return import_hwk_adapter_core.HardwareErrorCode.EvmClearSignPluginMissing;
|
|
140
146
|
case "6a84":
|
|
@@ -147,16 +153,133 @@ function mapEthAppError(ethCode, lastStep) {
|
|
|
147
153
|
return null;
|
|
148
154
|
}
|
|
149
155
|
}
|
|
156
|
+
function classifyEthAppError(err) {
|
|
157
|
+
const ethCode = getEthAppErrorCode(err);
|
|
158
|
+
if (!ethCode) return null;
|
|
159
|
+
if (ethCode === "6a80") {
|
|
160
|
+
return hasBlindSignFallbackStep(err) ? import_hwk_adapter_core.HardwareErrorCode.EvmBlindSigningRequired : null;
|
|
161
|
+
}
|
|
162
|
+
return mapEthAppErrorCode(ethCode);
|
|
163
|
+
}
|
|
164
|
+
var ERROR_TAG = {
|
|
165
|
+
// SDK-mint
|
|
166
|
+
DeviceNotAdvertising: "DeviceNotAdvertisingError",
|
|
167
|
+
// BLE scan miss
|
|
168
|
+
DeviceNotInDiscoveryCache: "DeviceNotInDiscoveryCacheError",
|
|
169
|
+
// dm.connect() before enumerate
|
|
170
|
+
BlePairingTimeout: "BlePairingTimeoutError",
|
|
171
|
+
// SMP 30s timeout
|
|
172
|
+
BleGattBondingFailed: "BleGattBondingFailedError",
|
|
173
|
+
// other GATT failure
|
|
174
|
+
UserAborted: "UserAborted",
|
|
175
|
+
DeviceAppStuck: "DeviceAppStuck",
|
|
176
|
+
// chain app wedged (APDU 0x6901)
|
|
177
|
+
DeviceTransportStuck: "DeviceTransportStuck",
|
|
178
|
+
// DMK transport queue wedged
|
|
179
|
+
// DMK-reuse (DMK throws same string; we synthesize too)
|
|
180
|
+
DeviceLocked: "DeviceLockedError",
|
|
181
|
+
DeviceNotRecognized: "DeviceNotRecognizedError",
|
|
182
|
+
DeviceSessionNotFound: "DeviceSessionNotFound",
|
|
183
|
+
OpenAppCommand: "OpenAppCommandError",
|
|
184
|
+
// DMK-only (read only)
|
|
185
|
+
EthAppCommand: "EthAppCommandError",
|
|
186
|
+
UserRefusedOnDevice: "UserRefusedOnDevice",
|
|
187
|
+
WrongAppOpened: "WrongAppOpenedError",
|
|
188
|
+
InvalidStatusWord: "InvalidStatusWordError",
|
|
189
|
+
AlreadySendingApdu: "AlreadySendingApduError",
|
|
190
|
+
UnknownDeviceExchange: "UnknownDeviceExchangeError",
|
|
191
|
+
NoAccessibleDevice: "NoAccessibleDeviceError",
|
|
192
|
+
UnknownDevice: "UnknownDeviceError",
|
|
193
|
+
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
194
|
+
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
195
|
+
OpeningConnection: "OpeningConnectionError",
|
|
196
|
+
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
197
|
+
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
198
|
+
Disconnect: "DisconnectError",
|
|
199
|
+
ReconnectionFailed: "ReconnectionFailedError",
|
|
200
|
+
WebHIDDisconnect: "WebHIDDisconnectError",
|
|
201
|
+
// ble-plx surfaces this when GATT notification setup fails — typical
|
|
202
|
+
// outcome when the user doesn't confirm pairing on the device, or the
|
|
203
|
+
// existing bond is invalid. Observed in production after ~30s.
|
|
204
|
+
PairingRefused: "PairingRefusedError"
|
|
205
|
+
};
|
|
150
206
|
function isDeviceLockedError(err) {
|
|
151
207
|
if (!err || typeof err !== "object") return false;
|
|
152
208
|
const e = err;
|
|
153
209
|
if (e.errorCode != null && LOCKED_ERROR_CODES.has(String(e.errorCode))) return true;
|
|
154
210
|
if (e.statusCode != null && LOCKED_ERROR_CODES.has(String(e.statusCode))) return true;
|
|
155
|
-
if (e._tag ===
|
|
211
|
+
if (e._tag === ERROR_TAG.DeviceLocked) return true;
|
|
156
212
|
if (e.originalError != null && isDeviceLockedError(e.originalError)) return true;
|
|
157
213
|
if (e.error != null && e._tag && isDeviceLockedError(e.error)) return true;
|
|
158
214
|
return false;
|
|
159
215
|
}
|
|
216
|
+
function isDeviceNotAdvertisingError(err) {
|
|
217
|
+
if (!err || typeof err !== "object") return false;
|
|
218
|
+
return err._tag === ERROR_TAG.DeviceNotAdvertising;
|
|
219
|
+
}
|
|
220
|
+
var PAIRING_FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
221
|
+
ERROR_TAG.BlePairingTimeout,
|
|
222
|
+
ERROR_TAG.BleGattBondingFailed,
|
|
223
|
+
ERROR_TAG.PairingRefused
|
|
224
|
+
]);
|
|
225
|
+
function isBlePairingFailureError(err) {
|
|
226
|
+
if (!err || typeof err !== "object") return false;
|
|
227
|
+
const tag = err._tag;
|
|
228
|
+
if (tag && PAIRING_FAILURE_TAGS.has(tag)) return true;
|
|
229
|
+
const orig = err.originalError;
|
|
230
|
+
if (orig != null && isBlePairingFailureError(orig)) return true;
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
234
|
+
ERROR_TAG.DeviceLocked,
|
|
235
|
+
ERROR_TAG.DeviceNotAdvertising,
|
|
236
|
+
ERROR_TAG.BlePairingTimeout,
|
|
237
|
+
ERROR_TAG.BleGattBondingFailed,
|
|
238
|
+
ERROR_TAG.PairingRefused,
|
|
239
|
+
ERROR_TAG.DeviceNotRecognized,
|
|
240
|
+
ERROR_TAG.NoAccessibleDevice,
|
|
241
|
+
ERROR_TAG.UnknownDevice,
|
|
242
|
+
ERROR_TAG.DeviceSessionNotFound,
|
|
243
|
+
ERROR_TAG.DeviceSessionRefresher,
|
|
244
|
+
ERROR_TAG.DeviceNotInitialized,
|
|
245
|
+
ERROR_TAG.OpeningConnection,
|
|
246
|
+
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
247
|
+
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
248
|
+
ERROR_TAG.Disconnect,
|
|
249
|
+
ERROR_TAG.ReconnectionFailed,
|
|
250
|
+
ERROR_TAG.WebHIDDisconnect
|
|
251
|
+
]);
|
|
252
|
+
var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
253
|
+
ERROR_TAG.NoAccessibleDevice,
|
|
254
|
+
ERROR_TAG.UnknownDevice,
|
|
255
|
+
ERROR_TAG.DeviceNotInitialized,
|
|
256
|
+
// SDK-internal: dm.connect() called before _discovered was populated.
|
|
257
|
+
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
258
|
+
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
259
|
+
]);
|
|
260
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
|
|
261
|
+
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
262
|
+
ERROR_TAG.DeviceNotRecognized,
|
|
263
|
+
ERROR_TAG.DeviceSessionNotFound,
|
|
264
|
+
ERROR_TAG.DeviceSessionRefresher,
|
|
265
|
+
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
266
|
+
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
267
|
+
ERROR_TAG.Disconnect,
|
|
268
|
+
ERROR_TAG.ReconnectionFailed,
|
|
269
|
+
ERROR_TAG.WebHIDDisconnect
|
|
270
|
+
]);
|
|
271
|
+
function isConnectionLevelError(err) {
|
|
272
|
+
if (!err || typeof err !== "object") return false;
|
|
273
|
+
const tag = err._tag;
|
|
274
|
+
if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
|
|
275
|
+
const e = err;
|
|
276
|
+
if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
|
|
277
|
+
if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
function isKnownConnectionTag(tag) {
|
|
281
|
+
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
282
|
+
}
|
|
160
283
|
function hasStatusCode(err, codeSet) {
|
|
161
284
|
if (!err || typeof err !== "object") return false;
|
|
162
285
|
const e = err;
|
|
@@ -166,10 +289,39 @@ function hasStatusCode(err, codeSet) {
|
|
|
166
289
|
if (e.error != null && e._tag && hasStatusCode(e.error, codeSet)) return true;
|
|
167
290
|
return false;
|
|
168
291
|
}
|
|
292
|
+
function hasBlindSignFallbackStep(err) {
|
|
293
|
+
if (!err || typeof err !== "object") return false;
|
|
294
|
+
const e = err;
|
|
295
|
+
if (e._lastStep === STEP_BLIND_SIGN_TRANSACTION_FALLBACK) return true;
|
|
296
|
+
if (Array.isArray(e._deviceActionSteps) && e._deviceActionSteps.includes(STEP_BLIND_SIGN_TRANSACTION_FALLBACK)) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (e.originalError != null && hasBlindSignFallbackStep(e.originalError)) return true;
|
|
300
|
+
if (e.error != null && e._tag && hasBlindSignFallbackStep(e.error)) return true;
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
function isDeviceNotFoundError(err) {
|
|
304
|
+
if (!err || typeof err !== "object") return false;
|
|
305
|
+
const tag = err._tag;
|
|
306
|
+
if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
|
|
307
|
+
const e = err;
|
|
308
|
+
if (e.originalError != null && isDeviceNotFoundError(e.originalError)) return true;
|
|
309
|
+
if (e.error != null && e._tag && isDeviceNotFoundError(e.error)) return true;
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
function isDeviceBusyError(err) {
|
|
313
|
+
if (!err || typeof err !== "object") return false;
|
|
314
|
+
const tag = err._tag;
|
|
315
|
+
if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
|
|
316
|
+
const e = err;
|
|
317
|
+
if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
|
|
318
|
+
if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
169
321
|
function isUserRejectedError(err) {
|
|
170
322
|
if (!err || typeof err !== "object") return false;
|
|
171
323
|
const e = err;
|
|
172
|
-
if (e._tag ===
|
|
324
|
+
if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
|
|
173
325
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
174
326
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
175
327
|
return false;
|
|
@@ -177,7 +329,7 @@ function isUserRejectedError(err) {
|
|
|
177
329
|
function isWrongAppError(err) {
|
|
178
330
|
if (!err || typeof err !== "object") return false;
|
|
179
331
|
const e = err;
|
|
180
|
-
if (e._tag ===
|
|
332
|
+
if (e._tag === ERROR_TAG.WrongAppOpened || e._tag === ERROR_TAG.InvalidStatusWord) {
|
|
181
333
|
if (hasStatusCode(err, WRONG_APP_CODES)) return true;
|
|
182
334
|
}
|
|
183
335
|
if (typeof e.message === "string") {
|
|
@@ -191,7 +343,6 @@ function isWrongAppError(err) {
|
|
|
191
343
|
function isAppNotInstalledError(err) {
|
|
192
344
|
if (!err || typeof err !== "object") return false;
|
|
193
345
|
const e = err;
|
|
194
|
-
if (e._tag === "OpenAppCommandError") return true;
|
|
195
346
|
if (typeof e.message === "string" && /unknown application/i.test(e.message)) return true;
|
|
196
347
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
197
348
|
return false;
|
|
@@ -199,7 +350,8 @@ function isAppNotInstalledError(err) {
|
|
|
199
350
|
function isDeviceDisconnectedError(err) {
|
|
200
351
|
if (!err || typeof err !== "object") return false;
|
|
201
352
|
const e = err;
|
|
202
|
-
|
|
353
|
+
const tag = e._tag;
|
|
354
|
+
if (typeof tag === "string" && DEVICE_DISCONNECTED_TAGS.has(tag)) return true;
|
|
203
355
|
if (typeof e.message === "string") {
|
|
204
356
|
const msg = e.message.toLowerCase();
|
|
205
357
|
if (msg.includes("disconnected") || msg.includes("not found") || msg.includes("no device") || msg.includes("unplugged"))
|
|
@@ -219,6 +371,21 @@ function isTimeoutError(err) {
|
|
|
219
371
|
if (e.code === import_hwk_adapter_core.HardwareErrorCode.OperationTimeout) return true;
|
|
220
372
|
return false;
|
|
221
373
|
}
|
|
374
|
+
function isAppStuckByApdu(err) {
|
|
375
|
+
if (!err || typeof err !== "object") return false;
|
|
376
|
+
const tag = err._tag;
|
|
377
|
+
if (tag === ERROR_TAG.DeviceAppStuck) return true;
|
|
378
|
+
return extractApduHex(err) === "6901";
|
|
379
|
+
}
|
|
380
|
+
function isTransportStuck(err) {
|
|
381
|
+
if (!err || typeof err !== "object") return false;
|
|
382
|
+
const tag = err._tag;
|
|
383
|
+
return tag === ERROR_TAG.DeviceTransportStuck || // already wrapped form
|
|
384
|
+
tag === ERROR_TAG.UnknownDeviceExchange || tag === ERROR_TAG.AlreadySendingApdu;
|
|
385
|
+
}
|
|
386
|
+
function isStuckAppStateError(err) {
|
|
387
|
+
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
388
|
+
}
|
|
222
389
|
function mapLedgerError(err, opts) {
|
|
223
390
|
let originalMessage = "Unknown Ledger error";
|
|
224
391
|
if (err instanceof Error) {
|
|
@@ -230,20 +397,24 @@ function mapLedgerError(err, opts) {
|
|
|
230
397
|
let code;
|
|
231
398
|
if (isDeviceLockedError(err)) {
|
|
232
399
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceLocked;
|
|
400
|
+
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
401
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound;
|
|
402
|
+
} else if (isDeviceBusyError(err)) {
|
|
403
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
|
|
404
|
+
} else if (isBlePairingFailureError(err)) {
|
|
405
|
+
code = import_hwk_adapter_core.HardwareErrorCode.BlePairingTimeout;
|
|
233
406
|
} else if (isUserRejectedError(err)) {
|
|
234
407
|
code = import_hwk_adapter_core.HardwareErrorCode.UserRejected;
|
|
235
408
|
} else if (isWrongAppError(err)) {
|
|
236
409
|
code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
|
|
237
410
|
} else if (isAppNotInstalledError(err)) {
|
|
238
|
-
code = import_hwk_adapter_core.HardwareErrorCode.
|
|
411
|
+
code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
|
|
239
412
|
} else if (isDeviceDisconnectedError(err)) {
|
|
240
413
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
|
|
241
414
|
} else if (isTimeoutError(err)) {
|
|
242
415
|
code = import_hwk_adapter_core.HardwareErrorCode.OperationTimeout;
|
|
243
416
|
} else {
|
|
244
|
-
const
|
|
245
|
-
const lastStep = err && typeof err === "object" ? err._lastStep : void 0;
|
|
246
|
-
const ethMapped = ethCode ? mapEthAppError(ethCode, lastStep) : null;
|
|
417
|
+
const ethMapped = classifyEthAppError(err);
|
|
247
418
|
const apduHex = ethMapped ? null : extractApduHex(err);
|
|
248
419
|
const chainMapped = apduHex ? mapSolanaAppError(apduHex) ?? mapTronAppError(apduHex) ?? mapBtcAppError(apduHex) : null;
|
|
249
420
|
code = ethMapped ?? chainMapped ?? import_hwk_adapter_core.HardwareErrorCode.UnknownError;
|
|
@@ -253,38 +424,100 @@ function mapLedgerError(err, opts) {
|
|
|
253
424
|
return { code, message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage), appName };
|
|
254
425
|
}
|
|
255
426
|
|
|
256
|
-
// src/utils/
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
427
|
+
// src/utils/ledgerDmkTransport.ts
|
|
428
|
+
function isLedgerDmkBleTransport(transport) {
|
|
429
|
+
const normalized = transport?.toUpperCase();
|
|
430
|
+
return normalized === "BLE" || normalized?.endsWith("_BLE") === true;
|
|
260
431
|
}
|
|
261
|
-
function
|
|
262
|
-
return
|
|
432
|
+
function isLedgerBleConnectionType(connectionType) {
|
|
433
|
+
return connectionType === "ble";
|
|
263
434
|
}
|
|
264
|
-
function
|
|
265
|
-
|
|
266
|
-
|
|
435
|
+
function isLedgerBleDescriptor(connectionType, descriptor) {
|
|
436
|
+
return isLedgerBleConnectionType(connectionType) || isLedgerDmkBleTransport(descriptor.transport);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/utils/sdkEventBus.ts
|
|
440
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
441
|
+
function onSdkEvent(listener) {
|
|
442
|
+
listeners.add(listener);
|
|
443
|
+
return () => {
|
|
444
|
+
listeners.delete(listener);
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function offSdkEvent(listener) {
|
|
448
|
+
listeners.delete(listener);
|
|
449
|
+
}
|
|
450
|
+
function emitSdkEvent(event) {
|
|
451
|
+
if (listeners.size === 0) return;
|
|
452
|
+
for (const listener of listeners) {
|
|
453
|
+
try {
|
|
454
|
+
listener(event);
|
|
455
|
+
} catch {
|
|
456
|
+
}
|
|
267
457
|
}
|
|
268
458
|
}
|
|
269
|
-
function
|
|
270
|
-
if (
|
|
271
|
-
|
|
459
|
+
function emitLog(level, ...args) {
|
|
460
|
+
if (listeners.size === 0) return;
|
|
461
|
+
const message = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
|
|
462
|
+
emitSdkEvent({ type: "log", level, message });
|
|
463
|
+
}
|
|
464
|
+
function safeStringify(value) {
|
|
465
|
+
if (value instanceof Error) {
|
|
466
|
+
return value.stack ? `${value.message}
|
|
467
|
+
${value.stack}` : value.message;
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
return JSON.stringify(value);
|
|
471
|
+
} catch {
|
|
472
|
+
return String(value);
|
|
272
473
|
}
|
|
273
474
|
}
|
|
274
475
|
|
|
476
|
+
// src/utils/debugLog.ts
|
|
477
|
+
function debugLog(...args) {
|
|
478
|
+
emitLog("debug", ...args);
|
|
479
|
+
}
|
|
480
|
+
function debugError(...args) {
|
|
481
|
+
emitLog("error", ...args);
|
|
482
|
+
}
|
|
483
|
+
|
|
275
484
|
// src/adapter/LedgerAdapter.ts
|
|
276
485
|
function formatDeviceMismatchError(expected, actual) {
|
|
277
486
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
278
487
|
}
|
|
488
|
+
var BTC_HIGH_INDEX_THRESHOLD = 100;
|
|
489
|
+
function btcAccountIndexFromPath(path) {
|
|
490
|
+
const segments = path.replace(/^m\//, "").split("/");
|
|
491
|
+
if (segments.length < 3) return null;
|
|
492
|
+
const accountSeg = segments[2].replace(/['h]$/i, "");
|
|
493
|
+
const accountIndex = parseInt(accountSeg, 10);
|
|
494
|
+
return Number.isFinite(accountIndex) ? accountIndex : null;
|
|
495
|
+
}
|
|
279
496
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
280
|
-
constructor(connector
|
|
497
|
+
constructor(connector) {
|
|
281
498
|
this.vendor = "ledger";
|
|
282
499
|
this.emitter = new import_hwk_adapter_core2.TypedEventEmitter();
|
|
283
|
-
// Device cache: tracks discovered devices from connector events
|
|
284
500
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
285
|
-
// Session tracking: maps connectId -> sessionId
|
|
286
501
|
this._sessions = /* @__PURE__ */ new Map();
|
|
287
502
|
this._uiRegistry = new import_hwk_adapter_core2.UiRequestRegistry();
|
|
503
|
+
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
504
|
+
// adapter instance: first 100+ path asks the user once via UI request,
|
|
505
|
+
// subsequent 100+ paths in the same session auto-promote silently.
|
|
506
|
+
// The Ledger device itself still requires a per-call confirmation — that's
|
|
507
|
+
// the Ledger app's safety boundary, not ours to bypass.
|
|
508
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
509
|
+
// Shared across concurrent callers — only `cancel()` aborts.
|
|
510
|
+
this._doConnectAbortController = null;
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
// Private helpers
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
/**
|
|
515
|
+
* Ensure at least one device is connected and return a valid connectId.
|
|
516
|
+
*
|
|
517
|
+
* - If a session already exists for the given connectId, reuse it.
|
|
518
|
+
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
519
|
+
* - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
|
|
520
|
+
*/
|
|
288
521
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
289
522
|
this._connectingPromise = null;
|
|
290
523
|
// ---------------------------------------------------------------------------
|
|
@@ -314,40 +547,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
314
547
|
this.emitter.emit("ui-event", event);
|
|
315
548
|
};
|
|
316
549
|
this.connector = connector;
|
|
317
|
-
this.
|
|
318
|
-
this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue({
|
|
319
|
-
emit: (event, data) => this.emitter.emit(event, data),
|
|
320
|
-
uiRegistry: this._uiRegistry
|
|
321
|
-
});
|
|
550
|
+
this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue();
|
|
322
551
|
this.registerEventListeners();
|
|
323
552
|
}
|
|
324
|
-
/**
|
|
325
|
-
* Classify a method's interruptibility.
|
|
326
|
-
* - Signing / typed data / transaction → 'confirm' (user may decide via preemption UI)
|
|
327
|
-
* - Read-only queries (getAddress / getPublicKey / getMasterFingerprint) → 'safe'
|
|
328
|
-
* (auto-cancels any pending read for the same device)
|
|
329
|
-
*/
|
|
330
|
-
static _getInterruptibility(method) {
|
|
331
|
-
if (method.toLowerCase().includes("sign")) return "confirm";
|
|
332
|
-
return "safe";
|
|
333
|
-
}
|
|
334
|
-
// ---------------------------------------------------------------------------
|
|
335
553
|
// Transport
|
|
336
|
-
// ---------------------------------------------------------------------------
|
|
337
|
-
// Transport is decided at connector creation time. These methods
|
|
338
|
-
// satisfy the IHardwareWallet interface with sensible defaults.
|
|
339
554
|
get activeTransport() {
|
|
340
|
-
return this.connector.connectionType
|
|
555
|
+
return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
|
|
341
556
|
}
|
|
342
557
|
getAvailableTransports() {
|
|
343
558
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
344
559
|
}
|
|
345
|
-
|
|
560
|
+
// Connector is bound at construction; switching requires a new adapter.
|
|
561
|
+
switchTransport(_type) {
|
|
562
|
+
return Promise.resolve();
|
|
346
563
|
}
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
564
|
// Lifecycle
|
|
349
|
-
|
|
350
|
-
|
|
565
|
+
init(_config) {
|
|
566
|
+
return Promise.resolve();
|
|
351
567
|
}
|
|
352
568
|
/**
|
|
353
569
|
* Clear cached device/session state without tearing down the adapter.
|
|
@@ -360,6 +576,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
360
576
|
this._connectingPromise = null;
|
|
361
577
|
this._uiRegistry.reset();
|
|
362
578
|
this._jobQueue.clear();
|
|
579
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
363
580
|
}
|
|
364
581
|
async dispose() {
|
|
365
582
|
this._uiRegistry.reset();
|
|
@@ -368,6 +585,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
368
585
|
this.connector.reset();
|
|
369
586
|
this._discoveredDevices.clear();
|
|
370
587
|
this._sessions.clear();
|
|
588
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
371
589
|
this.emitter.removeAllListeners();
|
|
372
590
|
}
|
|
373
591
|
uiResponse(response) {
|
|
@@ -376,10 +594,16 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
376
594
|
// ---------------------------------------------------------------------------
|
|
377
595
|
// Device management
|
|
378
596
|
// ---------------------------------------------------------------------------
|
|
379
|
-
async searchDevices() {
|
|
597
|
+
async searchDevices(options) {
|
|
598
|
+
if (options?.resetSession) {
|
|
599
|
+
this._doConnectAbortController?.abort();
|
|
600
|
+
this._sessions.clear();
|
|
601
|
+
this._connectingPromise = null;
|
|
602
|
+
this._doConnectAbortController = null;
|
|
603
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
604
|
+
}
|
|
380
605
|
await this._ensureDevicePermission();
|
|
381
606
|
const devices = await this.connector.searchDevices();
|
|
382
|
-
debugLog("[DMK] adapter.searchDevices raw:", JSON.stringify(devices));
|
|
383
607
|
this._discoveredDevices.clear();
|
|
384
608
|
for (const d of devices) {
|
|
385
609
|
if (d.connectId) {
|
|
@@ -389,11 +613,41 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
389
613
|
if (this._discoveredDevices.size === 0) {
|
|
390
614
|
await this._ensureDevicePermission();
|
|
391
615
|
}
|
|
616
|
+
debugLog(
|
|
617
|
+
`[LedgerAdapter] searchDevices() return count=${this._discoveredDevices.size} ids=[${[
|
|
618
|
+
...this._discoveredDevices.keys()
|
|
619
|
+
].join(",")}]`
|
|
620
|
+
);
|
|
392
621
|
return Array.from(this._discoveredDevices.values());
|
|
393
622
|
}
|
|
623
|
+
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
624
|
+
async _evictAllSessions() {
|
|
625
|
+
if (this._sessions.size === 0) return;
|
|
626
|
+
const stale = [...this._sessions.values()];
|
|
627
|
+
this._sessions.clear();
|
|
628
|
+
for (const sid of stale) {
|
|
629
|
+
try {
|
|
630
|
+
await this.connector.disconnect(sid);
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
static _createDeviceBusyError(method) {
|
|
636
|
+
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
637
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceBusy
|
|
638
|
+
});
|
|
639
|
+
}
|
|
394
640
|
async connectDevice(connectId) {
|
|
395
|
-
await this._ensureDevicePermission(connectId);
|
|
396
641
|
try {
|
|
642
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
643
|
+
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
644
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
648
|
+
await this._evictAllSessions();
|
|
649
|
+
}
|
|
650
|
+
await this._ensureDevicePermission(connectId);
|
|
397
651
|
const session = await this.connector.connect(connectId);
|
|
398
652
|
this._sessions.set(connectId, session.sessionId);
|
|
399
653
|
if (session.deviceInfo) {
|
|
@@ -429,7 +683,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
429
683
|
// Chain call helper
|
|
430
684
|
// ---------------------------------------------------------------------------
|
|
431
685
|
async callChain(connectId, deviceId, chain, method, params, skipFingerprint = false) {
|
|
432
|
-
await this._ensureDevicePermission(connectId, deviceId);
|
|
433
686
|
try {
|
|
434
687
|
const result = await this.connectorCall(connectId, method, params, {
|
|
435
688
|
chain,
|
|
@@ -441,45 +694,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
441
694
|
return this.errorToFailure(err);
|
|
442
695
|
}
|
|
443
696
|
}
|
|
444
|
-
/**
|
|
445
|
-
* Batch version of callChain — checks permission once,
|
|
446
|
-
* fingerprint is verified on the first call inside connectorCall.
|
|
447
|
-
*/
|
|
448
|
-
async callChainBatch(connectId, deviceId, chain, method, params, onProgress, skipFingerprint = false) {
|
|
449
|
-
await this._ensureDevicePermission(connectId, deviceId);
|
|
450
|
-
const results = [];
|
|
451
|
-
for (let i = 0; i < params.length; i++) {
|
|
452
|
-
try {
|
|
453
|
-
const result = await this.connectorCall(connectId, method, params[i], {
|
|
454
|
-
chain,
|
|
455
|
-
deviceId,
|
|
456
|
-
// Only verify fingerprint on the first call in the batch
|
|
457
|
-
skipFingerprint: skipFingerprint || i > 0
|
|
458
|
-
});
|
|
459
|
-
results.push(result);
|
|
460
|
-
onProgress?.({ index: i, total: params.length });
|
|
461
|
-
} catch (err) {
|
|
462
|
-
return this.errorToFailure(err);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return (0, import_hwk_adapter_core2.success)(results);
|
|
466
|
-
}
|
|
467
697
|
// ---------------------------------------------------------------------------
|
|
468
698
|
// EVM chain methods
|
|
469
699
|
// ---------------------------------------------------------------------------
|
|
470
700
|
evmGetAddress(connectId, deviceId, params) {
|
|
471
701
|
return this.callChain(connectId, deviceId, "evm", "evmGetAddress", params);
|
|
472
702
|
}
|
|
473
|
-
evmGetAddresses(connectId, deviceId, params, onProgress) {
|
|
474
|
-
return this.callChainBatch(
|
|
475
|
-
connectId,
|
|
476
|
-
deviceId,
|
|
477
|
-
"evm",
|
|
478
|
-
"evmGetAddress",
|
|
479
|
-
params,
|
|
480
|
-
onProgress
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
703
|
evmSignTransaction(connectId, deviceId, params) {
|
|
484
704
|
return this.callChain(connectId, deviceId, "evm", "evmSignTransaction", params);
|
|
485
705
|
}
|
|
@@ -495,16 +715,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
495
715
|
btcGetAddress(connectId, deviceId, params) {
|
|
496
716
|
return this.callChain(connectId, deviceId, "btc", "btcGetAddress", params);
|
|
497
717
|
}
|
|
498
|
-
btcGetAddresses(connectId, deviceId, params, onProgress) {
|
|
499
|
-
return this.callChainBatch(
|
|
500
|
-
connectId,
|
|
501
|
-
deviceId,
|
|
502
|
-
"btc",
|
|
503
|
-
"btcGetAddress",
|
|
504
|
-
params,
|
|
505
|
-
onProgress
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
718
|
btcGetPublicKey(connectId, deviceId, params) {
|
|
509
719
|
return this.callChain(connectId, deviceId, "btc", "btcGetPublicKey", params);
|
|
510
720
|
}
|
|
@@ -532,16 +742,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
532
742
|
solGetAddress(connectId, deviceId, params) {
|
|
533
743
|
return this.callChain(connectId, deviceId, "sol", "solGetAddress", params);
|
|
534
744
|
}
|
|
535
|
-
solGetAddresses(connectId, deviceId, params, onProgress) {
|
|
536
|
-
return this.callChainBatch(
|
|
537
|
-
connectId,
|
|
538
|
-
deviceId,
|
|
539
|
-
"sol",
|
|
540
|
-
"solGetAddress",
|
|
541
|
-
params,
|
|
542
|
-
onProgress
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
745
|
solSignTransaction(connectId, deviceId, params) {
|
|
546
746
|
return this.callChain(connectId, deviceId, "sol", "solSignTransaction", params);
|
|
547
747
|
}
|
|
@@ -552,38 +752,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
552
752
|
// TRON chain methods
|
|
553
753
|
// ---------------------------------------------------------------------------
|
|
554
754
|
tronGetAddress(connectId, deviceId, params) {
|
|
555
|
-
return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params
|
|
556
|
-
}
|
|
557
|
-
tronGetAddresses(connectId, deviceId, params, onProgress) {
|
|
558
|
-
return this.callChainBatch(
|
|
559
|
-
connectId,
|
|
560
|
-
deviceId,
|
|
561
|
-
"tron",
|
|
562
|
-
"tronGetAddress",
|
|
563
|
-
params,
|
|
564
|
-
onProgress,
|
|
565
|
-
true
|
|
566
|
-
);
|
|
755
|
+
return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params);
|
|
567
756
|
}
|
|
568
757
|
tronSignTransaction(connectId, deviceId, params) {
|
|
569
|
-
return this.callChain(
|
|
570
|
-
connectId,
|
|
571
|
-
deviceId,
|
|
572
|
-
"tron",
|
|
573
|
-
"tronSignTransaction",
|
|
574
|
-
params,
|
|
575
|
-
true
|
|
576
|
-
);
|
|
758
|
+
return this.callChain(connectId, deviceId, "tron", "tronSignTransaction", params);
|
|
577
759
|
}
|
|
578
760
|
tronSignMessage(connectId, deviceId, params) {
|
|
579
|
-
return this.callChain(
|
|
580
|
-
connectId,
|
|
581
|
-
deviceId,
|
|
582
|
-
"tron",
|
|
583
|
-
"tronSignMessage",
|
|
584
|
-
params,
|
|
585
|
-
true
|
|
586
|
-
);
|
|
761
|
+
return this.callChain(connectId, deviceId, "tron", "tronSignMessage", params);
|
|
587
762
|
}
|
|
588
763
|
on(event, listener) {
|
|
589
764
|
this.emitter.on(event, listener);
|
|
@@ -592,30 +767,41 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
592
767
|
this.emitter.off(event, listener);
|
|
593
768
|
}
|
|
594
769
|
cancel(connectId) {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
770
|
+
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
771
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
|
|
772
|
+
_tag: ERROR_TAG.UserAborted
|
|
773
|
+
});
|
|
774
|
+
this._lastCancelReason = userAbortReason;
|
|
775
|
+
setTimeout(() => {
|
|
776
|
+
if (this._lastCancelReason === userAbortReason) {
|
|
777
|
+
this._lastCancelReason = void 0;
|
|
778
|
+
}
|
|
779
|
+
}, 2e3);
|
|
780
|
+
this._uiRegistry.cancel();
|
|
781
|
+
if (connectId) {
|
|
782
|
+
this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
|
|
783
|
+
} else {
|
|
784
|
+
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
785
|
+
}
|
|
786
|
+
if (connectId) {
|
|
787
|
+
const sessionId = this._sessions.get(connectId) ?? connectId;
|
|
788
|
+
void this.connector.cancel(sessionId);
|
|
789
|
+
} else {
|
|
790
|
+
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
791
|
+
}
|
|
792
|
+
if (this._connectingPromise) {
|
|
793
|
+
this._doConnectAbortController?.abort(userAbortReason);
|
|
794
|
+
}
|
|
598
795
|
}
|
|
599
796
|
// ---------------------------------------------------------------------------
|
|
600
797
|
// Chain fingerprint
|
|
601
798
|
// ---------------------------------------------------------------------------
|
|
602
799
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
603
|
-
debugLog(
|
|
604
|
-
"[LedgerAdapter] getChainFingerprint called, chain:",
|
|
605
|
-
chain,
|
|
606
|
-
"connectId:",
|
|
607
|
-
connectId || "(empty)",
|
|
608
|
-
"sessions:",
|
|
609
|
-
this._sessions.size
|
|
610
|
-
);
|
|
611
|
-
await this._ensureDevicePermission(connectId, deviceId);
|
|
612
|
-
debugLog("[LedgerAdapter] getChainFingerprint permission ok, computing fingerprint");
|
|
613
800
|
try {
|
|
614
801
|
const fingerprint = await this._computeChainFingerprint(
|
|
615
802
|
chain,
|
|
616
|
-
(method, params) => this.connectorCall(connectId, method, params)
|
|
803
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
617
804
|
);
|
|
618
|
-
debugLog("[LedgerAdapter] getChainFingerprint result:", fingerprint?.substring(0, 20));
|
|
619
805
|
return (0, import_hwk_adapter_core2.success)(fingerprint);
|
|
620
806
|
} catch (err) {
|
|
621
807
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
@@ -686,102 +872,252 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
686
872
|
if (signal?.aborted) {
|
|
687
873
|
_LedgerAdapter._throwIfAborted(signal);
|
|
688
874
|
}
|
|
875
|
+
const waitPromise = this._uiRegistry.wait(
|
|
876
|
+
import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
877
|
+
);
|
|
689
878
|
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
690
879
|
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
691
880
|
payload: {
|
|
881
|
+
vendor: "ledger",
|
|
882
|
+
reason: "device-not-found",
|
|
692
883
|
message: "Please connect and unlock your Ledger device"
|
|
693
884
|
}
|
|
694
885
|
});
|
|
695
|
-
const waitPromise = this._uiRegistry.wait(
|
|
696
|
-
import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
697
|
-
);
|
|
698
886
|
let payload;
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
887
|
+
try {
|
|
888
|
+
if (signal) {
|
|
889
|
+
const onAbort = () => {
|
|
890
|
+
this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT);
|
|
891
|
+
};
|
|
892
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
893
|
+
try {
|
|
894
|
+
payload = await waitPromise;
|
|
895
|
+
} finally {
|
|
896
|
+
signal.removeEventListener("abort", onAbort);
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
705
899
|
payload = await waitPromise;
|
|
706
|
-
} finally {
|
|
707
|
-
signal.removeEventListener("abort", onAbort);
|
|
708
900
|
}
|
|
709
|
-
}
|
|
710
|
-
|
|
901
|
+
} catch (err) {
|
|
902
|
+
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
|
|
903
|
+
type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
|
|
904
|
+
payload: {}
|
|
905
|
+
});
|
|
906
|
+
throw Object.assign(new Error("User cancelled Ledger connection"), {
|
|
907
|
+
_tag: ERROR_TAG.UserAborted,
|
|
908
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
|
|
909
|
+
cause: err
|
|
910
|
+
});
|
|
711
911
|
}
|
|
712
912
|
if (!payload?.confirmed) {
|
|
713
913
|
throw Object.assign(new Error("User cancelled Ledger connection"), {
|
|
714
|
-
_tag:
|
|
914
|
+
_tag: ERROR_TAG.UserAborted,
|
|
915
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
|
|
715
916
|
});
|
|
716
917
|
}
|
|
918
|
+
await new Promise((resolve) => {
|
|
919
|
+
setTimeout(resolve, 800);
|
|
920
|
+
});
|
|
717
921
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
922
|
+
/**
|
|
923
|
+
* Decide whether a BTC pubkey call needs `showOnDevice=true` because of
|
|
924
|
+
* the BTC App's account-index policy, asking the user once per session.
|
|
925
|
+
*
|
|
926
|
+
* Returns the params to pass through (with `showOnDevice` possibly
|
|
927
|
+
* promoted), or `null` if the user declined.
|
|
928
|
+
*/
|
|
929
|
+
async _gateBtcHighIndex(params) {
|
|
930
|
+
const accountIndex = btcAccountIndexFromPath(params.path);
|
|
931
|
+
if (params.showOnDevice) return params;
|
|
932
|
+
if (accountIndex === null || accountIndex < BTC_HIGH_INDEX_THRESHOLD) {
|
|
933
|
+
return params;
|
|
725
934
|
}
|
|
726
|
-
if (this.
|
|
727
|
-
return
|
|
935
|
+
if (this._btcHighIndexConfirmedThisSession) {
|
|
936
|
+
return { ...params, showOnDevice: true };
|
|
728
937
|
}
|
|
729
|
-
|
|
938
|
+
const confirmed = await this._waitForBtcHighIndexConfirm(params.path, accountIndex);
|
|
939
|
+
if (!confirmed) return null;
|
|
940
|
+
this._btcHighIndexConfirmedThisSession = true;
|
|
941
|
+
return { ...params, showOnDevice: true };
|
|
942
|
+
}
|
|
943
|
+
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
944
|
+
const waitPromise = this._uiRegistry.wait(
|
|
945
|
+
import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
946
|
+
);
|
|
947
|
+
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
948
|
+
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
949
|
+
payload: {
|
|
950
|
+
vendor: "ledger",
|
|
951
|
+
path,
|
|
952
|
+
accountIndex
|
|
953
|
+
}
|
|
954
|
+
});
|
|
730
955
|
try {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
956
|
+
const payload = await waitPromise;
|
|
957
|
+
return !!payload?.confirmed;
|
|
958
|
+
} catch (err) {
|
|
959
|
+
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
|
|
960
|
+
type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
|
|
961
|
+
payload: {}
|
|
962
|
+
});
|
|
963
|
+
return false;
|
|
734
964
|
}
|
|
735
965
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
966
|
+
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
967
|
+
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
968
|
+
// doesn't kill caller B's await.
|
|
969
|
+
async ensureConnected(connectId, signal) {
|
|
970
|
+
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
971
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
972
|
+
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
973
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
977
|
+
if (!connectId && this._sessions.size > 0) {
|
|
978
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
|
|
979
|
+
throw Object.assign(
|
|
980
|
+
new Error(
|
|
981
|
+
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
982
|
+
),
|
|
983
|
+
{ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceOneDeviceOnly }
|
|
984
|
+
);
|
|
744
985
|
}
|
|
986
|
+
return this._sessions.keys().next().value;
|
|
745
987
|
}
|
|
746
|
-
|
|
747
|
-
new
|
|
748
|
-
|
|
749
|
-
)
|
|
750
|
-
|
|
751
|
-
|
|
988
|
+
if (!this._connectingPromise) {
|
|
989
|
+
this._doConnectAbortController = new AbortController();
|
|
990
|
+
const innerSignal = this._doConnectAbortController.signal;
|
|
991
|
+
this._connectingPromise = (async () => {
|
|
992
|
+
try {
|
|
993
|
+
return await this._doConnect(innerSignal, connectId);
|
|
994
|
+
} finally {
|
|
995
|
+
this._connectingPromise = null;
|
|
996
|
+
this._doConnectAbortController = null;
|
|
997
|
+
}
|
|
998
|
+
})();
|
|
999
|
+
}
|
|
1000
|
+
return this._abortable(signal, this._connectingPromise);
|
|
752
1001
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1002
|
+
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1003
|
+
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1004
|
+
// throw DeviceNotFound so the user is kicked out of the loop.
|
|
1005
|
+
async _doConnect(internalSignal, targetConnectId) {
|
|
1006
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
|
|
1007
|
+
try {
|
|
1008
|
+
return await this._connectDeviceOrThrow(targetConnectId);
|
|
1009
|
+
} catch (err) {
|
|
1010
|
+
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
1011
|
+
throw err;
|
|
1012
|
+
}
|
|
1013
|
+
this._discoveredDevices.delete(targetConnectId);
|
|
1014
|
+
if (isDeviceDisconnectedError(err)) {
|
|
1015
|
+
try {
|
|
1016
|
+
this.connector.reset?.();
|
|
1017
|
+
} catch {
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
let confirms = 0;
|
|
1023
|
+
while (!internalSignal.aborted) {
|
|
1024
|
+
this.emitter.emit("ui-event", {
|
|
1025
|
+
type: import_hwk_adapter_core2.EConnectorInteraction.Searching,
|
|
1026
|
+
payload: { sessionId: "" }
|
|
759
1027
|
});
|
|
1028
|
+
let devices = await this.searchDevices();
|
|
1029
|
+
if (devices.length === 0) {
|
|
1030
|
+
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1031
|
+
await new Promise((resolve) => {
|
|
1032
|
+
setTimeout(resolve, 1e3);
|
|
1033
|
+
});
|
|
1034
|
+
devices = await this.searchDevices();
|
|
1035
|
+
if (devices.length > 0) break;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (devices.length > 0) {
|
|
1039
|
+
try {
|
|
1040
|
+
return await this._connectFirstOrSelect(devices, targetConnectId);
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
1043
|
+
throw err;
|
|
1044
|
+
}
|
|
1045
|
+
this._discoveredDevices.clear();
|
|
1046
|
+
if (isDeviceDisconnectedError(err)) {
|
|
1047
|
+
try {
|
|
1048
|
+
this.connector.reset?.();
|
|
1049
|
+
} catch {
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (confirms >= _LedgerAdapter.MAX_DOCONNECT_CONFIRMS) {
|
|
1055
|
+
throw Object.assign(
|
|
1056
|
+
new Error(
|
|
1057
|
+
"Device not connected after multiple attempts. Please ensure your Ledger is awake, unlocked, and in range, then try again."
|
|
1058
|
+
),
|
|
1059
|
+
{ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound }
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
await this._waitForDeviceConnect(internalSignal);
|
|
1063
|
+
confirms += 1;
|
|
760
1064
|
}
|
|
761
|
-
|
|
1065
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1066
|
+
throw new Error("_doConnect aborted");
|
|
762
1067
|
}
|
|
763
|
-
async
|
|
764
|
-
if (!this.
|
|
1068
|
+
async _connectFirstOrSelect(devices, targetConnectId) {
|
|
1069
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length > 1) {
|
|
765
1070
|
debugLog(
|
|
766
|
-
`[DMK] Multiple Ledger devices found (${devices.length});
|
|
1071
|
+
`[DMK] Multiple Ledger USB devices found (${devices.length}); refusing to auto-select by ephemeral connectId.`
|
|
767
1072
|
);
|
|
768
|
-
|
|
1073
|
+
throw createMultipleUsbLedgerDevicesError();
|
|
769
1074
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
});
|
|
774
|
-
const response = await this._uiRegistry.wait(
|
|
775
|
-
import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE
|
|
776
|
-
);
|
|
777
|
-
const chosen = devices.find((d) => d.connectId === response?.sdkConnectId);
|
|
778
|
-
if (!chosen) {
|
|
779
|
-
throw Object.assign(
|
|
780
|
-
new Error(`Selected sdkConnectId '${response?.sdkConnectId}' not in discovered list`),
|
|
781
|
-
{ _tag: "DeviceNotRecognizedError" }
|
|
1075
|
+
if (targetConnectId) {
|
|
1076
|
+
const target = devices.find(
|
|
1077
|
+
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
782
1078
|
);
|
|
1079
|
+
if (target) {
|
|
1080
|
+
return this._connectDeviceOrThrow(target.connectId);
|
|
1081
|
+
}
|
|
1082
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) {
|
|
1083
|
+
debugLog(
|
|
1084
|
+
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)`
|
|
1085
|
+
);
|
|
1086
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
1087
|
+
}
|
|
1088
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
1089
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
1090
|
+
});
|
|
1091
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
1092
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
1093
|
+
}
|
|
1094
|
+
throw err;
|
|
783
1095
|
}
|
|
784
|
-
|
|
1096
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
1097
|
+
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1098
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
if (devices.length !== 1) {
|
|
1102
|
+
throw Object.assign(new Error("Ledger device not found."), {
|
|
1103
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
1107
|
+
}
|
|
1108
|
+
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1109
|
+
const result = await this.connectDevice(chosenConnectId);
|
|
1110
|
+
if (!result.success) {
|
|
1111
|
+
const payload = result.payload;
|
|
1112
|
+
const rethrow = Object.assign(new Error(payload.error), {
|
|
1113
|
+
code: payload.code
|
|
1114
|
+
});
|
|
1115
|
+
if (payload._tag !== void 0) {
|
|
1116
|
+
rethrow._tag = payload._tag;
|
|
1117
|
+
}
|
|
1118
|
+
throw rethrow;
|
|
1119
|
+
}
|
|
1120
|
+
return chosenConnectId;
|
|
785
1121
|
}
|
|
786
1122
|
/**
|
|
787
1123
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -791,30 +1127,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
791
1127
|
* 3. Calls connector.call()
|
|
792
1128
|
* 4. On disconnect error: clears stale session, re-connects, retries once
|
|
793
1129
|
*/
|
|
794
|
-
async connectorCall(connectId, method, params, fingerprint) {
|
|
1130
|
+
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId) {
|
|
795
1131
|
debugLog("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
|
|
796
1132
|
const queueKey = connectId || "__ledger_default__";
|
|
797
|
-
const interruptibility = _LedgerAdapter._getInterruptibility(method);
|
|
798
1133
|
return this._jobQueue.enqueue(
|
|
799
1134
|
queueKey,
|
|
800
|
-
async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint),
|
|
801
|
-
{
|
|
1135
|
+
async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId),
|
|
1136
|
+
{
|
|
1137
|
+
label: method,
|
|
1138
|
+
rejectIfBusy: true,
|
|
1139
|
+
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
1140
|
+
}
|
|
802
1141
|
);
|
|
803
1142
|
}
|
|
804
1143
|
/**
|
|
805
|
-
* Race a promise against an abort signal.
|
|
806
|
-
* signal
|
|
807
|
-
* actually be cancelled on Ledger DMK, but the caller gets the abort immediately.
|
|
1144
|
+
* Race a promise against an abort signal. On abort, rejects with
|
|
1145
|
+
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
808
1146
|
*/
|
|
809
|
-
|
|
1147
|
+
_abortable(signal, promise) {
|
|
1148
|
+
const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
810
1149
|
if (signal.aborted) {
|
|
811
|
-
return Promise.reject(
|
|
812
|
-
signal.reason ?? new Error("Aborted")
|
|
813
|
-
);
|
|
1150
|
+
return Promise.reject(getAbortReason());
|
|
814
1151
|
}
|
|
815
1152
|
return new Promise((resolve, reject) => {
|
|
816
1153
|
const onAbort = () => {
|
|
817
|
-
reject(
|
|
1154
|
+
reject(getAbortReason());
|
|
818
1155
|
};
|
|
819
1156
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
820
1157
|
promise.then(
|
|
@@ -836,31 +1173,195 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
836
1173
|
}
|
|
837
1174
|
}
|
|
838
1175
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
839
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint) {
|
|
1176
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId) {
|
|
840
1177
|
_LedgerAdapter._throwIfAborted(signal);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1178
|
+
await this._ensureDevicePermission(
|
|
1179
|
+
connectId,
|
|
1180
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
1181
|
+
signal
|
|
844
1182
|
);
|
|
845
1183
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1184
|
+
let effectiveParams = params;
|
|
1185
|
+
if (method === "btcGetPublicKey") {
|
|
1186
|
+
const gatedParams = await this._gateBtcHighIndex(params);
|
|
1187
|
+
if (gatedParams === null) {
|
|
1188
|
+
throw Object.assign(new Error("User cancelled BTC high-index confirmation"), {
|
|
1189
|
+
_tag: ERROR_TAG.UserAborted,
|
|
1190
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
effectiveParams = gatedParams;
|
|
1194
|
+
}
|
|
1195
|
+
const resolvedConnectId = await this.ensureConnected(connectId, signal);
|
|
846
1196
|
const sessionId = this._sessions.get(resolvedConnectId);
|
|
847
|
-
debugLog(
|
|
848
|
-
"[LedgerAdapter] connectorCall resolved:",
|
|
849
|
-
method,
|
|
850
|
-
"resolvedConnectId:",
|
|
851
|
-
resolvedConnectId,
|
|
852
|
-
"sessionId:",
|
|
853
|
-
sessionId
|
|
854
|
-
);
|
|
855
1197
|
if (!sessionId) {
|
|
856
1198
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
857
|
-
_tag:
|
|
1199
|
+
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
858
1200
|
});
|
|
859
1201
|
}
|
|
1202
|
+
try {
|
|
1203
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1204
|
+
const fp = await this._abortable(
|
|
1205
|
+
signal,
|
|
1206
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1207
|
+
sessionId,
|
|
1208
|
+
fingerprint.deviceId,
|
|
1209
|
+
fingerprint.chain
|
|
1210
|
+
)
|
|
1211
|
+
);
|
|
1212
|
+
if (!fp.success) {
|
|
1213
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1214
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return await this._abortable(signal, this.connector.call(sessionId, method, effectiveParams));
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
if (signal.aborted) throw err;
|
|
1221
|
+
const errObj = err;
|
|
1222
|
+
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1223
|
+
message: errObj?.message,
|
|
1224
|
+
_tag: errObj?._tag,
|
|
1225
|
+
errorCode: errObj?.errorCode,
|
|
1226
|
+
statusCode: errObj?.statusCode,
|
|
1227
|
+
isDisconnected: isDeviceDisconnectedError(err),
|
|
1228
|
+
isLocked: isDeviceLockedError(err),
|
|
1229
|
+
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1230
|
+
isStuckApp: isStuckAppStateError(err)
|
|
1231
|
+
});
|
|
1232
|
+
if (isStuckAppStateError(err)) {
|
|
1233
|
+
try {
|
|
1234
|
+
this._sessions.delete(resolvedConnectId);
|
|
1235
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1236
|
+
this.connector.reset?.();
|
|
1237
|
+
} catch {
|
|
1238
|
+
}
|
|
1239
|
+
debugLog(
|
|
1240
|
+
"[LedgerAdapter] stuck-app retry: method=",
|
|
1241
|
+
method,
|
|
1242
|
+
"delayMs=",
|
|
1243
|
+
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
|
|
1244
|
+
"_tag=",
|
|
1245
|
+
err?._tag
|
|
1246
|
+
);
|
|
1247
|
+
try {
|
|
1248
|
+
const retryResult = await this._retryAfterStuckApp(
|
|
1249
|
+
resolvedConnectId,
|
|
1250
|
+
method,
|
|
1251
|
+
effectiveParams,
|
|
1252
|
+
signal,
|
|
1253
|
+
err,
|
|
1254
|
+
fingerprint
|
|
1255
|
+
);
|
|
1256
|
+
debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
|
|
1257
|
+
return retryResult;
|
|
1258
|
+
} catch (retryErr) {
|
|
1259
|
+
if (signal.aborted) throw retryErr;
|
|
1260
|
+
if (isStuckAppStateError(retryErr)) {
|
|
1261
|
+
debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
|
|
1262
|
+
throw err;
|
|
1263
|
+
}
|
|
1264
|
+
debugLog(
|
|
1265
|
+
"[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
|
|
1266
|
+
method,
|
|
1267
|
+
"retryErrTag=",
|
|
1268
|
+
retryErr?._tag
|
|
1269
|
+
);
|
|
1270
|
+
throw retryErr;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
|
|
1274
|
+
let lastErr = err;
|
|
1275
|
+
for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
|
|
1276
|
+
if (signal.aborted) throw lastErr;
|
|
1277
|
+
try {
|
|
1278
|
+
this._sessions.delete(resolvedConnectId);
|
|
1279
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1280
|
+
if (isDeviceDisconnectedError(lastErr)) {
|
|
1281
|
+
try {
|
|
1282
|
+
this.connector.reset?.();
|
|
1283
|
+
} catch {
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const reConnectId = await this.ensureConnected(resolvedConnectId, signal);
|
|
1287
|
+
const reSessionId = this._sessions.get(reConnectId);
|
|
1288
|
+
if (!reSessionId) throw lastErr;
|
|
1289
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1290
|
+
const fp = await this._abortable(
|
|
1291
|
+
signal,
|
|
1292
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1293
|
+
reSessionId,
|
|
1294
|
+
fingerprint.deviceId,
|
|
1295
|
+
fingerprint.chain
|
|
1296
|
+
)
|
|
1297
|
+
);
|
|
1298
|
+
if (!fp.success) {
|
|
1299
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1300
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return await this._abortable(
|
|
1305
|
+
signal,
|
|
1306
|
+
this.connector.call(reSessionId, method, effectiveParams)
|
|
1307
|
+
);
|
|
1308
|
+
} catch (retryErr) {
|
|
1309
|
+
if (signal.aborted) throw retryErr;
|
|
1310
|
+
lastErr = retryErr;
|
|
1311
|
+
const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
|
|
1312
|
+
if (!canRetry) {
|
|
1313
|
+
throw retryErr;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
throw lastErr;
|
|
1318
|
+
}
|
|
1319
|
+
if (isTimeoutError(err)) {
|
|
1320
|
+
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
1321
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1322
|
+
return this._retryWithFreshConnection(
|
|
1323
|
+
resolvedConnectId,
|
|
1324
|
+
method,
|
|
1325
|
+
effectiveParams,
|
|
1326
|
+
signal,
|
|
1327
|
+
err,
|
|
1328
|
+
fingerprint
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
if (isConnectionLevelError(err)) {
|
|
1332
|
+
debugLog("[LedgerAdapter] connection-level fail-closed reset");
|
|
1333
|
+
this._sessions.delete(resolvedConnectId);
|
|
1334
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1335
|
+
this.connector.reset();
|
|
1336
|
+
const codeNum = err?.code;
|
|
1337
|
+
throw Object.assign(err, {
|
|
1338
|
+
code: codeNum ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceDisconnected
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
throw err;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Stuck-app recovery: pause for the device's UI transition, then retry once.
|
|
1346
|
+
*
|
|
1347
|
+
* Caller has already cleared the session + reset connector. We wait so Stax
|
|
1348
|
+
* finishes its post-CloseApp animation, then go through ensureConnected +
|
|
1349
|
+
* fingerprint check + call exactly once. Caller decides what to do on a
|
|
1350
|
+
* second stuck-app hit.
|
|
1351
|
+
*/
|
|
1352
|
+
async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
|
|
1353
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
1354
|
+
const retryConnectId = await this.ensureConnected(resolvedConnectId, signal);
|
|
1355
|
+
const retrySessionId = this._sessions.get(retryConnectId);
|
|
1356
|
+
if (!retrySessionId) throw originalErr;
|
|
860
1357
|
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
861
|
-
const fp = await
|
|
1358
|
+
const fp = await this._abortable(
|
|
862
1359
|
signal,
|
|
863
|
-
this._verifyDeviceFingerprintWithSession(
|
|
1360
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1361
|
+
retrySessionId,
|
|
1362
|
+
fingerprint.deviceId,
|
|
1363
|
+
fingerprint.chain
|
|
1364
|
+
)
|
|
864
1365
|
);
|
|
865
1366
|
if (!fp.success) {
|
|
866
1367
|
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
@@ -868,48 +1369,97 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
868
1369
|
});
|
|
869
1370
|
}
|
|
870
1371
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
_tag: errObj?._tag,
|
|
879
|
-
errorCode: errObj?.errorCode,
|
|
880
|
-
statusCode: errObj?.statusCode,
|
|
881
|
-
isDisconnected: isDeviceDisconnectedError(err),
|
|
882
|
-
isLocked: isDeviceLockedError(err)
|
|
883
|
-
});
|
|
884
|
-
if (isDeviceDisconnectedError(err)) {
|
|
885
|
-
debugLog("[LedgerAdapter] disconnected, retrying with fresh connection...");
|
|
886
|
-
this._discoveredDevices.clear();
|
|
887
|
-
return this._retryWithFreshConnection(resolvedConnectId, method, params, signal, err);
|
|
888
|
-
}
|
|
889
|
-
if (isDeviceLockedError(err)) {
|
|
890
|
-
await this._waitForDeviceConnect(signal);
|
|
891
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
892
|
-
return _LedgerAdapter._abortable(signal, this.connector.call(sessionId, method, params));
|
|
893
|
-
}
|
|
894
|
-
if (isTimeoutError(err)) {
|
|
895
|
-
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
896
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
897
|
-
return this._retryWithFreshConnection(resolvedConnectId, method, params, signal, err);
|
|
1372
|
+
return this._abortable(signal, this.connector.call(retrySessionId, method, params));
|
|
1373
|
+
}
|
|
1374
|
+
_sleepAbortable(ms, signal) {
|
|
1375
|
+
return new Promise((resolve, reject) => {
|
|
1376
|
+
if (signal.aborted) {
|
|
1377
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
1378
|
+
return;
|
|
898
1379
|
}
|
|
899
|
-
|
|
900
|
-
|
|
1380
|
+
const timer = setTimeout(() => {
|
|
1381
|
+
signal.removeEventListener("abort", onAbort);
|
|
1382
|
+
resolve();
|
|
1383
|
+
}, ms);
|
|
1384
|
+
const onAbort = () => {
|
|
1385
|
+
clearTimeout(timer);
|
|
1386
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
1387
|
+
};
|
|
1388
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1389
|
+
});
|
|
901
1390
|
}
|
|
902
|
-
/**
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1391
|
+
/**
|
|
1392
|
+
* Clear stale session, reconnect, and retry the call.
|
|
1393
|
+
*
|
|
1394
|
+
* Timeout recovery starts with a full connector reset. After an APDU
|
|
1395
|
+
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
1396
|
+
* on the same DMK can poison the next chain switch.
|
|
1397
|
+
*/
|
|
1398
|
+
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
1399
|
+
this.connector.reset();
|
|
1400
|
+
this._sessions.clear();
|
|
1401
|
+
this._discoveredDevices.clear();
|
|
1402
|
+
this._connectingPromise = null;
|
|
1403
|
+
const retryConnectId = await this.ensureConnected(targetConnectId, signal);
|
|
908
1404
|
const retrySessionId = this._sessions.get(retryConnectId);
|
|
909
1405
|
if (!retrySessionId) {
|
|
910
1406
|
throw originalErr;
|
|
911
1407
|
}
|
|
912
|
-
|
|
1408
|
+
try {
|
|
1409
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1410
|
+
const fp = await this._abortable(
|
|
1411
|
+
signal,
|
|
1412
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1413
|
+
retrySessionId,
|
|
1414
|
+
fingerprint.deviceId,
|
|
1415
|
+
fingerprint.chain
|
|
1416
|
+
)
|
|
1417
|
+
);
|
|
1418
|
+
if (!fp.success) {
|
|
1419
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1420
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return await this._abortable(signal, this.connector.call(retrySessionId, method, params));
|
|
1425
|
+
} catch (retryErr) {
|
|
1426
|
+
if (signal.aborted) throw retryErr;
|
|
1427
|
+
this.connector.reset();
|
|
1428
|
+
this._sessions.clear();
|
|
1429
|
+
this._discoveredDevices.clear();
|
|
1430
|
+
this._connectingPromise = null;
|
|
1431
|
+
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
1432
|
+
throw retryErr;
|
|
1433
|
+
}
|
|
1434
|
+
debugLog(
|
|
1435
|
+
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
1436
|
+
);
|
|
1437
|
+
this.connector.reset();
|
|
1438
|
+
this._sessions.clear();
|
|
1439
|
+
this._discoveredDevices.clear();
|
|
1440
|
+
this._connectingPromise = null;
|
|
1441
|
+
const finalConnectId = await this.ensureConnected(targetConnectId, signal);
|
|
1442
|
+
const finalSessionId = this._sessions.get(finalConnectId);
|
|
1443
|
+
if (!finalSessionId) {
|
|
1444
|
+
throw originalErr;
|
|
1445
|
+
}
|
|
1446
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1447
|
+
const fp = await this._abortable(
|
|
1448
|
+
signal,
|
|
1449
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1450
|
+
finalSessionId,
|
|
1451
|
+
fingerprint.deviceId,
|
|
1452
|
+
fingerprint.chain
|
|
1453
|
+
)
|
|
1454
|
+
);
|
|
1455
|
+
if (!fp.success) {
|
|
1456
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1457
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
return this._abortable(signal, this.connector.call(finalSessionId, method, params));
|
|
1462
|
+
}
|
|
913
1463
|
}
|
|
914
1464
|
/**
|
|
915
1465
|
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
@@ -923,7 +1473,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
923
1473
|
* - No connectId (searchDevices): environment-level permission
|
|
924
1474
|
* - With connectId (business methods): device-level permission
|
|
925
1475
|
*/
|
|
926
|
-
async _ensureDevicePermission(connectId, deviceId) {
|
|
1476
|
+
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
1477
|
+
if (signal?.aborted) {
|
|
1478
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1479
|
+
}
|
|
927
1480
|
const transportType = this.activeTransport ?? "hid";
|
|
928
1481
|
const waitPromise = this._uiRegistry.wait(
|
|
929
1482
|
import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
@@ -933,10 +1486,37 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
933
1486
|
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
934
1487
|
payload: { transportType, connectId, deviceId }
|
|
935
1488
|
});
|
|
936
|
-
|
|
1489
|
+
let response;
|
|
1490
|
+
const onAbort = () => {
|
|
1491
|
+
this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION);
|
|
1492
|
+
};
|
|
1493
|
+
try {
|
|
1494
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1495
|
+
response = await waitPromise;
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
if (signal?.aborted) {
|
|
1498
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1499
|
+
}
|
|
1500
|
+
if (err?._tag === import_hwk_adapter_core2.UI_REQUEST_PREEMPTED_TAG) {
|
|
1501
|
+
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
|
|
1502
|
+
type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
|
|
1503
|
+
payload: {}
|
|
1504
|
+
});
|
|
1505
|
+
throw Object.assign(new Error("Device permission request superseded"), {
|
|
1506
|
+
_tag: ERROR_TAG.UserAborted,
|
|
1507
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
|
|
1508
|
+
cause: err
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
throw err;
|
|
1512
|
+
} finally {
|
|
1513
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1514
|
+
}
|
|
1515
|
+
const { granted, reason, message } = response;
|
|
937
1516
|
if (!granted) {
|
|
938
|
-
throw Object.assign(new Error("Device permission denied"), {
|
|
939
|
-
code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied
|
|
1517
|
+
throw Object.assign(new Error(message ?? "Device permission denied"), {
|
|
1518
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied,
|
|
1519
|
+
reason
|
|
940
1520
|
});
|
|
941
1521
|
}
|
|
942
1522
|
}
|
|
@@ -946,12 +1526,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
946
1526
|
*/
|
|
947
1527
|
errorToFailure(err) {
|
|
948
1528
|
debugError("[LedgerAdapter] error:", err);
|
|
1529
|
+
const tag = err && typeof err === "object" ? err._tag : void 0;
|
|
949
1530
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
950
1531
|
const e = err;
|
|
951
|
-
|
|
1532
|
+
const params = e.code === import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : void 0;
|
|
1533
|
+
return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
|
|
952
1534
|
}
|
|
953
1535
|
const mapped = mapLedgerError(err);
|
|
954
|
-
return ledgerFailure(mapped.code, mapped.message, mapped.appName);
|
|
1536
|
+
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
955
1537
|
}
|
|
956
1538
|
registerEventListeners() {
|
|
957
1539
|
this.connector.on("device-connect", this.deviceConnectHandler);
|
|
@@ -967,32 +1549,38 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
967
1549
|
// Device info mapping
|
|
968
1550
|
// ---------------------------------------------------------------------------
|
|
969
1551
|
connectorDeviceToDeviceInfo(device) {
|
|
970
|
-
const isBle = device.connectId && /^[0-9A-Fa-f]{4}$/.test(device.connectId);
|
|
971
1552
|
return {
|
|
972
1553
|
vendor: "ledger",
|
|
973
1554
|
model: device.model ?? "unknown",
|
|
1555
|
+
modelName: device.modelName,
|
|
974
1556
|
firmwareVersion: "",
|
|
975
1557
|
deviceId: device.deviceId,
|
|
976
1558
|
connectId: device.connectId,
|
|
977
1559
|
label: device.name,
|
|
978
|
-
connectionType:
|
|
1560
|
+
connectionType: this.connector.connectionType,
|
|
1561
|
+
rssi: device.rssi,
|
|
1562
|
+
isConnectable: device.isConnectable,
|
|
1563
|
+
serialNumber: device.serialNumber,
|
|
979
1564
|
capabilities: device.capabilities
|
|
980
1565
|
};
|
|
981
1566
|
}
|
|
982
1567
|
};
|
|
983
|
-
//
|
|
984
|
-
//
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
_LedgerAdapter.
|
|
1568
|
+
// Layer 2 retry budget after connection-class error. Each round delegates
|
|
1569
|
+
// to Layer 1 (which owns the unlock prompt). Layer 2 never emits UI itself.
|
|
1570
|
+
_LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET = 3;
|
|
1571
|
+
// Stuck-app (APDU 0x6901) retry pause. Ledger Stax often returns 6901 when
|
|
1572
|
+
// OpenAppCommand lands mid-transition right after CloseApp; a short wait
|
|
1573
|
+
// lets the device finish its UI animation before we retry once.
|
|
1574
|
+
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS = 500;
|
|
1575
|
+
// Layer 1 confirm budget. After N failed Confirm cycles (user keeps clicking
|
|
1576
|
+
// Confirm but device never shows up / never unlocks), throw DeviceNotFound
|
|
1577
|
+
// instead of looping forever.
|
|
1578
|
+
_LedgerAdapter.MAX_DOCONNECT_CONFIRMS = 3;
|
|
994
1579
|
var LedgerAdapter = _LedgerAdapter;
|
|
995
1580
|
|
|
1581
|
+
// src/connector/LedgerConnectorBase.ts
|
|
1582
|
+
var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
|
|
1583
|
+
|
|
996
1584
|
// src/device/LedgerDeviceManager.ts
|
|
997
1585
|
var LedgerDeviceManager = class {
|
|
998
1586
|
constructor(dmk) {
|
|
@@ -1029,7 +1617,9 @@ var LedgerDeviceManager = class {
|
|
|
1029
1617
|
if (resolved) return;
|
|
1030
1618
|
resolved = true;
|
|
1031
1619
|
this._discovered.clear();
|
|
1032
|
-
debugLog(
|
|
1620
|
+
debugLog(
|
|
1621
|
+
`[DMK] enumerate count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
|
|
1622
|
+
);
|
|
1033
1623
|
for (const d of devices) {
|
|
1034
1624
|
this._discovered.set(d.id, d);
|
|
1035
1625
|
}
|
|
@@ -1039,8 +1629,10 @@ var LedgerDeviceManager = class {
|
|
|
1039
1629
|
devices.map((d) => ({
|
|
1040
1630
|
path: d.id,
|
|
1041
1631
|
type: d.deviceModel.model,
|
|
1632
|
+
modelName: d.deviceModel.name,
|
|
1042
1633
|
name: d.name,
|
|
1043
|
-
transport: d.transport
|
|
1634
|
+
transport: d.transport,
|
|
1635
|
+
rssi: d.rssi
|
|
1044
1636
|
}))
|
|
1045
1637
|
);
|
|
1046
1638
|
} else {
|
|
@@ -1061,8 +1653,10 @@ var LedgerDeviceManager = class {
|
|
|
1061
1653
|
devices.map((d) => ({
|
|
1062
1654
|
path: d.id,
|
|
1063
1655
|
type: d.deviceModel.model,
|
|
1656
|
+
modelName: d.deviceModel.name,
|
|
1064
1657
|
name: d.name,
|
|
1065
|
-
transport: d.transport
|
|
1658
|
+
transport: d.transport,
|
|
1659
|
+
rssi: d.rssi
|
|
1066
1660
|
}))
|
|
1067
1661
|
);
|
|
1068
1662
|
}
|
|
@@ -1086,8 +1680,10 @@ var LedgerDeviceManager = class {
|
|
|
1086
1680
|
descriptor: {
|
|
1087
1681
|
path: d.id,
|
|
1088
1682
|
type: d.deviceModel.model,
|
|
1683
|
+
modelName: d.deviceModel.name,
|
|
1089
1684
|
name: d.name,
|
|
1090
|
-
transport: d.transport
|
|
1685
|
+
transport: d.transport,
|
|
1686
|
+
rssi: d.rssi
|
|
1091
1687
|
}
|
|
1092
1688
|
});
|
|
1093
1689
|
}
|
|
@@ -1106,6 +1702,54 @@ var LedgerDeviceManager = class {
|
|
|
1106
1702
|
this._listenSub?.unsubscribe();
|
|
1107
1703
|
this._listenSub = null;
|
|
1108
1704
|
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Resolve to the latest snapshot of advertising devices observed within
|
|
1707
|
+
* `timeoutMs`. Rewrites `_discovered` so it tracks the live list.
|
|
1708
|
+
*
|
|
1709
|
+
* `listenToAvailableDevices` is a BehaviorSubject — it emits the cached
|
|
1710
|
+
* list on subscribe and *only* re-emits when the list changes. A stable
|
|
1711
|
+
* list of currently-advertising devices therefore produces only the
|
|
1712
|
+
* subscription frame; treating that frame as "fossil to be discarded"
|
|
1713
|
+
* caused devices to flicker in/out of the UI as searches alternated
|
|
1714
|
+
* between "saw a change frame" and "saw only the cached frame".
|
|
1715
|
+
*
|
|
1716
|
+
* The window still gives a chance for the active scan to update the list
|
|
1717
|
+
* (e.g. drop a peripheral that just stopped broadcasting), but if nothing
|
|
1718
|
+
* changes we trust the snapshot — DMK silence on a stable list means
|
|
1719
|
+
* "everything's still there".
|
|
1720
|
+
*/
|
|
1721
|
+
getLiveDevices(timeoutMs = 1500) {
|
|
1722
|
+
debugLog(`[DMK] getLiveDevices() called, timeoutMs=${timeoutMs}`);
|
|
1723
|
+
void this.requestDevice();
|
|
1724
|
+
return new Promise((resolve) => {
|
|
1725
|
+
let done = false;
|
|
1726
|
+
let lastSeen = [];
|
|
1727
|
+
let sub = null;
|
|
1728
|
+
const finish = (devices) => {
|
|
1729
|
+
if (done) return;
|
|
1730
|
+
done = true;
|
|
1731
|
+
sub?.unsubscribe();
|
|
1732
|
+
clearTimeout(timer);
|
|
1733
|
+
this._discovered.clear();
|
|
1734
|
+
for (const d of devices) this._discovered.set(d.id, d);
|
|
1735
|
+
debugLog(
|
|
1736
|
+
`[DMK] getLiveDevices() resolved count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
|
|
1737
|
+
);
|
|
1738
|
+
resolve(devices);
|
|
1739
|
+
};
|
|
1740
|
+
const timer = setTimeout(() => finish(lastSeen), timeoutMs);
|
|
1741
|
+
sub = this._dmk.listenToAvailableDevices({}).subscribe({
|
|
1742
|
+
next: (devices) => {
|
|
1743
|
+
lastSeen = devices;
|
|
1744
|
+
},
|
|
1745
|
+
error: (err) => {
|
|
1746
|
+
debugError("[DMK] getLiveDevices() error:", err);
|
|
1747
|
+
finish(lastSeen);
|
|
1748
|
+
},
|
|
1749
|
+
complete: () => finish(lastSeen)
|
|
1750
|
+
});
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1109
1753
|
requestDevice() {
|
|
1110
1754
|
if (this._discoverySub) {
|
|
1111
1755
|
return Promise.resolve();
|
|
@@ -1123,11 +1767,33 @@ var LedgerDeviceManager = class {
|
|
|
1123
1767
|
});
|
|
1124
1768
|
return Promise.resolve();
|
|
1125
1769
|
}
|
|
1770
|
+
/** Lookup a previously-discovered device's display name. */
|
|
1771
|
+
getDeviceName(deviceId) {
|
|
1772
|
+
return this._discovered.get(deviceId)?.name;
|
|
1773
|
+
}
|
|
1774
|
+
/** Lookup minimal model/signal info from a previously-discovered device. */
|
|
1775
|
+
getDiscoveredDeviceInfo(deviceId) {
|
|
1776
|
+
const d = this._discovered.get(deviceId);
|
|
1777
|
+
if (!d) return void 0;
|
|
1778
|
+
return {
|
|
1779
|
+
model: d.deviceModel?.model,
|
|
1780
|
+
modelName: d.deviceModel?.name,
|
|
1781
|
+
name: d.name,
|
|
1782
|
+
rssi: d.rssi
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
hasDiscoveredDevice(deviceId) {
|
|
1786
|
+
return this._discovered.has(deviceId);
|
|
1787
|
+
}
|
|
1126
1788
|
/** Connect to a previously discovered device. Returns sessionId. */
|
|
1127
1789
|
async connect(deviceId) {
|
|
1128
1790
|
const device = this._discovered.get(deviceId);
|
|
1129
1791
|
if (!device) {
|
|
1130
|
-
|
|
1792
|
+
const err = new Error(
|
|
1793
|
+
`Device "${deviceId}" not found in discovery cache. Call enumerate() or listen() first.`
|
|
1794
|
+
);
|
|
1795
|
+
err._tag = ERROR_TAG.DeviceNotInDiscoveryCache;
|
|
1796
|
+
throw err;
|
|
1131
1797
|
}
|
|
1132
1798
|
const sessionId = await this._dmk.connect({ device });
|
|
1133
1799
|
this._sessions.set(deviceId, sessionId);
|
|
@@ -1166,6 +1832,19 @@ var LedgerDeviceManager = class {
|
|
|
1166
1832
|
this._sessionToDevice.clear();
|
|
1167
1833
|
this._dmk.close();
|
|
1168
1834
|
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Tear down RxJS subscriptions and local state without closing the DMK.
|
|
1837
|
+
* For light-reset paths that want to drop session state but keep the
|
|
1838
|
+
* underlying transport alive for retry. Call this instead of orphaning
|
|
1839
|
+
* the manager — bare null assignment leaks _listenSub / _discoverySub.
|
|
1840
|
+
*/
|
|
1841
|
+
disposeKeepingDmk() {
|
|
1842
|
+
this.stopListening();
|
|
1843
|
+
this.stopDiscovery();
|
|
1844
|
+
this._discovered.clear();
|
|
1845
|
+
this._sessions.clear();
|
|
1846
|
+
this._sessionToDevice.clear();
|
|
1847
|
+
}
|
|
1169
1848
|
};
|
|
1170
1849
|
|
|
1171
1850
|
// src/signer/SignerManager.ts
|
|
@@ -1177,11 +1856,12 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
|
|
|
1177
1856
|
|
|
1178
1857
|
// src/signer/deviceActionToPromise.ts
|
|
1179
1858
|
var import_device_management_kit = require("@ledgerhq/device-management-kit");
|
|
1180
|
-
var
|
|
1181
|
-
function deviceActionToPromise(action, onInteraction, timeoutMs =
|
|
1859
|
+
var IDLE_WATCHDOG_MS = 65e3;
|
|
1860
|
+
function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller) {
|
|
1182
1861
|
return new Promise((resolve, reject) => {
|
|
1183
1862
|
let settled = false;
|
|
1184
1863
|
let lastStep;
|
|
1864
|
+
const observedSteps = [];
|
|
1185
1865
|
let sub;
|
|
1186
1866
|
let timer = null;
|
|
1187
1867
|
const cancelAction = () => {
|
|
@@ -1194,44 +1874,59 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1194
1874
|
} catch {
|
|
1195
1875
|
}
|
|
1196
1876
|
};
|
|
1197
|
-
const
|
|
1877
|
+
const armIdleWatchdog = () => {
|
|
1198
1878
|
if (timer) clearTimeout(timer);
|
|
1199
1879
|
if (timeoutMs > 0) {
|
|
1200
1880
|
timer = setTimeout(() => {
|
|
1201
1881
|
if (!settled) {
|
|
1202
1882
|
settled = true;
|
|
1203
1883
|
cancelAction();
|
|
1204
|
-
reject(
|
|
1884
|
+
reject(
|
|
1885
|
+
new Error(
|
|
1886
|
+
"Ledger transport unresponsive: no DMK state change within the watchdog window. The device may have lost connection."
|
|
1887
|
+
)
|
|
1888
|
+
);
|
|
1205
1889
|
}
|
|
1206
1890
|
}, timeoutMs);
|
|
1207
1891
|
}
|
|
1208
1892
|
};
|
|
1209
|
-
|
|
1893
|
+
armIdleWatchdog();
|
|
1210
1894
|
if (onRegisterCanceller) {
|
|
1211
|
-
onRegisterCanceller(() => {
|
|
1212
|
-
if (settled)
|
|
1895
|
+
onRegisterCanceller((reason) => {
|
|
1896
|
+
if (settled) {
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1213
1899
|
settled = true;
|
|
1214
1900
|
if (timer) clearTimeout(timer);
|
|
1215
1901
|
cancelAction();
|
|
1216
|
-
|
|
1902
|
+
const err = new Error(reason?.message ?? "Device action cancelled");
|
|
1903
|
+
if (reason?.code !== void 0) {
|
|
1904
|
+
err.code = reason.code;
|
|
1905
|
+
}
|
|
1906
|
+
if (reason?.tag) {
|
|
1907
|
+
Object.defineProperty(err, "_tag", {
|
|
1908
|
+
value: reason.tag,
|
|
1909
|
+
configurable: true,
|
|
1910
|
+
enumerable: false,
|
|
1911
|
+
writable: true
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
reject(err);
|
|
1217
1915
|
});
|
|
1218
1916
|
}
|
|
1219
|
-
debugLog("[DMK-Observable] subscribing to action.observable...");
|
|
1220
1917
|
sub = action.observable.subscribe({
|
|
1221
1918
|
next: (state) => {
|
|
1222
|
-
if (settled)
|
|
1223
|
-
|
|
1919
|
+
if (settled) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
armIdleWatchdog();
|
|
1224
1923
|
const step = state?.intermediateValue?.step;
|
|
1225
|
-
if (step)
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
hasOutput: state.status === import_device_management_kit.DeviceActionStatus.Completed,
|
|
1232
|
-
hasError: state.status === import_device_management_kit.DeviceActionStatus.Error
|
|
1233
|
-
})
|
|
1234
|
-
);
|
|
1924
|
+
if (step) {
|
|
1925
|
+
lastStep = step;
|
|
1926
|
+
if (observedSteps[observedSteps.length - 1] !== step) {
|
|
1927
|
+
observedSteps.push(step);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1235
1930
|
if (state.status === import_device_management_kit.DeviceActionStatus.Completed) {
|
|
1236
1931
|
settled = true;
|
|
1237
1932
|
if (timer) clearTimeout(timer);
|
|
@@ -1243,10 +1938,14 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1243
1938
|
if (timer) clearTimeout(timer);
|
|
1244
1939
|
onInteraction?.("interaction-complete");
|
|
1245
1940
|
sub?.unsubscribe();
|
|
1246
|
-
|
|
1941
|
+
rejectWithStepContext(state.error, lastStep, observedSteps, reject);
|
|
1247
1942
|
} else if (state.status === import_device_management_kit.DeviceActionStatus.Pending && onInteraction) {
|
|
1248
1943
|
const interaction = state.intermediateValue?.requiredUserInteraction;
|
|
1249
1944
|
if (interaction && interaction !== "none") {
|
|
1945
|
+
if (interaction === "unlock-device" && timer) {
|
|
1946
|
+
clearTimeout(timer);
|
|
1947
|
+
timer = null;
|
|
1948
|
+
}
|
|
1250
1949
|
onInteraction(String(interaction));
|
|
1251
1950
|
}
|
|
1252
1951
|
}
|
|
@@ -1256,7 +1955,7 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1256
1955
|
settled = true;
|
|
1257
1956
|
if (timer) clearTimeout(timer);
|
|
1258
1957
|
sub?.unsubscribe();
|
|
1259
|
-
|
|
1958
|
+
rejectWithStepContext(err, lastStep, observedSteps, reject);
|
|
1260
1959
|
}
|
|
1261
1960
|
},
|
|
1262
1961
|
complete: () => {
|
|
@@ -1269,15 +1968,25 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1269
1968
|
});
|
|
1270
1969
|
});
|
|
1271
1970
|
}
|
|
1272
|
-
function
|
|
1273
|
-
if (err && typeof err === "object" && lastStep) {
|
|
1971
|
+
function rejectWithStepContext(err, lastStep, observedSteps, reject) {
|
|
1972
|
+
if (err && typeof err === "object" && (lastStep || observedSteps.length > 0)) {
|
|
1274
1973
|
try {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1974
|
+
if (lastStep) {
|
|
1975
|
+
Object.defineProperty(err, "_lastStep", {
|
|
1976
|
+
value: lastStep,
|
|
1977
|
+
configurable: true,
|
|
1978
|
+
enumerable: false,
|
|
1979
|
+
writable: true
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
if (observedSteps.length > 0) {
|
|
1983
|
+
Object.defineProperty(err, "_deviceActionSteps", {
|
|
1984
|
+
value: [...observedSteps],
|
|
1985
|
+
configurable: true,
|
|
1986
|
+
enumerable: false,
|
|
1987
|
+
writable: true
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1281
1990
|
} catch {
|
|
1282
1991
|
}
|
|
1283
1992
|
}
|
|
@@ -1343,7 +2052,8 @@ var SignerManager = class _SignerManager {
|
|
|
1343
2052
|
async getOrCreate(sessionId) {
|
|
1344
2053
|
debugLog("[DMK] SignerManager.getOrCreate:", { sessionId });
|
|
1345
2054
|
const builder = await this._builderFn({ dmk: this._dmk, sessionId });
|
|
1346
|
-
|
|
2055
|
+
const builderWithContext = builder.withContextModule ? builder.withContextModule(_SignerManager._createContextModule()) : builder;
|
|
2056
|
+
return new SignerEth(builderWithContext.build());
|
|
1347
2057
|
}
|
|
1348
2058
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this
|
|
1349
2059
|
invalidate(_sessionId) {
|
|
@@ -1352,10 +2062,24 @@ var SignerManager = class _SignerManager {
|
|
|
1352
2062
|
clearAll() {
|
|
1353
2063
|
}
|
|
1354
2064
|
static _defaultBuilder() {
|
|
1355
|
-
return (args) =>
|
|
1356
|
-
|
|
1357
|
-
|
|
2065
|
+
return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
|
|
2066
|
+
}
|
|
2067
|
+
static _createContextModule() {
|
|
2068
|
+
const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
2069
|
+
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2070
|
+
}
|
|
2071
|
+
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
2072
|
+
const report = contextModule.report.bind(contextModule);
|
|
2073
|
+
contextModule.report = async (params) => {
|
|
2074
|
+
try {
|
|
2075
|
+
void report(params).catch((err) => {
|
|
2076
|
+
debugLog("[DMK] ContextModule.report failed:", err);
|
|
2077
|
+
});
|
|
2078
|
+
} catch (err) {
|
|
2079
|
+
debugLog("[DMK] ContextModule.report failed:", err);
|
|
2080
|
+
}
|
|
1358
2081
|
};
|
|
2082
|
+
return contextModule;
|
|
1359
2083
|
}
|
|
1360
2084
|
};
|
|
1361
2085
|
|
|
@@ -1465,6 +2189,7 @@ async function _getEthSigner(ctx, sessionId) {
|
|
|
1465
2189
|
const signerManager = await ctx.getSignerManager();
|
|
1466
2190
|
const signer = await signerManager.getOrCreate(sessionId);
|
|
1467
2191
|
signer.onInteraction = (interaction) => {
|
|
2192
|
+
debugLog("[LedgerConnector] evm.onInteraction:", interaction);
|
|
1468
2193
|
ctx.emit("ui-event", {
|
|
1469
2194
|
type: collapseSignerInteraction(interaction),
|
|
1470
2195
|
payload: { sessionId }
|
|
@@ -1740,6 +2465,7 @@ async function _createBtcSigner(ctx, sessionId) {
|
|
|
1740
2465
|
const sdkSigner = new SignerBtcBuilder({ dmk, sessionId }).build();
|
|
1741
2466
|
const signer = new SignerBtc(sdkSigner);
|
|
1742
2467
|
signer.onInteraction = (interaction) => {
|
|
2468
|
+
debugLog("[LedgerConnector] btc.onInteraction:", interaction);
|
|
1743
2469
|
ctx.emit("ui-event", {
|
|
1744
2470
|
type: collapseSignerInteraction(interaction),
|
|
1745
2471
|
payload: { sessionId }
|
|
@@ -1910,6 +2636,7 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
1910
2636
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
1911
2637
|
const signer = new SignerSol(sdkSigner);
|
|
1912
2638
|
signer.onInteraction = (interaction) => {
|
|
2639
|
+
debugLog("[LedgerConnector] sol.onInteraction:", interaction);
|
|
1913
2640
|
ctx.emit("ui-event", {
|
|
1914
2641
|
type: collapseSignerInteraction(interaction),
|
|
1915
2642
|
payload: { sessionId }
|
|
@@ -1922,14 +2649,15 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
1922
2649
|
}
|
|
1923
2650
|
|
|
1924
2651
|
// src/connector/chains/tron.ts
|
|
1925
|
-
var
|
|
2652
|
+
var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
|
|
1926
2653
|
var import_hw_app_trx = __toESM(require("@ledgerhq/hw-app-trx"));
|
|
1927
2654
|
|
|
1928
2655
|
// src/connector/chains/legacyChainCall.ts
|
|
1929
|
-
var
|
|
2656
|
+
var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
|
|
1930
2657
|
|
|
1931
2658
|
// src/app/AppManager.ts
|
|
1932
2659
|
var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
|
|
2660
|
+
var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
|
|
1933
2661
|
var APP_NAME_MAP = {
|
|
1934
2662
|
ETH: "Ethereum",
|
|
1935
2663
|
BTC: "Bitcoin",
|
|
@@ -2001,7 +2729,31 @@ var AppManager = class {
|
|
|
2001
2729
|
debugLog("[AppManager] currentApp:", result.data.name);
|
|
2002
2730
|
return result.data.name;
|
|
2003
2731
|
}
|
|
2004
|
-
|
|
2732
|
+
const errResult = result;
|
|
2733
|
+
const dmkErr = errResult.error ?? {};
|
|
2734
|
+
const original = dmkErr.originalError;
|
|
2735
|
+
debugLog(
|
|
2736
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
2737
|
+
sessionId,
|
|
2738
|
+
"tag=",
|
|
2739
|
+
dmkErr._tag,
|
|
2740
|
+
"errorCode=",
|
|
2741
|
+
dmkErr.errorCode,
|
|
2742
|
+
"message=",
|
|
2743
|
+
dmkErr.message,
|
|
2744
|
+
"originalErrorMessage=",
|
|
2745
|
+
original?.message ?? String(original ?? "")
|
|
2746
|
+
);
|
|
2747
|
+
throw Object.assign(
|
|
2748
|
+
new Error(
|
|
2749
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
2750
|
+
),
|
|
2751
|
+
{
|
|
2752
|
+
_tag: dmkErr._tag,
|
|
2753
|
+
errorCode: dmkErr.errorCode,
|
|
2754
|
+
originalError: original
|
|
2755
|
+
}
|
|
2756
|
+
);
|
|
2005
2757
|
}
|
|
2006
2758
|
async _openApp(sessionId, appName) {
|
|
2007
2759
|
const result = await this._dmk.sendCommand({
|
|
@@ -2010,9 +2762,11 @@ var AppManager = class {
|
|
|
2010
2762
|
});
|
|
2011
2763
|
if (!(0, import_device_management_kit2.isSuccessCommandResult)(result)) {
|
|
2012
2764
|
const { statusCode } = result;
|
|
2765
|
+
const hasStatusCode2 = statusCode != null && statusCode !== "";
|
|
2013
2766
|
debugLog("[AppManager] openApp failed:", appName, "statusCode:", statusCode);
|
|
2014
2767
|
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
2015
|
-
_tag:
|
|
2768
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
2769
|
+
code: hasStatusCode2 ? void 0 : import_hwk_adapter_core9.HardwareErrorCode.AppNotInstalled,
|
|
2016
2770
|
errorCode: String(statusCode ?? ""),
|
|
2017
2771
|
statusCode,
|
|
2018
2772
|
appName
|
|
@@ -2068,14 +2822,14 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
|
2068
2822
|
const onAppOpenPrompt = () => {
|
|
2069
2823
|
openAppPromptShown = true;
|
|
2070
2824
|
ctx.emit("ui-event", {
|
|
2071
|
-
type:
|
|
2825
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOpenApp,
|
|
2072
2826
|
payload: { sessionId }
|
|
2073
2827
|
});
|
|
2074
2828
|
};
|
|
2075
2829
|
const closeOpenAppUiIfShown = () => {
|
|
2076
2830
|
if (openAppPromptShown) {
|
|
2077
2831
|
ctx.emit("ui-event", {
|
|
2078
|
-
type:
|
|
2832
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
|
|
2079
2833
|
payload: { sessionId }
|
|
2080
2834
|
});
|
|
2081
2835
|
openAppPromptShown = false;
|
|
@@ -2096,7 +2850,7 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
|
2096
2850
|
let confirmEmitted = false;
|
|
2097
2851
|
if (needsConfirmation) {
|
|
2098
2852
|
ctx.emit("ui-event", {
|
|
2099
|
-
type:
|
|
2853
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
|
|
2100
2854
|
payload: { sessionId }
|
|
2101
2855
|
});
|
|
2102
2856
|
confirmEmitted = true;
|
|
@@ -2106,7 +2860,7 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
|
2106
2860
|
} finally {
|
|
2107
2861
|
if (confirmEmitted || openAppPromptShown) {
|
|
2108
2862
|
ctx.emit("ui-event", {
|
|
2109
|
-
type:
|
|
2863
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
|
|
2110
2864
|
payload: { sessionId }
|
|
2111
2865
|
});
|
|
2112
2866
|
openAppPromptShown = false;
|
|
@@ -2194,7 +2948,7 @@ async function tronSignTransaction(ctx, sessionId, params) {
|
|
|
2194
2948
|
if (!params.rawTxHex) {
|
|
2195
2949
|
throw Object.assign(
|
|
2196
2950
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
2197
|
-
{ code:
|
|
2951
|
+
{ code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
|
|
2198
2952
|
);
|
|
2199
2953
|
}
|
|
2200
2954
|
const path = normalizePath(params.path);
|
|
@@ -2238,6 +2992,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
2238
2992
|
sol: "Solana",
|
|
2239
2993
|
tron: "Tron"
|
|
2240
2994
|
};
|
|
2995
|
+
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
2996
|
+
Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
|
|
2997
|
+
);
|
|
2998
|
+
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
2241
2999
|
async function defaultLedgerKitImporter(pkg) {
|
|
2242
3000
|
switch (pkg) {
|
|
2243
3001
|
case "@ledgerhq/device-management-kit":
|
|
@@ -2261,16 +3019,6 @@ var LedgerConnectorBase = class {
|
|
|
2261
3019
|
this._dmk = null;
|
|
2262
3020
|
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
2263
3021
|
// ---------------------------------------------------------------------------
|
|
2264
|
-
// ConnectId <-> DMK path mapping
|
|
2265
|
-
//
|
|
2266
|
-
// DMK uses internal paths (BLE MAC, USB UUID) that may change across sessions.
|
|
2267
|
-
// _resolveConnectId() maps these to stable external IDs (BLE: "A58F", USB: same).
|
|
2268
|
-
// This bidirectional map is the SINGLE SOURCE OF TRUTH for all connectId usage.
|
|
2269
|
-
// ---------------------------------------------------------------------------
|
|
2270
|
-
this._connectIdToPath = /* @__PURE__ */ new Map();
|
|
2271
|
-
// "A58F" -> "D5:75:7D:4B:51:E8"
|
|
2272
|
-
this._pathToConnectId = /* @__PURE__ */ new Map();
|
|
2273
|
-
// ---------------------------------------------------------------------------
|
|
2274
3022
|
// Per-session DeviceAction cancellers
|
|
2275
3023
|
//
|
|
2276
3024
|
// Each chain handler registers its active DeviceAction's canceller via
|
|
@@ -2279,10 +3027,23 @@ var LedgerConnectorBase = class {
|
|
|
2279
3027
|
// unsubscribes the observable and releases DMK's IntentQueue slot.
|
|
2280
3028
|
// ---------------------------------------------------------------------------
|
|
2281
3029
|
this._cancellers = /* @__PURE__ */ new Map();
|
|
3030
|
+
// ---------------------------------------------------------------------------
|
|
3031
|
+
// Per-session DMK state subscriptions
|
|
3032
|
+
//
|
|
3033
|
+
// DMK only emits `device-disconnect` on the connector when `disconnect()`
|
|
3034
|
+
// is called explicitly. To pick up autonomous disconnects (USB unplug,
|
|
3035
|
+
// BLE drop, device sleep, DMK transport reset) we subscribe to
|
|
3036
|
+
// `_dmk.getDeviceSessionState({sessionId})` per active session and emit
|
|
3037
|
+
// `device-disconnect` ourselves the moment DMK reports the device went
|
|
3038
|
+
// to NOT_CONNECTED. The subscription is torn down on disconnect /
|
|
3039
|
+
// reset / observable completion to avoid leaks.
|
|
3040
|
+
// ---------------------------------------------------------------------------
|
|
3041
|
+
this._sessionStateSubs = /* @__PURE__ */ new Map();
|
|
2282
3042
|
this._createTransport = createTransport;
|
|
2283
3043
|
this.connectionType = options?.connectionType ?? "usb";
|
|
2284
3044
|
this._providedDmk = options?.dmk;
|
|
2285
3045
|
this._importLedgerKit = options?.importLedgerKit ?? defaultLedgerKitImporter;
|
|
3046
|
+
this._requirePreFlightScan = options?.requirePreFlightScan ?? false;
|
|
2286
3047
|
if (this._providedDmk) {
|
|
2287
3048
|
this._initManagers(this._providedDmk);
|
|
2288
3049
|
}
|
|
@@ -2300,38 +3061,23 @@ var LedgerConnectorBase = class {
|
|
|
2300
3061
|
importLedgerKit: this._importLedgerKit
|
|
2301
3062
|
};
|
|
2302
3063
|
}
|
|
2303
|
-
// "D5:75:7D:4B:51:E8" -> "A58F"
|
|
2304
|
-
/** Register a connectId <-> path mapping from a device descriptor. */
|
|
2305
|
-
_registerDeviceId(descriptor) {
|
|
2306
|
-
const connectId = this._resolveConnectId(descriptor);
|
|
2307
|
-
this._connectIdToPath.set(connectId, descriptor.path);
|
|
2308
|
-
this._pathToConnectId.set(descriptor.path, connectId);
|
|
2309
|
-
return connectId;
|
|
2310
|
-
}
|
|
2311
|
-
/** Get DMK path from external connectId. Falls back to connectId itself. */
|
|
2312
|
-
_getPathForConnectId(connectId) {
|
|
2313
|
-
return this._connectIdToPath.get(connectId) ?? connectId;
|
|
2314
|
-
}
|
|
2315
|
-
/** Get external connectId from DMK path. Falls back to path itself. */
|
|
2316
|
-
_getConnectIdForPath(path) {
|
|
2317
|
-
return this._pathToConnectId.get(path) ?? path;
|
|
2318
|
-
}
|
|
2319
3064
|
// ---------------------------------------------------------------------------
|
|
2320
3065
|
// Protected — hooks for subclasses
|
|
2321
3066
|
// ---------------------------------------------------------------------------
|
|
2322
3067
|
/**
|
|
2323
3068
|
* Resolve the connectId for a discovered device descriptor.
|
|
2324
3069
|
* Default: use the DMK path (ephemeral UUID).
|
|
2325
|
-
* Override in subclasses
|
|
3070
|
+
* Override in subclasses only when the public connectId differs from the transport path.
|
|
2326
3071
|
*/
|
|
2327
3072
|
_resolveConnectId(descriptor) {
|
|
2328
3073
|
return descriptor.path;
|
|
2329
3074
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
3075
|
+
/**
|
|
3076
|
+
* Authoritative discovery for this transport. Default = enumerate snapshot
|
|
3077
|
+
* (USB/HID). BLE overrides to drive from a live scan window since DMK's
|
|
3078
|
+
* enumerate() cache survives peripherals going offline.
|
|
3079
|
+
*/
|
|
3080
|
+
async _discoverDescriptors(dm) {
|
|
2335
3081
|
let descriptors = await dm.enumerate();
|
|
2336
3082
|
if (descriptors.length === 0) {
|
|
2337
3083
|
try {
|
|
@@ -2340,92 +3086,284 @@ var LedgerConnectorBase = class {
|
|
|
2340
3086
|
}
|
|
2341
3087
|
descriptors = await dm.enumerate();
|
|
2342
3088
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
};
|
|
2351
|
-
|
|
3089
|
+
return descriptors;
|
|
3090
|
+
}
|
|
3091
|
+
_assertSingleUsbDescriptor(descriptors) {
|
|
3092
|
+
if (isLedgerBleConnectionType(this.connectionType) || descriptors.length <= 1) {
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
debugLog(
|
|
3096
|
+
`[DMK] Multiple Ledger USB devices found (${descriptors.length}); refusing to choose by ephemeral path. paths=[${descriptors.map((d) => d.path).join(",")}]`
|
|
3097
|
+
);
|
|
3098
|
+
throw createMultipleUsbLedgerDevicesError();
|
|
3099
|
+
}
|
|
3100
|
+
// ---------------------------------------------------------------------------
|
|
3101
|
+
// IConnector -- Device discovery
|
|
3102
|
+
// ---------------------------------------------------------------------------
|
|
3103
|
+
async searchDevices() {
|
|
3104
|
+
const dm = await this._getDeviceManager();
|
|
3105
|
+
const descriptors = await this._discoverDescriptors(dm);
|
|
3106
|
+
this._assertSingleUsbDescriptor(descriptors);
|
|
3107
|
+
const resolvedDescriptors = descriptors.map((d) => ({
|
|
3108
|
+
descriptor: d,
|
|
3109
|
+
connectId: this._resolveConnectId(d)
|
|
3110
|
+
}));
|
|
3111
|
+
const result = resolvedDescriptors.map(({ descriptor: d, connectId }) => ({
|
|
3112
|
+
connectId,
|
|
3113
|
+
deviceId: d.path,
|
|
3114
|
+
name: d.name || d.type || "Ledger",
|
|
3115
|
+
model: d.type,
|
|
3116
|
+
modelName: d.modelName,
|
|
3117
|
+
rssi: d.rssi,
|
|
3118
|
+
isConnectable: d.isConnectable,
|
|
3119
|
+
serialNumber: d.serialNumber
|
|
3120
|
+
}));
|
|
3121
|
+
debugLog(
|
|
3122
|
+
`[DMK] connector.searchDevices() return count=${result.length} ids=[${result.map((r) => r.connectId).join(",")}] paths=[${result.map((r) => r.deviceId).join(",")}]`
|
|
3123
|
+
);
|
|
2352
3124
|
return result;
|
|
2353
3125
|
}
|
|
2354
3126
|
// ---------------------------------------------------------------------------
|
|
2355
3127
|
// IConnector -- Connection
|
|
2356
3128
|
// ---------------------------------------------------------------------------
|
|
2357
3129
|
async connect(deviceId) {
|
|
2358
|
-
const
|
|
2359
|
-
let
|
|
2360
|
-
|
|
2361
|
-
|
|
3130
|
+
const callerSuppliedConnectId = Boolean(deviceId);
|
|
3131
|
+
let targetPath = deviceId;
|
|
3132
|
+
if (callerSuppliedConnectId && !isLedgerBleConnectionType(this.connectionType)) {
|
|
3133
|
+
const dm = await this._getDeviceManager();
|
|
3134
|
+
this._assertSingleUsbDescriptor(await this._discoverDescriptors(dm));
|
|
3135
|
+
}
|
|
2362
3136
|
if (!targetPath) {
|
|
2363
|
-
const
|
|
2364
|
-
if (
|
|
3137
|
+
const discovered = await this.searchDevices();
|
|
3138
|
+
if (discovered.length === 0) {
|
|
2365
3139
|
throw new Error(
|
|
2366
|
-
`No Ledger device found. Make sure the device is connected${this.connectionType
|
|
3140
|
+
`No Ledger device found. Make sure the device is connected${isLedgerBleConnectionType(this.connectionType) ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
|
|
2367
3141
|
);
|
|
2368
3142
|
}
|
|
2369
|
-
targetPath =
|
|
2370
|
-
}
|
|
2371
|
-
const externalConnectId =
|
|
3143
|
+
targetPath = discovered[0].deviceId;
|
|
3144
|
+
}
|
|
3145
|
+
const externalConnectId = targetPath;
|
|
3146
|
+
const HANG_CEILING_MS = 5 * 6e4;
|
|
3147
|
+
const dmConnectWithObserve = async (dm, path) => {
|
|
3148
|
+
if (!isLedgerBleConnectionType(this.connectionType)) return dm.connect(path);
|
|
3149
|
+
let timeoutId;
|
|
3150
|
+
let timedOut = false;
|
|
3151
|
+
const connectPromise = dm.connect(path);
|
|
3152
|
+
const hangPromise = new Promise((_, reject) => {
|
|
3153
|
+
timeoutId = setTimeout(() => {
|
|
3154
|
+
timedOut = true;
|
|
3155
|
+
const err = new Error(
|
|
3156
|
+
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
3157
|
+
);
|
|
3158
|
+
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
3159
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
3160
|
+
reject(err);
|
|
3161
|
+
}, HANG_CEILING_MS);
|
|
3162
|
+
});
|
|
3163
|
+
try {
|
|
3164
|
+
return await Promise.race([connectPromise, hangPromise]);
|
|
3165
|
+
} catch (err) {
|
|
3166
|
+
const e = err;
|
|
3167
|
+
debugLog("[DMK] dm.connect rejected \u2014 observed:", {
|
|
3168
|
+
_tag: e?._tag,
|
|
3169
|
+
message: e?.message,
|
|
3170
|
+
errorCode: e?.errorCode,
|
|
3171
|
+
statusCode: e?.statusCode,
|
|
3172
|
+
originalTag: e?.originalError?._tag,
|
|
3173
|
+
originalMessage: e?.originalError?.message
|
|
3174
|
+
});
|
|
3175
|
+
if (timedOut) {
|
|
3176
|
+
void connectPromise.then(
|
|
3177
|
+
(sessionId) => dm.disconnect(sessionId).catch(() => void 0),
|
|
3178
|
+
() => void 0
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
throw err;
|
|
3182
|
+
} finally {
|
|
3183
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3184
|
+
}
|
|
3185
|
+
};
|
|
3186
|
+
const isBleDirectConnect = isLedgerBleConnectionType(this.connectionType) && callerSuppliedConnectId;
|
|
3187
|
+
const throwNotAdvertising = () => {
|
|
3188
|
+
const err = new Error(
|
|
3189
|
+
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
3190
|
+
);
|
|
3191
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
3192
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
|
|
3193
|
+
throw err;
|
|
3194
|
+
};
|
|
2372
3195
|
const doConnect = async (path) => {
|
|
2373
|
-
const
|
|
3196
|
+
const dm = await this._getDeviceManager();
|
|
3197
|
+
if (isBleDirectConnect && !dm.hasDiscoveredDevice(path)) {
|
|
3198
|
+
const live = await dm.getLiveDevices(BLE_CONNECT_SCAN_TIMEOUT_MS);
|
|
3199
|
+
if (this._requirePreFlightScan && !live.some((d) => d.id === path)) {
|
|
3200
|
+
throwNotAdvertising();
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
let sessionId;
|
|
3204
|
+
try {
|
|
3205
|
+
sessionId = await dmConnectWithObserve(dm, path);
|
|
3206
|
+
} catch (err) {
|
|
3207
|
+
if (isBleDirectConnect && err?._tag === ERROR_TAG.DeviceNotInDiscoveryCache) {
|
|
3208
|
+
throwNotAdvertising();
|
|
3209
|
+
}
|
|
3210
|
+
throw err;
|
|
3211
|
+
}
|
|
3212
|
+
try {
|
|
3213
|
+
this._watchSessionState(sessionId, externalConnectId);
|
|
3214
|
+
} catch (subErr) {
|
|
3215
|
+
debugLog("[DMK] state subscription failed during connect; disconnecting session:", subErr);
|
|
3216
|
+
try {
|
|
3217
|
+
await dm.disconnect(sessionId);
|
|
3218
|
+
} catch {
|
|
3219
|
+
}
|
|
3220
|
+
throw subErr;
|
|
3221
|
+
}
|
|
3222
|
+
const info = dm.getDiscoveredDeviceInfo(path);
|
|
2374
3223
|
const session = {
|
|
2375
3224
|
sessionId,
|
|
2376
3225
|
deviceInfo: {
|
|
2377
3226
|
vendor: "ledger",
|
|
2378
|
-
model: "unknown",
|
|
3227
|
+
model: info?.model ?? "unknown",
|
|
3228
|
+
modelName: info?.modelName,
|
|
2379
3229
|
firmwareVersion: "unknown",
|
|
2380
3230
|
deviceId: path,
|
|
2381
3231
|
connectId: externalConnectId,
|
|
2382
3232
|
connectionType: this.connectionType,
|
|
3233
|
+
rssi: info?.rssi,
|
|
2383
3234
|
capabilities: { persistentDeviceIdentity: false }
|
|
2384
3235
|
}
|
|
2385
3236
|
};
|
|
2386
|
-
const
|
|
3237
|
+
const name = dm.getDeviceName(path) ?? "Ledger";
|
|
2387
3238
|
this._emit("device-connect", {
|
|
2388
|
-
device: {
|
|
2389
|
-
connectId: externalConnectId,
|
|
2390
|
-
deviceId: path,
|
|
2391
|
-
name: realName
|
|
2392
|
-
}
|
|
3239
|
+
device: { connectId: externalConnectId, deviceId: path, name }
|
|
2393
3240
|
});
|
|
2394
3241
|
return session;
|
|
2395
3242
|
};
|
|
2396
3243
|
try {
|
|
2397
3244
|
return await doConnect(targetPath);
|
|
2398
|
-
} catch {
|
|
3245
|
+
} catch (err) {
|
|
2399
3246
|
this._resetSignersAndSessions();
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
const descriptors = await dm2.enumerate();
|
|
2405
|
-
if (descriptors.length === 0) {
|
|
2406
|
-
throw new Error(
|
|
2407
|
-
`No Ledger device found after retry. Make sure the device is connected${this.connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
|
|
2408
|
-
);
|
|
3247
|
+
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
3248
|
+
const tag = err?._tag;
|
|
3249
|
+
if (isKnownConnectionTag(tag)) {
|
|
3250
|
+
throw err;
|
|
2409
3251
|
}
|
|
2410
|
-
|
|
3252
|
+
const wrapped = new Error(
|
|
3253
|
+
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
3254
|
+
);
|
|
3255
|
+
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
3256
|
+
wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
3257
|
+
wrapped.originalError = err;
|
|
3258
|
+
throw wrapped;
|
|
3259
|
+
}
|
|
3260
|
+
if (err && typeof err === "object" && err._tag) {
|
|
3261
|
+
const taggedError = err instanceof Error ? err : Object.assign(
|
|
3262
|
+
new Error(err.message ?? "Ledger device error"),
|
|
3263
|
+
err
|
|
3264
|
+
);
|
|
3265
|
+
throw taggedError;
|
|
2411
3266
|
}
|
|
2412
|
-
|
|
3267
|
+
throw new Error("Ledger device appears unplugged. Plug in the device and try again.");
|
|
2413
3268
|
}
|
|
2414
3269
|
}
|
|
2415
3270
|
async disconnect(sessionId) {
|
|
2416
3271
|
if (!this._deviceManager) return;
|
|
2417
3272
|
const deviceId = this._deviceManager.getDeviceId(sessionId);
|
|
2418
3273
|
this._signerManager?.invalidate(sessionId);
|
|
3274
|
+
this._unwatchSessionState(sessionId);
|
|
2419
3275
|
await this._deviceManager.disconnect(sessionId);
|
|
2420
3276
|
if (deviceId) {
|
|
2421
3277
|
this._emit("device-disconnect", { connectId: deviceId });
|
|
2422
3278
|
}
|
|
2423
3279
|
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Subscribe to DMK's per-session state observable so that any autonomous
|
|
3282
|
+
* disconnect (USB unplug, BLE drop, device sleep, transport reset) is
|
|
3283
|
+
* surfaced as a `device-disconnect` event — without this, the upstream
|
|
3284
|
+
* `LedgerAdapter._sessions` map would hold a dead session entry until
|
|
3285
|
+
* the next call hit `DeviceSessionNotFound`.
|
|
3286
|
+
*
|
|
3287
|
+
* Subscribe failure is fatal — see the inline note at the subscribe call.
|
|
3288
|
+
*/
|
|
3289
|
+
_watchSessionState(sessionId, externalConnectId) {
|
|
3290
|
+
const dmk = this._dmk;
|
|
3291
|
+
if (!dmk) return;
|
|
3292
|
+
const previous = this._sessionStateSubs.get(sessionId);
|
|
3293
|
+
if (previous) {
|
|
3294
|
+
try {
|
|
3295
|
+
previous.unsubscribe();
|
|
3296
|
+
} catch {
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
|
|
3300
|
+
next: (state) => {
|
|
3301
|
+
if (state?.deviceStatus === "NOT CONNECTED") {
|
|
3302
|
+
this._handleAutonomousDisconnect(sessionId, externalConnectId);
|
|
3303
|
+
}
|
|
3304
|
+
},
|
|
3305
|
+
error: () => {
|
|
3306
|
+
this._handleAutonomousDisconnect(sessionId, externalConnectId);
|
|
3307
|
+
},
|
|
3308
|
+
complete: () => {
|
|
3309
|
+
this._handleAutonomousDisconnect(sessionId, externalConnectId);
|
|
3310
|
+
}
|
|
3311
|
+
});
|
|
3312
|
+
this._sessionStateSubs.set(sessionId, sub);
|
|
3313
|
+
}
|
|
3314
|
+
_unwatchSessionState(sessionId) {
|
|
3315
|
+
const sub = this._sessionStateSubs.get(sessionId);
|
|
3316
|
+
if (!sub) return;
|
|
3317
|
+
try {
|
|
3318
|
+
sub.unsubscribe();
|
|
3319
|
+
} catch {
|
|
3320
|
+
}
|
|
3321
|
+
this._sessionStateSubs.delete(sessionId);
|
|
3322
|
+
}
|
|
3323
|
+
_handleAutonomousDisconnect(sessionId, externalConnectId) {
|
|
3324
|
+
if (!this._sessionStateSubs.has(sessionId)) return;
|
|
3325
|
+
debugLog(
|
|
3326
|
+
"[DMK] autonomous disconnect detected \u2014 sessionId:",
|
|
3327
|
+
sessionId,
|
|
3328
|
+
"connectId:",
|
|
3329
|
+
externalConnectId
|
|
3330
|
+
);
|
|
3331
|
+
this._unwatchSessionState(sessionId);
|
|
3332
|
+
this._signerManager?.invalidate(sessionId);
|
|
3333
|
+
this._cancellers.get(sessionId)?.({
|
|
3334
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
|
|
3335
|
+
tag: "DeviceDisconnected",
|
|
3336
|
+
message: "Device disconnected"
|
|
3337
|
+
});
|
|
3338
|
+
this._cancellers.delete(sessionId);
|
|
3339
|
+
this._emit("device-disconnect", { connectId: externalConnectId });
|
|
3340
|
+
}
|
|
2424
3341
|
// ---------------------------------------------------------------------------
|
|
2425
3342
|
// IConnector -- Method dispatch
|
|
2426
3343
|
// ---------------------------------------------------------------------------
|
|
2427
3344
|
async call(sessionId, method, params) {
|
|
2428
3345
|
debugLog("[DMK] call:", method, JSON.stringify(params));
|
|
3346
|
+
try {
|
|
3347
|
+
return await this._dispatch(sessionId, method, params);
|
|
3348
|
+
} catch (err) {
|
|
3349
|
+
if (isAppStuckByApdu(err)) {
|
|
3350
|
+
throw Object.assign(new Error("Ledger app is unresponsive"), {
|
|
3351
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
|
|
3352
|
+
_tag: ERROR_TAG.DeviceAppStuck,
|
|
3353
|
+
originalError: err
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
if (isTransportStuck(err)) {
|
|
3357
|
+
throw Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
3358
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
|
|
3359
|
+
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
3360
|
+
originalError: err
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
throw err;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
async _dispatch(sessionId, method, params) {
|
|
2429
3367
|
const ctx = this._ctxForMethod(method);
|
|
2430
3368
|
switch (method) {
|
|
2431
3369
|
// EVM
|
|
@@ -2565,14 +3503,13 @@ var LedgerConnectorBase = class {
|
|
|
2565
3503
|
}
|
|
2566
3504
|
/**
|
|
2567
3505
|
* Replace an old session with a new one after app switch.
|
|
2568
|
-
*
|
|
2569
|
-
* and emits device-connect so the adapter updates its _sessions Map.
|
|
3506
|
+
* Emits device-connect so the adapter updates its _sessions Map.
|
|
2570
3507
|
*/
|
|
2571
3508
|
_replaceSession(oldSessionId, _newSessionId) {
|
|
2572
3509
|
const dm = this._deviceManager;
|
|
2573
3510
|
if (!dm) return;
|
|
2574
3511
|
const oldDeviceId = dm.getDeviceId(oldSessionId);
|
|
2575
|
-
const connectId = oldDeviceId
|
|
3512
|
+
const connectId = oldDeviceId;
|
|
2576
3513
|
this._signerManager?.invalidate(oldSessionId);
|
|
2577
3514
|
if (connectId) {
|
|
2578
3515
|
this._emit("device-connect", {
|
|
@@ -2585,13 +3522,18 @@ var LedgerConnectorBase = class {
|
|
|
2585
3522
|
}
|
|
2586
3523
|
}
|
|
2587
3524
|
/**
|
|
2588
|
-
* Light reset: clear signer/session state but keep DMK
|
|
3525
|
+
* Light reset: clear signer/session state but keep DMK alive.
|
|
2589
3526
|
* Used by connect() retry — we want to re-discover with the same transport.
|
|
3527
|
+
*
|
|
3528
|
+
* Note: drops the device manager but tears down its RxJS subs first via
|
|
3529
|
+
* disposeKeepingDmk(). Bare null-assignment would leak _listenSub /
|
|
3530
|
+
* _discoverySub and double-scan after the next _initManagers().
|
|
2590
3531
|
*/
|
|
2591
3532
|
_resetSignersAndSessions() {
|
|
2592
3533
|
debugLog("[DMK] _resetSignersAndSessions called");
|
|
2593
3534
|
this._signerManager?.clearAll();
|
|
2594
3535
|
this._signerManager = null;
|
|
3536
|
+
this._deviceManager?.disposeKeepingDmk();
|
|
2595
3537
|
this._deviceManager = null;
|
|
2596
3538
|
}
|
|
2597
3539
|
_resetAll() {
|
|
@@ -2603,13 +3545,18 @@ var LedgerConnectorBase = class {
|
|
|
2603
3545
|
}
|
|
2604
3546
|
}
|
|
2605
3547
|
this._cancellers.clear();
|
|
3548
|
+
for (const sub of this._sessionStateSubs.values()) {
|
|
3549
|
+
try {
|
|
3550
|
+
sub.unsubscribe();
|
|
3551
|
+
} catch {
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
this._sessionStateSubs.clear();
|
|
2606
3555
|
this._signerManager?.clearAll();
|
|
2607
3556
|
this._deviceManager?.dispose();
|
|
2608
3557
|
this._deviceManager = null;
|
|
2609
3558
|
this._signerManager = null;
|
|
2610
3559
|
this._dmk = null;
|
|
2611
|
-
this._connectIdToPath.clear();
|
|
2612
|
-
this._pathToConnectId.clear();
|
|
2613
3560
|
}
|
|
2614
3561
|
// ---------------------------------------------------------------------------
|
|
2615
3562
|
// Private -- Events
|
|
@@ -2643,15 +3590,21 @@ var LedgerConnectorBase = class {
|
|
|
2643
3590
|
};
|
|
2644
3591
|
}
|
|
2645
3592
|
_wrapError(err, opts) {
|
|
2646
|
-
const mapped = mapLedgerError(err, opts);
|
|
2647
|
-
const error = new Error(mapped.message);
|
|
2648
3593
|
const src = err && typeof err === "object" ? err : {};
|
|
3594
|
+
const hasSerializedCode = typeof src.code === "number" && HARDWARE_ERROR_CODE_VALUES.has(src.code);
|
|
3595
|
+
const mapped = hasSerializedCode ? {
|
|
3596
|
+
code: src.code,
|
|
3597
|
+
message: typeof src.message === "string" ? src.message : "Unknown Ledger error",
|
|
3598
|
+
appName: typeof src.appName === "string" ? src.appName : opts?.defaultAppName
|
|
3599
|
+
} : mapLedgerError(err, opts);
|
|
3600
|
+
const error = new Error(mapped.message);
|
|
2649
3601
|
Object.assign(error, {
|
|
2650
3602
|
code: mapped.code,
|
|
2651
3603
|
appName: mapped.appName,
|
|
2652
3604
|
_tag: src._tag,
|
|
2653
3605
|
errorCode: src.errorCode,
|
|
2654
|
-
_lastStep: src._lastStep
|
|
3606
|
+
_lastStep: src._lastStep,
|
|
3607
|
+
_deviceActionSteps: src._deviceActionSteps
|
|
2655
3608
|
});
|
|
2656
3609
|
Object.defineProperty(error, "originalError", {
|
|
2657
3610
|
value: err,
|
|
@@ -2662,13 +3615,6 @@ var LedgerConnectorBase = class {
|
|
|
2662
3615
|
return error;
|
|
2663
3616
|
}
|
|
2664
3617
|
};
|
|
2665
|
-
|
|
2666
|
-
// src/utils/bleIdentity.ts
|
|
2667
|
-
function extractBleHexId(name) {
|
|
2668
|
-
if (!name) return void 0;
|
|
2669
|
-
const match = name.match(/\b([0-9A-Fa-f]{4})$/);
|
|
2670
|
-
return match ? match[1].toUpperCase() : void 0;
|
|
2671
|
-
}
|
|
2672
3618
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2673
3619
|
0 && (module.exports = {
|
|
2674
3620
|
AppManager,
|
|
@@ -2680,12 +3626,15 @@ function extractBleHexId(name) {
|
|
|
2680
3626
|
SignerEth,
|
|
2681
3627
|
SignerManager,
|
|
2682
3628
|
SignerSol,
|
|
3629
|
+
debugLog,
|
|
2683
3630
|
deviceActionToPromise,
|
|
2684
|
-
extractBleHexId,
|
|
2685
|
-
isDebugEnabled,
|
|
2686
3631
|
isDeviceLockedError,
|
|
3632
|
+
isLedgerBleConnectionType,
|
|
3633
|
+
isLedgerBleDescriptor,
|
|
3634
|
+
isLedgerDmkBleTransport,
|
|
2687
3635
|
ledgerFailure,
|
|
2688
3636
|
mapLedgerError,
|
|
2689
|
-
|
|
3637
|
+
offSdkEvent,
|
|
3638
|
+
onSdkEvent
|
|
2690
3639
|
});
|
|
2691
3640
|
//# sourceMappingURL=index.js.map
|