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