@onekeyfe/hwk-ledger-adapter 1.1.26-alpha.9 → 1.1.26-patch.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +252 -176
- package/dist/index.d.ts +252 -176
- package/dist/index.js +1377 -439
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1378 -441
- 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;
|
|
272
468
|
}
|
|
469
|
+
try {
|
|
470
|
+
return JSON.stringify(value);
|
|
471
|
+
} catch {
|
|
472
|
+
return String(value);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/utils/debugLog.ts
|
|
477
|
+
function debugLog(...args) {
|
|
478
|
+
emitLog("debug", ...args);
|
|
479
|
+
}
|
|
480
|
+
function debugError(...args) {
|
|
481
|
+
emitLog("error", ...args);
|
|
273
482
|
}
|
|
274
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,103 +872,250 @@ 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;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
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
|
+
);
|
|
985
|
+
}
|
|
986
|
+
return this._sessions.keys().next().value;
|
|
987
|
+
}
|
|
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
|
+
})();
|
|
734
999
|
}
|
|
1000
|
+
return this._abortable(signal, this._connectingPromise);
|
|
735
1001
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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: "" }
|
|
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
|
+
}
|
|
739
1038
|
if (devices.length > 0) {
|
|
740
|
-
|
|
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
|
+
}
|
|
741
1053
|
}
|
|
742
|
-
if (
|
|
743
|
-
|
|
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
|
+
);
|
|
744
1061
|
}
|
|
1062
|
+
await this._waitForDeviceConnect(internalSignal);
|
|
1063
|
+
confirms += 1;
|
|
745
1064
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
1065
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1066
|
+
throw new Error("_doConnect aborted");
|
|
1067
|
+
}
|
|
1068
|
+
async _connectFirstOrSelect(devices, targetConnectId) {
|
|
1069
|
+
if (targetConnectId) {
|
|
1070
|
+
const target = devices.find(
|
|
1071
|
+
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1072
|
+
);
|
|
1073
|
+
if (target) {
|
|
1074
|
+
return this._connectDeviceOrThrow(target.connectId);
|
|
1075
|
+
}
|
|
1076
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) {
|
|
1077
|
+
debugLog(
|
|
1078
|
+
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)`
|
|
1079
|
+
);
|
|
1080
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
1081
|
+
}
|
|
1082
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
1083
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
1084
|
+
});
|
|
1085
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
1086
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
1087
|
+
}
|
|
1088
|
+
throw err;
|
|
1089
|
+
}
|
|
1090
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
1091
|
+
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1092
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
if (devices.length > 1) {
|
|
1096
|
+
throw createMultipleUsbLedgerDevicesError();
|
|
1097
|
+
}
|
|
1098
|
+
if (devices.length !== 1) {
|
|
1099
|
+
throw Object.assign(new Error("Ledger device not found."), {
|
|
1100
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
752
1104
|
}
|
|
753
|
-
async
|
|
754
|
-
const chosenConnectId = devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices);
|
|
1105
|
+
async _connectDeviceOrThrow(chosenConnectId) {
|
|
755
1106
|
const result = await this.connectDevice(chosenConnectId);
|
|
756
1107
|
if (!result.success) {
|
|
757
|
-
|
|
758
|
-
|
|
1108
|
+
const payload = result.payload;
|
|
1109
|
+
const rethrow = Object.assign(new Error(payload.error), {
|
|
1110
|
+
code: payload.code
|
|
759
1111
|
});
|
|
1112
|
+
if (payload._tag !== void 0) {
|
|
1113
|
+
rethrow._tag = payload._tag;
|
|
1114
|
+
}
|
|
1115
|
+
throw rethrow;
|
|
760
1116
|
}
|
|
761
1117
|
return chosenConnectId;
|
|
762
1118
|
}
|
|
763
|
-
async _chooseDeviceFromList(devices) {
|
|
764
|
-
if (!this._handleSelectDevice) {
|
|
765
|
-
debugLog(
|
|
766
|
-
`[DMK] Multiple Ledger devices found (${devices.length}); handleSelectDevice=false, picking first (${devices[0].connectId}).`
|
|
767
|
-
);
|
|
768
|
-
return devices[0].connectId;
|
|
769
|
-
}
|
|
770
|
-
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
771
|
-
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
772
|
-
payload: { devices }
|
|
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" }
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
return chosen.connectId;
|
|
785
|
-
}
|
|
786
1119
|
/**
|
|
787
1120
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
788
1121
|
*
|
|
@@ -791,30 +1124,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
791
1124
|
* 3. Calls connector.call()
|
|
792
1125
|
* 4. On disconnect error: clears stale session, re-connects, retries once
|
|
793
1126
|
*/
|
|
794
|
-
async connectorCall(connectId, method, params, fingerprint) {
|
|
1127
|
+
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId) {
|
|
795
1128
|
debugLog("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
|
|
796
1129
|
const queueKey = connectId || "__ledger_default__";
|
|
797
|
-
const interruptibility = _LedgerAdapter._getInterruptibility(method);
|
|
798
1130
|
return this._jobQueue.enqueue(
|
|
799
1131
|
queueKey,
|
|
800
|
-
async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint),
|
|
801
|
-
{
|
|
1132
|
+
async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId),
|
|
1133
|
+
{
|
|
1134
|
+
label: method,
|
|
1135
|
+
rejectIfBusy: true,
|
|
1136
|
+
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
1137
|
+
}
|
|
802
1138
|
);
|
|
803
1139
|
}
|
|
804
1140
|
/**
|
|
805
|
-
* Race a promise against an abort signal.
|
|
806
|
-
* signal
|
|
807
|
-
* actually be cancelled on Ledger DMK, but the caller gets the abort immediately.
|
|
1141
|
+
* Race a promise against an abort signal. On abort, rejects with
|
|
1142
|
+
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
808
1143
|
*/
|
|
809
|
-
|
|
1144
|
+
_abortable(signal, promise) {
|
|
1145
|
+
const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
810
1146
|
if (signal.aborted) {
|
|
811
|
-
return Promise.reject(
|
|
812
|
-
signal.reason ?? new Error("Aborted")
|
|
813
|
-
);
|
|
1147
|
+
return Promise.reject(getAbortReason());
|
|
814
1148
|
}
|
|
815
1149
|
return new Promise((resolve, reject) => {
|
|
816
1150
|
const onAbort = () => {
|
|
817
|
-
reject(
|
|
1151
|
+
reject(getAbortReason());
|
|
818
1152
|
};
|
|
819
1153
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
820
1154
|
promise.then(
|
|
@@ -836,40 +1170,49 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
836
1170
|
}
|
|
837
1171
|
}
|
|
838
1172
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
839
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint) {
|
|
1173
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId) {
|
|
840
1174
|
_LedgerAdapter._throwIfAborted(signal);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1175
|
+
await this._ensureDevicePermission(
|
|
1176
|
+
connectId,
|
|
1177
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
1178
|
+
signal
|
|
844
1179
|
);
|
|
845
1180
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1181
|
+
let effectiveParams = params;
|
|
1182
|
+
if (method === "btcGetPublicKey") {
|
|
1183
|
+
const gatedParams = await this._gateBtcHighIndex(params);
|
|
1184
|
+
if (gatedParams === null) {
|
|
1185
|
+
throw Object.assign(new Error("User cancelled BTC high-index confirmation"), {
|
|
1186
|
+
_tag: ERROR_TAG.UserAborted,
|
|
1187
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
effectiveParams = gatedParams;
|
|
1191
|
+
}
|
|
1192
|
+
const resolvedConnectId = await this.ensureConnected(connectId, signal);
|
|
846
1193
|
const sessionId = this._sessions.get(resolvedConnectId);
|
|
847
|
-
debugLog(
|
|
848
|
-
"[LedgerAdapter] connectorCall resolved:",
|
|
849
|
-
method,
|
|
850
|
-
"resolvedConnectId:",
|
|
851
|
-
resolvedConnectId,
|
|
852
|
-
"sessionId:",
|
|
853
|
-
sessionId
|
|
854
|
-
);
|
|
855
1194
|
if (!sessionId) {
|
|
856
1195
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
857
|
-
_tag:
|
|
1196
|
+
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
858
1197
|
});
|
|
859
1198
|
}
|
|
860
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
861
|
-
const fp = await _LedgerAdapter._abortable(
|
|
862
|
-
signal,
|
|
863
|
-
this._verifyDeviceFingerprintWithSession(sessionId, fingerprint.deviceId, fingerprint.chain)
|
|
864
|
-
);
|
|
865
|
-
if (!fp.success) {
|
|
866
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
867
|
-
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
1199
|
try {
|
|
872
|
-
|
|
1200
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1201
|
+
const fp = await this._abortable(
|
|
1202
|
+
signal,
|
|
1203
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1204
|
+
sessionId,
|
|
1205
|
+
fingerprint.deviceId,
|
|
1206
|
+
fingerprint.chain
|
|
1207
|
+
)
|
|
1208
|
+
);
|
|
1209
|
+
if (!fp.success) {
|
|
1210
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1211
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return await this._abortable(signal, this.connector.call(sessionId, method, effectiveParams));
|
|
873
1216
|
} catch (err) {
|
|
874
1217
|
if (signal.aborted) throw err;
|
|
875
1218
|
const errObj = err;
|
|
@@ -879,37 +1222,241 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
879
1222
|
errorCode: errObj?.errorCode,
|
|
880
1223
|
statusCode: errObj?.statusCode,
|
|
881
1224
|
isDisconnected: isDeviceDisconnectedError(err),
|
|
882
|
-
isLocked: isDeviceLockedError(err)
|
|
1225
|
+
isLocked: isDeviceLockedError(err),
|
|
1226
|
+
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1227
|
+
isStuckApp: isStuckAppStateError(err)
|
|
883
1228
|
});
|
|
884
|
-
if (
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1229
|
+
if (isStuckAppStateError(err)) {
|
|
1230
|
+
try {
|
|
1231
|
+
this._sessions.delete(resolvedConnectId);
|
|
1232
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1233
|
+
this.connector.reset?.();
|
|
1234
|
+
} catch {
|
|
1235
|
+
}
|
|
1236
|
+
debugLog(
|
|
1237
|
+
"[LedgerAdapter] stuck-app retry: method=",
|
|
1238
|
+
method,
|
|
1239
|
+
"delayMs=",
|
|
1240
|
+
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
|
|
1241
|
+
"_tag=",
|
|
1242
|
+
err?._tag
|
|
1243
|
+
);
|
|
1244
|
+
try {
|
|
1245
|
+
const retryResult = await this._retryAfterStuckApp(
|
|
1246
|
+
resolvedConnectId,
|
|
1247
|
+
method,
|
|
1248
|
+
effectiveParams,
|
|
1249
|
+
signal,
|
|
1250
|
+
err,
|
|
1251
|
+
fingerprint
|
|
1252
|
+
);
|
|
1253
|
+
debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
|
|
1254
|
+
return retryResult;
|
|
1255
|
+
} catch (retryErr) {
|
|
1256
|
+
if (signal.aborted) throw retryErr;
|
|
1257
|
+
if (isStuckAppStateError(retryErr)) {
|
|
1258
|
+
debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
|
|
1259
|
+
throw err;
|
|
1260
|
+
}
|
|
1261
|
+
debugLog(
|
|
1262
|
+
"[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
|
|
1263
|
+
method,
|
|
1264
|
+
"retryErrTag=",
|
|
1265
|
+
retryErr?._tag
|
|
1266
|
+
);
|
|
1267
|
+
throw retryErr;
|
|
1268
|
+
}
|
|
888
1269
|
}
|
|
889
|
-
if (isDeviceLockedError(err)) {
|
|
890
|
-
|
|
891
|
-
_LedgerAdapter.
|
|
892
|
-
|
|
1270
|
+
if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
|
|
1271
|
+
let lastErr = err;
|
|
1272
|
+
for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
|
|
1273
|
+
if (signal.aborted) throw lastErr;
|
|
1274
|
+
try {
|
|
1275
|
+
this._sessions.delete(resolvedConnectId);
|
|
1276
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1277
|
+
if (isDeviceDisconnectedError(lastErr)) {
|
|
1278
|
+
try {
|
|
1279
|
+
this.connector.reset?.();
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
const reConnectId = await this.ensureConnected(resolvedConnectId, signal);
|
|
1284
|
+
const reSessionId = this._sessions.get(reConnectId);
|
|
1285
|
+
if (!reSessionId) throw lastErr;
|
|
1286
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1287
|
+
const fp = await this._abortable(
|
|
1288
|
+
signal,
|
|
1289
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1290
|
+
reSessionId,
|
|
1291
|
+
fingerprint.deviceId,
|
|
1292
|
+
fingerprint.chain
|
|
1293
|
+
)
|
|
1294
|
+
);
|
|
1295
|
+
if (!fp.success) {
|
|
1296
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1297
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return await this._abortable(
|
|
1302
|
+
signal,
|
|
1303
|
+
this.connector.call(reSessionId, method, effectiveParams)
|
|
1304
|
+
);
|
|
1305
|
+
} catch (retryErr) {
|
|
1306
|
+
if (signal.aborted) throw retryErr;
|
|
1307
|
+
lastErr = retryErr;
|
|
1308
|
+
const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
|
|
1309
|
+
if (!canRetry) {
|
|
1310
|
+
throw retryErr;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
throw lastErr;
|
|
893
1315
|
}
|
|
894
1316
|
if (isTimeoutError(err)) {
|
|
895
1317
|
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
896
1318
|
this._discoveredDevices.delete(resolvedConnectId);
|
|
897
|
-
return this._retryWithFreshConnection(
|
|
1319
|
+
return this._retryWithFreshConnection(
|
|
1320
|
+
resolvedConnectId,
|
|
1321
|
+
method,
|
|
1322
|
+
effectiveParams,
|
|
1323
|
+
signal,
|
|
1324
|
+
err,
|
|
1325
|
+
fingerprint
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
if (isConnectionLevelError(err)) {
|
|
1329
|
+
debugLog("[LedgerAdapter] connection-level fail-closed reset");
|
|
1330
|
+
this._sessions.delete(resolvedConnectId);
|
|
1331
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1332
|
+
this.connector.reset();
|
|
1333
|
+
const codeNum = err?.code;
|
|
1334
|
+
throw Object.assign(err, {
|
|
1335
|
+
code: codeNum ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceDisconnected
|
|
1336
|
+
});
|
|
898
1337
|
}
|
|
899
1338
|
throw err;
|
|
900
1339
|
}
|
|
901
1340
|
}
|
|
902
|
-
/**
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1341
|
+
/**
|
|
1342
|
+
* Stuck-app recovery: pause for the device's UI transition, then retry once.
|
|
1343
|
+
*
|
|
1344
|
+
* Caller has already cleared the session + reset connector. We wait so Stax
|
|
1345
|
+
* finishes its post-CloseApp animation, then go through ensureConnected +
|
|
1346
|
+
* fingerprint check + call exactly once. Caller decides what to do on a
|
|
1347
|
+
* second stuck-app hit.
|
|
1348
|
+
*/
|
|
1349
|
+
async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
|
|
1350
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
1351
|
+
const retryConnectId = await this.ensureConnected(resolvedConnectId, signal);
|
|
1352
|
+
const retrySessionId = this._sessions.get(retryConnectId);
|
|
1353
|
+
if (!retrySessionId) throw originalErr;
|
|
1354
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1355
|
+
const fp = await this._abortable(
|
|
1356
|
+
signal,
|
|
1357
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1358
|
+
retrySessionId,
|
|
1359
|
+
fingerprint.deviceId,
|
|
1360
|
+
fingerprint.chain
|
|
1361
|
+
)
|
|
1362
|
+
);
|
|
1363
|
+
if (!fp.success) {
|
|
1364
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1365
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return this._abortable(signal, this.connector.call(retrySessionId, method, params));
|
|
1370
|
+
}
|
|
1371
|
+
_sleepAbortable(ms, signal) {
|
|
1372
|
+
return new Promise((resolve, reject) => {
|
|
1373
|
+
if (signal.aborted) {
|
|
1374
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const timer = setTimeout(() => {
|
|
1378
|
+
signal.removeEventListener("abort", onAbort);
|
|
1379
|
+
resolve();
|
|
1380
|
+
}, ms);
|
|
1381
|
+
const onAbort = () => {
|
|
1382
|
+
clearTimeout(timer);
|
|
1383
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
1384
|
+
};
|
|
1385
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Clear stale session, reconnect, and retry the call.
|
|
1390
|
+
*
|
|
1391
|
+
* Timeout recovery starts with a full connector reset. After an APDU
|
|
1392
|
+
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
1393
|
+
* on the same DMK can poison the next chain switch.
|
|
1394
|
+
*/
|
|
1395
|
+
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
1396
|
+
this.connector.reset();
|
|
1397
|
+
this._sessions.clear();
|
|
1398
|
+
this._discoveredDevices.clear();
|
|
1399
|
+
this._connectingPromise = null;
|
|
1400
|
+
const retryConnectId = await this.ensureConnected(targetConnectId, signal);
|
|
908
1401
|
const retrySessionId = this._sessions.get(retryConnectId);
|
|
909
1402
|
if (!retrySessionId) {
|
|
910
1403
|
throw originalErr;
|
|
911
1404
|
}
|
|
912
|
-
|
|
1405
|
+
try {
|
|
1406
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1407
|
+
const fp = await this._abortable(
|
|
1408
|
+
signal,
|
|
1409
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1410
|
+
retrySessionId,
|
|
1411
|
+
fingerprint.deviceId,
|
|
1412
|
+
fingerprint.chain
|
|
1413
|
+
)
|
|
1414
|
+
);
|
|
1415
|
+
if (!fp.success) {
|
|
1416
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1417
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return await this._abortable(signal, this.connector.call(retrySessionId, method, params));
|
|
1422
|
+
} catch (retryErr) {
|
|
1423
|
+
if (signal.aborted) throw retryErr;
|
|
1424
|
+
this.connector.reset();
|
|
1425
|
+
this._sessions.clear();
|
|
1426
|
+
this._discoveredDevices.clear();
|
|
1427
|
+
this._connectingPromise = null;
|
|
1428
|
+
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
1429
|
+
throw retryErr;
|
|
1430
|
+
}
|
|
1431
|
+
debugLog(
|
|
1432
|
+
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
1433
|
+
);
|
|
1434
|
+
this.connector.reset();
|
|
1435
|
+
this._sessions.clear();
|
|
1436
|
+
this._discoveredDevices.clear();
|
|
1437
|
+
this._connectingPromise = null;
|
|
1438
|
+
const finalConnectId = await this.ensureConnected(targetConnectId, signal);
|
|
1439
|
+
const finalSessionId = this._sessions.get(finalConnectId);
|
|
1440
|
+
if (!finalSessionId) {
|
|
1441
|
+
throw originalErr;
|
|
1442
|
+
}
|
|
1443
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1444
|
+
const fp = await this._abortable(
|
|
1445
|
+
signal,
|
|
1446
|
+
this._verifyDeviceFingerprintWithSession(
|
|
1447
|
+
finalSessionId,
|
|
1448
|
+
fingerprint.deviceId,
|
|
1449
|
+
fingerprint.chain
|
|
1450
|
+
)
|
|
1451
|
+
);
|
|
1452
|
+
if (!fp.success) {
|
|
1453
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1454
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return this._abortable(signal, this.connector.call(finalSessionId, method, params));
|
|
1459
|
+
}
|
|
913
1460
|
}
|
|
914
1461
|
/**
|
|
915
1462
|
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
@@ -923,7 +1470,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
923
1470
|
* - No connectId (searchDevices): environment-level permission
|
|
924
1471
|
* - With connectId (business methods): device-level permission
|
|
925
1472
|
*/
|
|
926
|
-
async _ensureDevicePermission(connectId, deviceId) {
|
|
1473
|
+
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
1474
|
+
if (signal?.aborted) {
|
|
1475
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1476
|
+
}
|
|
927
1477
|
const transportType = this.activeTransport ?? "hid";
|
|
928
1478
|
const waitPromise = this._uiRegistry.wait(
|
|
929
1479
|
import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
@@ -933,10 +1483,37 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
933
1483
|
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
934
1484
|
payload: { transportType, connectId, deviceId }
|
|
935
1485
|
});
|
|
936
|
-
|
|
1486
|
+
let response;
|
|
1487
|
+
const onAbort = () => {
|
|
1488
|
+
this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION);
|
|
1489
|
+
};
|
|
1490
|
+
try {
|
|
1491
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1492
|
+
response = await waitPromise;
|
|
1493
|
+
} catch (err) {
|
|
1494
|
+
if (signal?.aborted) {
|
|
1495
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1496
|
+
}
|
|
1497
|
+
if (err?._tag === import_hwk_adapter_core2.UI_REQUEST_PREEMPTED_TAG) {
|
|
1498
|
+
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
|
|
1499
|
+
type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
|
|
1500
|
+
payload: {}
|
|
1501
|
+
});
|
|
1502
|
+
throw Object.assign(new Error("Device permission request superseded"), {
|
|
1503
|
+
_tag: ERROR_TAG.UserAborted,
|
|
1504
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
|
|
1505
|
+
cause: err
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
throw err;
|
|
1509
|
+
} finally {
|
|
1510
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1511
|
+
}
|
|
1512
|
+
const { granted, reason, message } = response;
|
|
937
1513
|
if (!granted) {
|
|
938
|
-
throw Object.assign(new Error("Device permission denied"), {
|
|
939
|
-
code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied
|
|
1514
|
+
throw Object.assign(new Error(message ?? "Device permission denied"), {
|
|
1515
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied,
|
|
1516
|
+
reason
|
|
940
1517
|
});
|
|
941
1518
|
}
|
|
942
1519
|
}
|
|
@@ -946,12 +1523,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
946
1523
|
*/
|
|
947
1524
|
errorToFailure(err) {
|
|
948
1525
|
debugError("[LedgerAdapter] error:", err);
|
|
1526
|
+
const tag = err && typeof err === "object" ? err._tag : void 0;
|
|
949
1527
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
950
1528
|
const e = err;
|
|
951
|
-
|
|
1529
|
+
const params = e.code === import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : void 0;
|
|
1530
|
+
return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
|
|
952
1531
|
}
|
|
953
1532
|
const mapped = mapLedgerError(err);
|
|
954
|
-
return ledgerFailure(mapped.code, mapped.message, mapped.appName);
|
|
1533
|
+
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
955
1534
|
}
|
|
956
1535
|
registerEventListeners() {
|
|
957
1536
|
this.connector.on("device-connect", this.deviceConnectHandler);
|
|
@@ -967,32 +1546,38 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
967
1546
|
// Device info mapping
|
|
968
1547
|
// ---------------------------------------------------------------------------
|
|
969
1548
|
connectorDeviceToDeviceInfo(device) {
|
|
970
|
-
const isBle = device.connectId && /^[0-9A-Fa-f]{4}$/.test(device.connectId);
|
|
971
1549
|
return {
|
|
972
1550
|
vendor: "ledger",
|
|
973
1551
|
model: device.model ?? "unknown",
|
|
1552
|
+
modelName: device.modelName,
|
|
974
1553
|
firmwareVersion: "",
|
|
975
1554
|
deviceId: device.deviceId,
|
|
976
1555
|
connectId: device.connectId,
|
|
977
1556
|
label: device.name,
|
|
978
|
-
connectionType:
|
|
1557
|
+
connectionType: this.connector.connectionType,
|
|
1558
|
+
rssi: device.rssi,
|
|
1559
|
+
isConnectable: device.isConnectable,
|
|
1560
|
+
serialNumber: device.serialNumber,
|
|
979
1561
|
capabilities: device.capabilities
|
|
980
1562
|
};
|
|
981
1563
|
}
|
|
982
1564
|
};
|
|
983
|
-
//
|
|
984
|
-
//
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
_LedgerAdapter.
|
|
1565
|
+
// Layer 2 retry budget after connection-class error. Each round delegates
|
|
1566
|
+
// to Layer 1 (which owns the unlock prompt). Layer 2 never emits UI itself.
|
|
1567
|
+
_LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET = 3;
|
|
1568
|
+
// Stuck-app (APDU 0x6901) retry pause. Ledger Stax often returns 6901 when
|
|
1569
|
+
// OpenAppCommand lands mid-transition right after CloseApp; a short wait
|
|
1570
|
+
// lets the device finish its UI animation before we retry once.
|
|
1571
|
+
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS = 500;
|
|
1572
|
+
// Layer 1 confirm budget. After N failed Confirm cycles (user keeps clicking
|
|
1573
|
+
// Confirm but device never shows up / never unlocks), throw DeviceNotFound
|
|
1574
|
+
// instead of looping forever.
|
|
1575
|
+
_LedgerAdapter.MAX_DOCONNECT_CONFIRMS = 3;
|
|
994
1576
|
var LedgerAdapter = _LedgerAdapter;
|
|
995
1577
|
|
|
1578
|
+
// src/connector/LedgerConnectorBase.ts
|
|
1579
|
+
var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
|
|
1580
|
+
|
|
996
1581
|
// src/device/LedgerDeviceManager.ts
|
|
997
1582
|
var LedgerDeviceManager = class {
|
|
998
1583
|
constructor(dmk) {
|
|
@@ -1029,7 +1614,9 @@ var LedgerDeviceManager = class {
|
|
|
1029
1614
|
if (resolved) return;
|
|
1030
1615
|
resolved = true;
|
|
1031
1616
|
this._discovered.clear();
|
|
1032
|
-
debugLog(
|
|
1617
|
+
debugLog(
|
|
1618
|
+
`[DMK] enumerate count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
|
|
1619
|
+
);
|
|
1033
1620
|
for (const d of devices) {
|
|
1034
1621
|
this._discovered.set(d.id, d);
|
|
1035
1622
|
}
|
|
@@ -1039,8 +1626,10 @@ var LedgerDeviceManager = class {
|
|
|
1039
1626
|
devices.map((d) => ({
|
|
1040
1627
|
path: d.id,
|
|
1041
1628
|
type: d.deviceModel.model,
|
|
1629
|
+
modelName: d.deviceModel.name,
|
|
1042
1630
|
name: d.name,
|
|
1043
|
-
transport: d.transport
|
|
1631
|
+
transport: d.transport,
|
|
1632
|
+
rssi: d.rssi
|
|
1044
1633
|
}))
|
|
1045
1634
|
);
|
|
1046
1635
|
} else {
|
|
@@ -1061,8 +1650,10 @@ var LedgerDeviceManager = class {
|
|
|
1061
1650
|
devices.map((d) => ({
|
|
1062
1651
|
path: d.id,
|
|
1063
1652
|
type: d.deviceModel.model,
|
|
1653
|
+
modelName: d.deviceModel.name,
|
|
1064
1654
|
name: d.name,
|
|
1065
|
-
transport: d.transport
|
|
1655
|
+
transport: d.transport,
|
|
1656
|
+
rssi: d.rssi
|
|
1066
1657
|
}))
|
|
1067
1658
|
);
|
|
1068
1659
|
}
|
|
@@ -1086,8 +1677,10 @@ var LedgerDeviceManager = class {
|
|
|
1086
1677
|
descriptor: {
|
|
1087
1678
|
path: d.id,
|
|
1088
1679
|
type: d.deviceModel.model,
|
|
1680
|
+
modelName: d.deviceModel.name,
|
|
1089
1681
|
name: d.name,
|
|
1090
|
-
transport: d.transport
|
|
1682
|
+
transport: d.transport,
|
|
1683
|
+
rssi: d.rssi
|
|
1091
1684
|
}
|
|
1092
1685
|
});
|
|
1093
1686
|
}
|
|
@@ -1106,6 +1699,54 @@ var LedgerDeviceManager = class {
|
|
|
1106
1699
|
this._listenSub?.unsubscribe();
|
|
1107
1700
|
this._listenSub = null;
|
|
1108
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Resolve to the latest snapshot of advertising devices observed within
|
|
1704
|
+
* `timeoutMs`. Rewrites `_discovered` so it tracks the live list.
|
|
1705
|
+
*
|
|
1706
|
+
* `listenToAvailableDevices` is a BehaviorSubject — it emits the cached
|
|
1707
|
+
* list on subscribe and *only* re-emits when the list changes. A stable
|
|
1708
|
+
* list of currently-advertising devices therefore produces only the
|
|
1709
|
+
* subscription frame; treating that frame as "fossil to be discarded"
|
|
1710
|
+
* caused devices to flicker in/out of the UI as searches alternated
|
|
1711
|
+
* between "saw a change frame" and "saw only the cached frame".
|
|
1712
|
+
*
|
|
1713
|
+
* The window still gives a chance for the active scan to update the list
|
|
1714
|
+
* (e.g. drop a peripheral that just stopped broadcasting), but if nothing
|
|
1715
|
+
* changes we trust the snapshot — DMK silence on a stable list means
|
|
1716
|
+
* "everything's still there".
|
|
1717
|
+
*/
|
|
1718
|
+
getLiveDevices(timeoutMs = 1500) {
|
|
1719
|
+
debugLog(`[DMK] getLiveDevices() called, timeoutMs=${timeoutMs}`);
|
|
1720
|
+
void this.requestDevice();
|
|
1721
|
+
return new Promise((resolve) => {
|
|
1722
|
+
let done = false;
|
|
1723
|
+
let lastSeen = [];
|
|
1724
|
+
let sub = null;
|
|
1725
|
+
const finish = (devices) => {
|
|
1726
|
+
if (done) return;
|
|
1727
|
+
done = true;
|
|
1728
|
+
sub?.unsubscribe();
|
|
1729
|
+
clearTimeout(timer);
|
|
1730
|
+
this._discovered.clear();
|
|
1731
|
+
for (const d of devices) this._discovered.set(d.id, d);
|
|
1732
|
+
debugLog(
|
|
1733
|
+
`[DMK] getLiveDevices() resolved count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
|
|
1734
|
+
);
|
|
1735
|
+
resolve(devices);
|
|
1736
|
+
};
|
|
1737
|
+
const timer = setTimeout(() => finish(lastSeen), timeoutMs);
|
|
1738
|
+
sub = this._dmk.listenToAvailableDevices({}).subscribe({
|
|
1739
|
+
next: (devices) => {
|
|
1740
|
+
lastSeen = devices;
|
|
1741
|
+
},
|
|
1742
|
+
error: (err) => {
|
|
1743
|
+
debugError("[DMK] getLiveDevices() error:", err);
|
|
1744
|
+
finish(lastSeen);
|
|
1745
|
+
},
|
|
1746
|
+
complete: () => finish(lastSeen)
|
|
1747
|
+
});
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1109
1750
|
requestDevice() {
|
|
1110
1751
|
if (this._discoverySub) {
|
|
1111
1752
|
return Promise.resolve();
|
|
@@ -1123,11 +1764,33 @@ var LedgerDeviceManager = class {
|
|
|
1123
1764
|
});
|
|
1124
1765
|
return Promise.resolve();
|
|
1125
1766
|
}
|
|
1767
|
+
/** Lookup a previously-discovered device's display name. */
|
|
1768
|
+
getDeviceName(deviceId) {
|
|
1769
|
+
return this._discovered.get(deviceId)?.name;
|
|
1770
|
+
}
|
|
1771
|
+
/** Lookup minimal model/signal info from a previously-discovered device. */
|
|
1772
|
+
getDiscoveredDeviceInfo(deviceId) {
|
|
1773
|
+
const d = this._discovered.get(deviceId);
|
|
1774
|
+
if (!d) return void 0;
|
|
1775
|
+
return {
|
|
1776
|
+
model: d.deviceModel?.model,
|
|
1777
|
+
modelName: d.deviceModel?.name,
|
|
1778
|
+
name: d.name,
|
|
1779
|
+
rssi: d.rssi
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
hasDiscoveredDevice(deviceId) {
|
|
1783
|
+
return this._discovered.has(deviceId);
|
|
1784
|
+
}
|
|
1126
1785
|
/** Connect to a previously discovered device. Returns sessionId. */
|
|
1127
1786
|
async connect(deviceId) {
|
|
1128
1787
|
const device = this._discovered.get(deviceId);
|
|
1129
1788
|
if (!device) {
|
|
1130
|
-
|
|
1789
|
+
const err = new Error(
|
|
1790
|
+
`Device "${deviceId}" not found in discovery cache. Call enumerate() or listen() first.`
|
|
1791
|
+
);
|
|
1792
|
+
err._tag = ERROR_TAG.DeviceNotInDiscoveryCache;
|
|
1793
|
+
throw err;
|
|
1131
1794
|
}
|
|
1132
1795
|
const sessionId = await this._dmk.connect({ device });
|
|
1133
1796
|
this._sessions.set(deviceId, sessionId);
|
|
@@ -1166,6 +1829,19 @@ var LedgerDeviceManager = class {
|
|
|
1166
1829
|
this._sessionToDevice.clear();
|
|
1167
1830
|
this._dmk.close();
|
|
1168
1831
|
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Tear down RxJS subscriptions and local state without closing the DMK.
|
|
1834
|
+
* For light-reset paths that want to drop session state but keep the
|
|
1835
|
+
* underlying transport alive for retry. Call this instead of orphaning
|
|
1836
|
+
* the manager — bare null assignment leaks _listenSub / _discoverySub.
|
|
1837
|
+
*/
|
|
1838
|
+
disposeKeepingDmk() {
|
|
1839
|
+
this.stopListening();
|
|
1840
|
+
this.stopDiscovery();
|
|
1841
|
+
this._discovered.clear();
|
|
1842
|
+
this._sessions.clear();
|
|
1843
|
+
this._sessionToDevice.clear();
|
|
1844
|
+
}
|
|
1169
1845
|
};
|
|
1170
1846
|
|
|
1171
1847
|
// src/signer/SignerManager.ts
|
|
@@ -1177,11 +1853,12 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
|
|
|
1177
1853
|
|
|
1178
1854
|
// src/signer/deviceActionToPromise.ts
|
|
1179
1855
|
var import_device_management_kit = require("@ledgerhq/device-management-kit");
|
|
1180
|
-
var
|
|
1181
|
-
function deviceActionToPromise(action, onInteraction, timeoutMs =
|
|
1856
|
+
var IDLE_WATCHDOG_MS = 65e3;
|
|
1857
|
+
function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller) {
|
|
1182
1858
|
return new Promise((resolve, reject) => {
|
|
1183
1859
|
let settled = false;
|
|
1184
1860
|
let lastStep;
|
|
1861
|
+
const observedSteps = [];
|
|
1185
1862
|
let sub;
|
|
1186
1863
|
let timer = null;
|
|
1187
1864
|
const cancelAction = () => {
|
|
@@ -1194,44 +1871,59 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1194
1871
|
} catch {
|
|
1195
1872
|
}
|
|
1196
1873
|
};
|
|
1197
|
-
const
|
|
1874
|
+
const armIdleWatchdog = () => {
|
|
1198
1875
|
if (timer) clearTimeout(timer);
|
|
1199
1876
|
if (timeoutMs > 0) {
|
|
1200
1877
|
timer = setTimeout(() => {
|
|
1201
1878
|
if (!settled) {
|
|
1202
1879
|
settled = true;
|
|
1203
1880
|
cancelAction();
|
|
1204
|
-
reject(
|
|
1881
|
+
reject(
|
|
1882
|
+
new Error(
|
|
1883
|
+
"Ledger transport unresponsive: no DMK state change within the watchdog window. The device may have lost connection."
|
|
1884
|
+
)
|
|
1885
|
+
);
|
|
1205
1886
|
}
|
|
1206
1887
|
}, timeoutMs);
|
|
1207
1888
|
}
|
|
1208
1889
|
};
|
|
1209
|
-
|
|
1890
|
+
armIdleWatchdog();
|
|
1210
1891
|
if (onRegisterCanceller) {
|
|
1211
|
-
onRegisterCanceller(() => {
|
|
1212
|
-
if (settled)
|
|
1892
|
+
onRegisterCanceller((reason) => {
|
|
1893
|
+
if (settled) {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1213
1896
|
settled = true;
|
|
1214
1897
|
if (timer) clearTimeout(timer);
|
|
1215
1898
|
cancelAction();
|
|
1216
|
-
|
|
1899
|
+
const err = new Error(reason?.message ?? "Device action cancelled");
|
|
1900
|
+
if (reason?.code !== void 0) {
|
|
1901
|
+
err.code = reason.code;
|
|
1902
|
+
}
|
|
1903
|
+
if (reason?.tag) {
|
|
1904
|
+
Object.defineProperty(err, "_tag", {
|
|
1905
|
+
value: reason.tag,
|
|
1906
|
+
configurable: true,
|
|
1907
|
+
enumerable: false,
|
|
1908
|
+
writable: true
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
reject(err);
|
|
1217
1912
|
});
|
|
1218
1913
|
}
|
|
1219
|
-
debugLog("[DMK-Observable] subscribing to action.observable...");
|
|
1220
1914
|
sub = action.observable.subscribe({
|
|
1221
1915
|
next: (state) => {
|
|
1222
|
-
if (settled)
|
|
1223
|
-
|
|
1916
|
+
if (settled) {
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
armIdleWatchdog();
|
|
1224
1920
|
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
|
-
);
|
|
1921
|
+
if (step) {
|
|
1922
|
+
lastStep = step;
|
|
1923
|
+
if (observedSteps[observedSteps.length - 1] !== step) {
|
|
1924
|
+
observedSteps.push(step);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1235
1927
|
if (state.status === import_device_management_kit.DeviceActionStatus.Completed) {
|
|
1236
1928
|
settled = true;
|
|
1237
1929
|
if (timer) clearTimeout(timer);
|
|
@@ -1243,10 +1935,14 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1243
1935
|
if (timer) clearTimeout(timer);
|
|
1244
1936
|
onInteraction?.("interaction-complete");
|
|
1245
1937
|
sub?.unsubscribe();
|
|
1246
|
-
|
|
1938
|
+
rejectWithStepContext(state.error, lastStep, observedSteps, reject);
|
|
1247
1939
|
} else if (state.status === import_device_management_kit.DeviceActionStatus.Pending && onInteraction) {
|
|
1248
1940
|
const interaction = state.intermediateValue?.requiredUserInteraction;
|
|
1249
1941
|
if (interaction && interaction !== "none") {
|
|
1942
|
+
if (interaction === "unlock-device" && timer) {
|
|
1943
|
+
clearTimeout(timer);
|
|
1944
|
+
timer = null;
|
|
1945
|
+
}
|
|
1250
1946
|
onInteraction(String(interaction));
|
|
1251
1947
|
}
|
|
1252
1948
|
}
|
|
@@ -1256,7 +1952,7 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1256
1952
|
settled = true;
|
|
1257
1953
|
if (timer) clearTimeout(timer);
|
|
1258
1954
|
sub?.unsubscribe();
|
|
1259
|
-
|
|
1955
|
+
rejectWithStepContext(err, lastStep, observedSteps, reject);
|
|
1260
1956
|
}
|
|
1261
1957
|
},
|
|
1262
1958
|
complete: () => {
|
|
@@ -1269,15 +1965,25 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1269
1965
|
});
|
|
1270
1966
|
});
|
|
1271
1967
|
}
|
|
1272
|
-
function
|
|
1273
|
-
if (err && typeof err === "object" && lastStep) {
|
|
1968
|
+
function rejectWithStepContext(err, lastStep, observedSteps, reject) {
|
|
1969
|
+
if (err && typeof err === "object" && (lastStep || observedSteps.length > 0)) {
|
|
1274
1970
|
try {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1971
|
+
if (lastStep) {
|
|
1972
|
+
Object.defineProperty(err, "_lastStep", {
|
|
1973
|
+
value: lastStep,
|
|
1974
|
+
configurable: true,
|
|
1975
|
+
enumerable: false,
|
|
1976
|
+
writable: true
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
if (observedSteps.length > 0) {
|
|
1980
|
+
Object.defineProperty(err, "_deviceActionSteps", {
|
|
1981
|
+
value: [...observedSteps],
|
|
1982
|
+
configurable: true,
|
|
1983
|
+
enumerable: false,
|
|
1984
|
+
writable: true
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1281
1987
|
} catch {
|
|
1282
1988
|
}
|
|
1283
1989
|
}
|
|
@@ -1343,7 +2049,8 @@ var SignerManager = class _SignerManager {
|
|
|
1343
2049
|
async getOrCreate(sessionId) {
|
|
1344
2050
|
debugLog("[DMK] SignerManager.getOrCreate:", { sessionId });
|
|
1345
2051
|
const builder = await this._builderFn({ dmk: this._dmk, sessionId });
|
|
1346
|
-
|
|
2052
|
+
const builderWithContext = builder.withContextModule ? builder.withContextModule(_SignerManager._createContextModule()) : builder;
|
|
2053
|
+
return new SignerEth(builderWithContext.build());
|
|
1347
2054
|
}
|
|
1348
2055
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this
|
|
1349
2056
|
invalidate(_sessionId) {
|
|
@@ -1352,10 +2059,24 @@ var SignerManager = class _SignerManager {
|
|
|
1352
2059
|
clearAll() {
|
|
1353
2060
|
}
|
|
1354
2061
|
static _defaultBuilder() {
|
|
1355
|
-
return (args) =>
|
|
1356
|
-
|
|
1357
|
-
|
|
2062
|
+
return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
|
|
2063
|
+
}
|
|
2064
|
+
static _createContextModule() {
|
|
2065
|
+
const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
2066
|
+
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2067
|
+
}
|
|
2068
|
+
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
2069
|
+
const report = contextModule.report.bind(contextModule);
|
|
2070
|
+
contextModule.report = async (params) => {
|
|
2071
|
+
try {
|
|
2072
|
+
void report(params).catch((err) => {
|
|
2073
|
+
debugLog("[DMK] ContextModule.report failed:", err);
|
|
2074
|
+
});
|
|
2075
|
+
} catch (err) {
|
|
2076
|
+
debugLog("[DMK] ContextModule.report failed:", err);
|
|
2077
|
+
}
|
|
1358
2078
|
};
|
|
2079
|
+
return contextModule;
|
|
1359
2080
|
}
|
|
1360
2081
|
};
|
|
1361
2082
|
|
|
@@ -1465,6 +2186,7 @@ async function _getEthSigner(ctx, sessionId) {
|
|
|
1465
2186
|
const signerManager = await ctx.getSignerManager();
|
|
1466
2187
|
const signer = await signerManager.getOrCreate(sessionId);
|
|
1467
2188
|
signer.onInteraction = (interaction) => {
|
|
2189
|
+
debugLog("[LedgerConnector] evm.onInteraction:", interaction);
|
|
1468
2190
|
ctx.emit("ui-event", {
|
|
1469
2191
|
type: collapseSignerInteraction(interaction),
|
|
1470
2192
|
payload: { sessionId }
|
|
@@ -1740,6 +2462,7 @@ async function _createBtcSigner(ctx, sessionId) {
|
|
|
1740
2462
|
const sdkSigner = new SignerBtcBuilder({ dmk, sessionId }).build();
|
|
1741
2463
|
const signer = new SignerBtc(sdkSigner);
|
|
1742
2464
|
signer.onInteraction = (interaction) => {
|
|
2465
|
+
debugLog("[LedgerConnector] btc.onInteraction:", interaction);
|
|
1743
2466
|
ctx.emit("ui-event", {
|
|
1744
2467
|
type: collapseSignerInteraction(interaction),
|
|
1745
2468
|
payload: { sessionId }
|
|
@@ -1910,6 +2633,7 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
1910
2633
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
1911
2634
|
const signer = new SignerSol(sdkSigner);
|
|
1912
2635
|
signer.onInteraction = (interaction) => {
|
|
2636
|
+
debugLog("[LedgerConnector] sol.onInteraction:", interaction);
|
|
1913
2637
|
ctx.emit("ui-event", {
|
|
1914
2638
|
type: collapseSignerInteraction(interaction),
|
|
1915
2639
|
payload: { sessionId }
|
|
@@ -1922,14 +2646,15 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
1922
2646
|
}
|
|
1923
2647
|
|
|
1924
2648
|
// src/connector/chains/tron.ts
|
|
1925
|
-
var
|
|
2649
|
+
var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
|
|
1926
2650
|
var import_hw_app_trx = __toESM(require("@ledgerhq/hw-app-trx"));
|
|
1927
2651
|
|
|
1928
2652
|
// src/connector/chains/legacyChainCall.ts
|
|
1929
|
-
var
|
|
2653
|
+
var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
|
|
1930
2654
|
|
|
1931
2655
|
// src/app/AppManager.ts
|
|
1932
2656
|
var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
|
|
2657
|
+
var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
|
|
1933
2658
|
var APP_NAME_MAP = {
|
|
1934
2659
|
ETH: "Ethereum",
|
|
1935
2660
|
BTC: "Bitcoin",
|
|
@@ -2001,7 +2726,31 @@ var AppManager = class {
|
|
|
2001
2726
|
debugLog("[AppManager] currentApp:", result.data.name);
|
|
2002
2727
|
return result.data.name;
|
|
2003
2728
|
}
|
|
2004
|
-
|
|
2729
|
+
const errResult = result;
|
|
2730
|
+
const dmkErr = errResult.error ?? {};
|
|
2731
|
+
const original = dmkErr.originalError;
|
|
2732
|
+
debugLog(
|
|
2733
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
2734
|
+
sessionId,
|
|
2735
|
+
"tag=",
|
|
2736
|
+
dmkErr._tag,
|
|
2737
|
+
"errorCode=",
|
|
2738
|
+
dmkErr.errorCode,
|
|
2739
|
+
"message=",
|
|
2740
|
+
dmkErr.message,
|
|
2741
|
+
"originalErrorMessage=",
|
|
2742
|
+
original?.message ?? String(original ?? "")
|
|
2743
|
+
);
|
|
2744
|
+
throw Object.assign(
|
|
2745
|
+
new Error(
|
|
2746
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
2747
|
+
),
|
|
2748
|
+
{
|
|
2749
|
+
_tag: dmkErr._tag,
|
|
2750
|
+
errorCode: dmkErr.errorCode,
|
|
2751
|
+
originalError: original
|
|
2752
|
+
}
|
|
2753
|
+
);
|
|
2005
2754
|
}
|
|
2006
2755
|
async _openApp(sessionId, appName) {
|
|
2007
2756
|
const result = await this._dmk.sendCommand({
|
|
@@ -2010,9 +2759,11 @@ var AppManager = class {
|
|
|
2010
2759
|
});
|
|
2011
2760
|
if (!(0, import_device_management_kit2.isSuccessCommandResult)(result)) {
|
|
2012
2761
|
const { statusCode } = result;
|
|
2762
|
+
const hasStatusCode2 = statusCode != null && statusCode !== "";
|
|
2013
2763
|
debugLog("[AppManager] openApp failed:", appName, "statusCode:", statusCode);
|
|
2014
2764
|
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
2015
|
-
_tag:
|
|
2765
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
2766
|
+
code: hasStatusCode2 ? void 0 : import_hwk_adapter_core9.HardwareErrorCode.AppNotInstalled,
|
|
2016
2767
|
errorCode: String(statusCode ?? ""),
|
|
2017
2768
|
statusCode,
|
|
2018
2769
|
appName
|
|
@@ -2068,14 +2819,14 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
|
2068
2819
|
const onAppOpenPrompt = () => {
|
|
2069
2820
|
openAppPromptShown = true;
|
|
2070
2821
|
ctx.emit("ui-event", {
|
|
2071
|
-
type:
|
|
2822
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOpenApp,
|
|
2072
2823
|
payload: { sessionId }
|
|
2073
2824
|
});
|
|
2074
2825
|
};
|
|
2075
2826
|
const closeOpenAppUiIfShown = () => {
|
|
2076
2827
|
if (openAppPromptShown) {
|
|
2077
2828
|
ctx.emit("ui-event", {
|
|
2078
|
-
type:
|
|
2829
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
|
|
2079
2830
|
payload: { sessionId }
|
|
2080
2831
|
});
|
|
2081
2832
|
openAppPromptShown = false;
|
|
@@ -2096,7 +2847,7 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
|
2096
2847
|
let confirmEmitted = false;
|
|
2097
2848
|
if (needsConfirmation) {
|
|
2098
2849
|
ctx.emit("ui-event", {
|
|
2099
|
-
type:
|
|
2850
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
|
|
2100
2851
|
payload: { sessionId }
|
|
2101
2852
|
});
|
|
2102
2853
|
confirmEmitted = true;
|
|
@@ -2106,7 +2857,7 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
|
2106
2857
|
} finally {
|
|
2107
2858
|
if (confirmEmitted || openAppPromptShown) {
|
|
2108
2859
|
ctx.emit("ui-event", {
|
|
2109
|
-
type:
|
|
2860
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
|
|
2110
2861
|
payload: { sessionId }
|
|
2111
2862
|
});
|
|
2112
2863
|
openAppPromptShown = false;
|
|
@@ -2194,7 +2945,7 @@ async function tronSignTransaction(ctx, sessionId, params) {
|
|
|
2194
2945
|
if (!params.rawTxHex) {
|
|
2195
2946
|
throw Object.assign(
|
|
2196
2947
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
2197
|
-
{ code:
|
|
2948
|
+
{ code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
|
|
2198
2949
|
);
|
|
2199
2950
|
}
|
|
2200
2951
|
const path = normalizePath(params.path);
|
|
@@ -2238,6 +2989,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
2238
2989
|
sol: "Solana",
|
|
2239
2990
|
tron: "Tron"
|
|
2240
2991
|
};
|
|
2992
|
+
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
2993
|
+
Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
|
|
2994
|
+
);
|
|
2995
|
+
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
2241
2996
|
async function defaultLedgerKitImporter(pkg) {
|
|
2242
2997
|
switch (pkg) {
|
|
2243
2998
|
case "@ledgerhq/device-management-kit":
|
|
@@ -2261,16 +3016,6 @@ var LedgerConnectorBase = class {
|
|
|
2261
3016
|
this._dmk = null;
|
|
2262
3017
|
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
2263
3018
|
// ---------------------------------------------------------------------------
|
|
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
3019
|
// Per-session DeviceAction cancellers
|
|
2275
3020
|
//
|
|
2276
3021
|
// Each chain handler registers its active DeviceAction's canceller via
|
|
@@ -2279,10 +3024,23 @@ var LedgerConnectorBase = class {
|
|
|
2279
3024
|
// unsubscribes the observable and releases DMK's IntentQueue slot.
|
|
2280
3025
|
// ---------------------------------------------------------------------------
|
|
2281
3026
|
this._cancellers = /* @__PURE__ */ new Map();
|
|
3027
|
+
// ---------------------------------------------------------------------------
|
|
3028
|
+
// Per-session DMK state subscriptions
|
|
3029
|
+
//
|
|
3030
|
+
// DMK only emits `device-disconnect` on the connector when `disconnect()`
|
|
3031
|
+
// is called explicitly. To pick up autonomous disconnects (USB unplug,
|
|
3032
|
+
// BLE drop, device sleep, DMK transport reset) we subscribe to
|
|
3033
|
+
// `_dmk.getDeviceSessionState({sessionId})` per active session and emit
|
|
3034
|
+
// `device-disconnect` ourselves the moment DMK reports the device went
|
|
3035
|
+
// to NOT_CONNECTED. The subscription is torn down on disconnect /
|
|
3036
|
+
// reset / observable completion to avoid leaks.
|
|
3037
|
+
// ---------------------------------------------------------------------------
|
|
3038
|
+
this._sessionStateSubs = /* @__PURE__ */ new Map();
|
|
2282
3039
|
this._createTransport = createTransport;
|
|
2283
3040
|
this.connectionType = options?.connectionType ?? "usb";
|
|
2284
3041
|
this._providedDmk = options?.dmk;
|
|
2285
3042
|
this._importLedgerKit = options?.importLedgerKit ?? defaultLedgerKitImporter;
|
|
3043
|
+
this._requirePreFlightScan = options?.requirePreFlightScan ?? false;
|
|
2286
3044
|
if (this._providedDmk) {
|
|
2287
3045
|
this._initManagers(this._providedDmk);
|
|
2288
3046
|
}
|
|
@@ -2300,38 +3058,23 @@ var LedgerConnectorBase = class {
|
|
|
2300
3058
|
importLedgerKit: this._importLedgerKit
|
|
2301
3059
|
};
|
|
2302
3060
|
}
|
|
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
3061
|
// ---------------------------------------------------------------------------
|
|
2320
3062
|
// Protected — hooks for subclasses
|
|
2321
3063
|
// ---------------------------------------------------------------------------
|
|
2322
3064
|
/**
|
|
2323
3065
|
* Resolve the connectId for a discovered device descriptor.
|
|
2324
3066
|
* Default: use the DMK path (ephemeral UUID).
|
|
2325
|
-
* Override in subclasses
|
|
3067
|
+
* Override in subclasses only when the public connectId differs from the transport path.
|
|
2326
3068
|
*/
|
|
2327
3069
|
_resolveConnectId(descriptor) {
|
|
2328
3070
|
return descriptor.path;
|
|
2329
3071
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
3072
|
+
/**
|
|
3073
|
+
* Authoritative discovery for this transport. Default = enumerate snapshot
|
|
3074
|
+
* (USB/HID). BLE overrides to drive from a live scan window since DMK's
|
|
3075
|
+
* enumerate() cache survives peripherals going offline.
|
|
3076
|
+
*/
|
|
3077
|
+
async _discoverDescriptors(dm) {
|
|
2335
3078
|
let descriptors = await dm.enumerate();
|
|
2336
3079
|
if (descriptors.length === 0) {
|
|
2337
3080
|
try {
|
|
@@ -2340,92 +3083,276 @@ var LedgerConnectorBase = class {
|
|
|
2340
3083
|
}
|
|
2341
3084
|
descriptors = await dm.enumerate();
|
|
2342
3085
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
3086
|
+
return descriptors;
|
|
3087
|
+
}
|
|
3088
|
+
// ---------------------------------------------------------------------------
|
|
3089
|
+
// IConnector -- Device discovery
|
|
3090
|
+
//
|
|
3091
|
+
// Discovery never throws on multiple USB devices — it returns the full list.
|
|
3092
|
+
// The "connect exactly one device" rule is enforced upstream: an explicit
|
|
3093
|
+
// connectId connects that device (or fails as not-found), and only the
|
|
3094
|
+
// no-connectId USB path rejects when more than one device is present
|
|
3095
|
+
// (see LedgerAdapter._connectFirstOrSelect).
|
|
3096
|
+
// ---------------------------------------------------------------------------
|
|
3097
|
+
async searchDevices() {
|
|
3098
|
+
const dm = await this._getDeviceManager();
|
|
3099
|
+
const descriptors = await this._discoverDescriptors(dm);
|
|
3100
|
+
const resolvedDescriptors = descriptors.map((d) => ({
|
|
3101
|
+
descriptor: d,
|
|
3102
|
+
connectId: this._resolveConnectId(d)
|
|
3103
|
+
}));
|
|
3104
|
+
const result = resolvedDescriptors.map(({ descriptor: d, connectId }) => ({
|
|
3105
|
+
connectId,
|
|
3106
|
+
deviceId: d.path,
|
|
3107
|
+
name: d.name || d.type || "Ledger",
|
|
3108
|
+
model: d.type,
|
|
3109
|
+
modelName: d.modelName,
|
|
3110
|
+
rssi: d.rssi,
|
|
3111
|
+
isConnectable: d.isConnectable,
|
|
3112
|
+
serialNumber: d.serialNumber
|
|
3113
|
+
}));
|
|
3114
|
+
debugLog(
|
|
3115
|
+
`[DMK] connector.searchDevices() return count=${result.length} ids=[${result.map((r) => r.connectId).join(",")}] paths=[${result.map((r) => r.deviceId).join(",")}]`
|
|
3116
|
+
);
|
|
2352
3117
|
return result;
|
|
2353
3118
|
}
|
|
2354
3119
|
// ---------------------------------------------------------------------------
|
|
2355
3120
|
// IConnector -- Connection
|
|
2356
3121
|
// ---------------------------------------------------------------------------
|
|
2357
3122
|
async connect(deviceId) {
|
|
2358
|
-
const
|
|
2359
|
-
let
|
|
2360
|
-
const dmkPath = deviceId ? this._getPathForConnectId(deviceId) : void 0;
|
|
2361
|
-
let targetPath = dmkPath;
|
|
3123
|
+
const callerSuppliedConnectId = Boolean(deviceId);
|
|
3124
|
+
let targetPath = deviceId;
|
|
2362
3125
|
if (!targetPath) {
|
|
2363
|
-
const
|
|
2364
|
-
if (
|
|
3126
|
+
const discovered = await this.searchDevices();
|
|
3127
|
+
if (discovered.length === 0) {
|
|
2365
3128
|
throw new Error(
|
|
2366
|
-
`No Ledger device found. Make sure the device is connected${this.connectionType
|
|
3129
|
+
`No Ledger device found. Make sure the device is connected${isLedgerBleConnectionType(this.connectionType) ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
|
|
2367
3130
|
);
|
|
2368
3131
|
}
|
|
2369
|
-
targetPath =
|
|
2370
|
-
}
|
|
2371
|
-
const externalConnectId =
|
|
3132
|
+
targetPath = discovered[0].deviceId;
|
|
3133
|
+
}
|
|
3134
|
+
const externalConnectId = targetPath;
|
|
3135
|
+
const HANG_CEILING_MS = 5 * 6e4;
|
|
3136
|
+
const dmConnectWithObserve = async (dm, path) => {
|
|
3137
|
+
if (!isLedgerBleConnectionType(this.connectionType)) return dm.connect(path);
|
|
3138
|
+
let timeoutId;
|
|
3139
|
+
let timedOut = false;
|
|
3140
|
+
const connectPromise = dm.connect(path);
|
|
3141
|
+
const hangPromise = new Promise((_, reject) => {
|
|
3142
|
+
timeoutId = setTimeout(() => {
|
|
3143
|
+
timedOut = true;
|
|
3144
|
+
const err = new Error(
|
|
3145
|
+
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
3146
|
+
);
|
|
3147
|
+
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
3148
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
3149
|
+
reject(err);
|
|
3150
|
+
}, HANG_CEILING_MS);
|
|
3151
|
+
});
|
|
3152
|
+
try {
|
|
3153
|
+
return await Promise.race([connectPromise, hangPromise]);
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
const e = err;
|
|
3156
|
+
debugLog("[DMK] dm.connect rejected \u2014 observed:", {
|
|
3157
|
+
_tag: e?._tag,
|
|
3158
|
+
message: e?.message,
|
|
3159
|
+
errorCode: e?.errorCode,
|
|
3160
|
+
statusCode: e?.statusCode,
|
|
3161
|
+
originalTag: e?.originalError?._tag,
|
|
3162
|
+
originalMessage: e?.originalError?.message
|
|
3163
|
+
});
|
|
3164
|
+
if (timedOut) {
|
|
3165
|
+
void connectPromise.then(
|
|
3166
|
+
(sessionId) => dm.disconnect(sessionId).catch(() => void 0),
|
|
3167
|
+
() => void 0
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
throw err;
|
|
3171
|
+
} finally {
|
|
3172
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3173
|
+
}
|
|
3174
|
+
};
|
|
3175
|
+
const isBleDirectConnect = isLedgerBleConnectionType(this.connectionType) && callerSuppliedConnectId;
|
|
3176
|
+
const throwNotAdvertising = () => {
|
|
3177
|
+
const err = new Error(
|
|
3178
|
+
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
3179
|
+
);
|
|
3180
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
3181
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
|
|
3182
|
+
throw err;
|
|
3183
|
+
};
|
|
2372
3184
|
const doConnect = async (path) => {
|
|
2373
|
-
const
|
|
3185
|
+
const dm = await this._getDeviceManager();
|
|
3186
|
+
if (isBleDirectConnect && !dm.hasDiscoveredDevice(path)) {
|
|
3187
|
+
const live = await dm.getLiveDevices(BLE_CONNECT_SCAN_TIMEOUT_MS);
|
|
3188
|
+
if (this._requirePreFlightScan && !live.some((d) => d.id === path)) {
|
|
3189
|
+
throwNotAdvertising();
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
let sessionId;
|
|
3193
|
+
try {
|
|
3194
|
+
sessionId = await dmConnectWithObserve(dm, path);
|
|
3195
|
+
} catch (err) {
|
|
3196
|
+
if (isBleDirectConnect && err?._tag === ERROR_TAG.DeviceNotInDiscoveryCache) {
|
|
3197
|
+
throwNotAdvertising();
|
|
3198
|
+
}
|
|
3199
|
+
throw err;
|
|
3200
|
+
}
|
|
3201
|
+
try {
|
|
3202
|
+
this._watchSessionState(sessionId, externalConnectId);
|
|
3203
|
+
} catch (subErr) {
|
|
3204
|
+
debugLog("[DMK] state subscription failed during connect; disconnecting session:", subErr);
|
|
3205
|
+
try {
|
|
3206
|
+
await dm.disconnect(sessionId);
|
|
3207
|
+
} catch {
|
|
3208
|
+
}
|
|
3209
|
+
throw subErr;
|
|
3210
|
+
}
|
|
3211
|
+
const info = dm.getDiscoveredDeviceInfo(path);
|
|
2374
3212
|
const session = {
|
|
2375
3213
|
sessionId,
|
|
2376
3214
|
deviceInfo: {
|
|
2377
3215
|
vendor: "ledger",
|
|
2378
|
-
model: "unknown",
|
|
3216
|
+
model: info?.model ?? "unknown",
|
|
3217
|
+
modelName: info?.modelName,
|
|
2379
3218
|
firmwareVersion: "unknown",
|
|
2380
3219
|
deviceId: path,
|
|
2381
3220
|
connectId: externalConnectId,
|
|
2382
3221
|
connectionType: this.connectionType,
|
|
3222
|
+
rssi: info?.rssi,
|
|
2383
3223
|
capabilities: { persistentDeviceIdentity: false }
|
|
2384
3224
|
}
|
|
2385
3225
|
};
|
|
2386
|
-
const
|
|
3226
|
+
const name = dm.getDeviceName(path) ?? "Ledger";
|
|
2387
3227
|
this._emit("device-connect", {
|
|
2388
|
-
device: {
|
|
2389
|
-
connectId: externalConnectId,
|
|
2390
|
-
deviceId: path,
|
|
2391
|
-
name: realName
|
|
2392
|
-
}
|
|
3228
|
+
device: { connectId: externalConnectId, deviceId: path, name }
|
|
2393
3229
|
});
|
|
2394
3230
|
return session;
|
|
2395
3231
|
};
|
|
2396
3232
|
try {
|
|
2397
3233
|
return await doConnect(targetPath);
|
|
2398
|
-
} catch {
|
|
3234
|
+
} catch (err) {
|
|
2399
3235
|
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
|
-
);
|
|
3236
|
+
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
3237
|
+
const tag = err?._tag;
|
|
3238
|
+
if (isKnownConnectionTag(tag)) {
|
|
3239
|
+
throw err;
|
|
2409
3240
|
}
|
|
2410
|
-
|
|
3241
|
+
const wrapped = new Error(
|
|
3242
|
+
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
3243
|
+
);
|
|
3244
|
+
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
3245
|
+
wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
3246
|
+
wrapped.originalError = err;
|
|
3247
|
+
throw wrapped;
|
|
2411
3248
|
}
|
|
2412
|
-
|
|
3249
|
+
if (err && typeof err === "object" && err._tag) {
|
|
3250
|
+
const taggedError = err instanceof Error ? err : Object.assign(
|
|
3251
|
+
new Error(err.message ?? "Ledger device error"),
|
|
3252
|
+
err
|
|
3253
|
+
);
|
|
3254
|
+
throw taggedError;
|
|
3255
|
+
}
|
|
3256
|
+
throw new Error("Ledger device appears unplugged. Plug in the device and try again.");
|
|
2413
3257
|
}
|
|
2414
3258
|
}
|
|
2415
3259
|
async disconnect(sessionId) {
|
|
2416
3260
|
if (!this._deviceManager) return;
|
|
2417
3261
|
const deviceId = this._deviceManager.getDeviceId(sessionId);
|
|
2418
3262
|
this._signerManager?.invalidate(sessionId);
|
|
3263
|
+
this._unwatchSessionState(sessionId);
|
|
2419
3264
|
await this._deviceManager.disconnect(sessionId);
|
|
2420
3265
|
if (deviceId) {
|
|
2421
3266
|
this._emit("device-disconnect", { connectId: deviceId });
|
|
2422
3267
|
}
|
|
2423
3268
|
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Subscribe to DMK's per-session state observable so that any autonomous
|
|
3271
|
+
* disconnect (USB unplug, BLE drop, device sleep, transport reset) is
|
|
3272
|
+
* surfaced as a `device-disconnect` event — without this, the upstream
|
|
3273
|
+
* `LedgerAdapter._sessions` map would hold a dead session entry until
|
|
3274
|
+
* the next call hit `DeviceSessionNotFound`.
|
|
3275
|
+
*
|
|
3276
|
+
* Subscribe failure is fatal — see the inline note at the subscribe call.
|
|
3277
|
+
*/
|
|
3278
|
+
_watchSessionState(sessionId, externalConnectId) {
|
|
3279
|
+
const dmk = this._dmk;
|
|
3280
|
+
if (!dmk) return;
|
|
3281
|
+
const previous = this._sessionStateSubs.get(sessionId);
|
|
3282
|
+
if (previous) {
|
|
3283
|
+
try {
|
|
3284
|
+
previous.unsubscribe();
|
|
3285
|
+
} catch {
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
|
|
3289
|
+
next: (state) => {
|
|
3290
|
+
if (state?.deviceStatus === "NOT CONNECTED") {
|
|
3291
|
+
this._handleAutonomousDisconnect(sessionId, externalConnectId);
|
|
3292
|
+
}
|
|
3293
|
+
},
|
|
3294
|
+
error: () => {
|
|
3295
|
+
this._handleAutonomousDisconnect(sessionId, externalConnectId);
|
|
3296
|
+
},
|
|
3297
|
+
complete: () => {
|
|
3298
|
+
this._handleAutonomousDisconnect(sessionId, externalConnectId);
|
|
3299
|
+
}
|
|
3300
|
+
});
|
|
3301
|
+
this._sessionStateSubs.set(sessionId, sub);
|
|
3302
|
+
}
|
|
3303
|
+
_unwatchSessionState(sessionId) {
|
|
3304
|
+
const sub = this._sessionStateSubs.get(sessionId);
|
|
3305
|
+
if (!sub) return;
|
|
3306
|
+
try {
|
|
3307
|
+
sub.unsubscribe();
|
|
3308
|
+
} catch {
|
|
3309
|
+
}
|
|
3310
|
+
this._sessionStateSubs.delete(sessionId);
|
|
3311
|
+
}
|
|
3312
|
+
_handleAutonomousDisconnect(sessionId, externalConnectId) {
|
|
3313
|
+
if (!this._sessionStateSubs.has(sessionId)) return;
|
|
3314
|
+
debugLog(
|
|
3315
|
+
"[DMK] autonomous disconnect detected \u2014 sessionId:",
|
|
3316
|
+
sessionId,
|
|
3317
|
+
"connectId:",
|
|
3318
|
+
externalConnectId
|
|
3319
|
+
);
|
|
3320
|
+
this._unwatchSessionState(sessionId);
|
|
3321
|
+
this._signerManager?.invalidate(sessionId);
|
|
3322
|
+
this._cancellers.get(sessionId)?.({
|
|
3323
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
|
|
3324
|
+
tag: "DeviceDisconnected",
|
|
3325
|
+
message: "Device disconnected"
|
|
3326
|
+
});
|
|
3327
|
+
this._cancellers.delete(sessionId);
|
|
3328
|
+
this._emit("device-disconnect", { connectId: externalConnectId });
|
|
3329
|
+
}
|
|
2424
3330
|
// ---------------------------------------------------------------------------
|
|
2425
3331
|
// IConnector -- Method dispatch
|
|
2426
3332
|
// ---------------------------------------------------------------------------
|
|
2427
3333
|
async call(sessionId, method, params) {
|
|
2428
3334
|
debugLog("[DMK] call:", method, JSON.stringify(params));
|
|
3335
|
+
try {
|
|
3336
|
+
return await this._dispatch(sessionId, method, params);
|
|
3337
|
+
} catch (err) {
|
|
3338
|
+
if (isAppStuckByApdu(err)) {
|
|
3339
|
+
throw Object.assign(new Error("Ledger app is unresponsive"), {
|
|
3340
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
|
|
3341
|
+
_tag: ERROR_TAG.DeviceAppStuck,
|
|
3342
|
+
originalError: err
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
if (isTransportStuck(err)) {
|
|
3346
|
+
throw Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
3347
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
|
|
3348
|
+
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
3349
|
+
originalError: err
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
throw err;
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
async _dispatch(sessionId, method, params) {
|
|
2429
3356
|
const ctx = this._ctxForMethod(method);
|
|
2430
3357
|
switch (method) {
|
|
2431
3358
|
// EVM
|
|
@@ -2565,14 +3492,13 @@ var LedgerConnectorBase = class {
|
|
|
2565
3492
|
}
|
|
2566
3493
|
/**
|
|
2567
3494
|
* Replace an old session with a new one after app switch.
|
|
2568
|
-
*
|
|
2569
|
-
* and emits device-connect so the adapter updates its _sessions Map.
|
|
3495
|
+
* Emits device-connect so the adapter updates its _sessions Map.
|
|
2570
3496
|
*/
|
|
2571
3497
|
_replaceSession(oldSessionId, _newSessionId) {
|
|
2572
3498
|
const dm = this._deviceManager;
|
|
2573
3499
|
if (!dm) return;
|
|
2574
3500
|
const oldDeviceId = dm.getDeviceId(oldSessionId);
|
|
2575
|
-
const connectId = oldDeviceId
|
|
3501
|
+
const connectId = oldDeviceId;
|
|
2576
3502
|
this._signerManager?.invalidate(oldSessionId);
|
|
2577
3503
|
if (connectId) {
|
|
2578
3504
|
this._emit("device-connect", {
|
|
@@ -2585,13 +3511,18 @@ var LedgerConnectorBase = class {
|
|
|
2585
3511
|
}
|
|
2586
3512
|
}
|
|
2587
3513
|
/**
|
|
2588
|
-
* Light reset: clear signer/session state but keep DMK
|
|
3514
|
+
* Light reset: clear signer/session state but keep DMK alive.
|
|
2589
3515
|
* Used by connect() retry — we want to re-discover with the same transport.
|
|
3516
|
+
*
|
|
3517
|
+
* Note: drops the device manager but tears down its RxJS subs first via
|
|
3518
|
+
* disposeKeepingDmk(). Bare null-assignment would leak _listenSub /
|
|
3519
|
+
* _discoverySub and double-scan after the next _initManagers().
|
|
2590
3520
|
*/
|
|
2591
3521
|
_resetSignersAndSessions() {
|
|
2592
3522
|
debugLog("[DMK] _resetSignersAndSessions called");
|
|
2593
3523
|
this._signerManager?.clearAll();
|
|
2594
3524
|
this._signerManager = null;
|
|
3525
|
+
this._deviceManager?.disposeKeepingDmk();
|
|
2595
3526
|
this._deviceManager = null;
|
|
2596
3527
|
}
|
|
2597
3528
|
_resetAll() {
|
|
@@ -2603,13 +3534,18 @@ var LedgerConnectorBase = class {
|
|
|
2603
3534
|
}
|
|
2604
3535
|
}
|
|
2605
3536
|
this._cancellers.clear();
|
|
3537
|
+
for (const sub of this._sessionStateSubs.values()) {
|
|
3538
|
+
try {
|
|
3539
|
+
sub.unsubscribe();
|
|
3540
|
+
} catch {
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
this._sessionStateSubs.clear();
|
|
2606
3544
|
this._signerManager?.clearAll();
|
|
2607
3545
|
this._deviceManager?.dispose();
|
|
2608
3546
|
this._deviceManager = null;
|
|
2609
3547
|
this._signerManager = null;
|
|
2610
3548
|
this._dmk = null;
|
|
2611
|
-
this._connectIdToPath.clear();
|
|
2612
|
-
this._pathToConnectId.clear();
|
|
2613
3549
|
}
|
|
2614
3550
|
// ---------------------------------------------------------------------------
|
|
2615
3551
|
// Private -- Events
|
|
@@ -2643,15 +3579,21 @@ var LedgerConnectorBase = class {
|
|
|
2643
3579
|
};
|
|
2644
3580
|
}
|
|
2645
3581
|
_wrapError(err, opts) {
|
|
2646
|
-
const mapped = mapLedgerError(err, opts);
|
|
2647
|
-
const error = new Error(mapped.message);
|
|
2648
3582
|
const src = err && typeof err === "object" ? err : {};
|
|
3583
|
+
const hasSerializedCode = typeof src.code === "number" && HARDWARE_ERROR_CODE_VALUES.has(src.code);
|
|
3584
|
+
const mapped = hasSerializedCode ? {
|
|
3585
|
+
code: src.code,
|
|
3586
|
+
message: typeof src.message === "string" ? src.message : "Unknown Ledger error",
|
|
3587
|
+
appName: typeof src.appName === "string" ? src.appName : opts?.defaultAppName
|
|
3588
|
+
} : mapLedgerError(err, opts);
|
|
3589
|
+
const error = new Error(mapped.message);
|
|
2649
3590
|
Object.assign(error, {
|
|
2650
3591
|
code: mapped.code,
|
|
2651
3592
|
appName: mapped.appName,
|
|
2652
3593
|
_tag: src._tag,
|
|
2653
3594
|
errorCode: src.errorCode,
|
|
2654
|
-
_lastStep: src._lastStep
|
|
3595
|
+
_lastStep: src._lastStep,
|
|
3596
|
+
_deviceActionSteps: src._deviceActionSteps
|
|
2655
3597
|
});
|
|
2656
3598
|
Object.defineProperty(error, "originalError", {
|
|
2657
3599
|
value: err,
|
|
@@ -2662,13 +3604,6 @@ var LedgerConnectorBase = class {
|
|
|
2662
3604
|
return error;
|
|
2663
3605
|
}
|
|
2664
3606
|
};
|
|
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
3607
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2673
3608
|
0 && (module.exports = {
|
|
2674
3609
|
AppManager,
|
|
@@ -2680,12 +3615,15 @@ function extractBleHexId(name) {
|
|
|
2680
3615
|
SignerEth,
|
|
2681
3616
|
SignerManager,
|
|
2682
3617
|
SignerSol,
|
|
3618
|
+
debugLog,
|
|
2683
3619
|
deviceActionToPromise,
|
|
2684
|
-
extractBleHexId,
|
|
2685
|
-
isDebugEnabled,
|
|
2686
3620
|
isDeviceLockedError,
|
|
3621
|
+
isLedgerBleConnectionType,
|
|
3622
|
+
isLedgerBleDescriptor,
|
|
3623
|
+
isLedgerDmkBleTransport,
|
|
2687
3624
|
ledgerFailure,
|
|
2688
3625
|
mapLedgerError,
|
|
2689
|
-
|
|
3626
|
+
offSdkEvent,
|
|
3627
|
+
onSdkEvent
|
|
2690
3628
|
});
|
|
2691
3629
|
//# sourceMappingURL=index.js.map
|