@onekeyfe/hwk-ledger-adapter 1.1.26-alpha.8 → 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 +274 -170
- package/dist/index.d.ts +274 -170
- package/dist/index.js +1534 -568
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1535 -570
- 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,7 +365,22 @@ function isTimeoutError(err) {
|
|
|
219
365
|
if (e.code === import_hwk_adapter_core.HardwareErrorCode.OperationTimeout) return true;
|
|
220
366
|
return false;
|
|
221
367
|
}
|
|
222
|
-
function
|
|
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
|
+
}
|
|
383
|
+
function mapLedgerError(err, opts) {
|
|
223
384
|
let originalMessage = "Unknown Ledger error";
|
|
224
385
|
if (err instanceof Error) {
|
|
225
386
|
originalMessage = err.message;
|
|
@@ -230,60 +391,127 @@ function mapLedgerError(err) {
|
|
|
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;
|
|
250
415
|
}
|
|
251
|
-
const
|
|
416
|
+
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
417
|
+
const appName = errAppName ?? opts?.defaultAppName;
|
|
252
418
|
return { code, message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage), appName };
|
|
253
419
|
}
|
|
254
420
|
|
|
255
|
-
// src/utils/
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
421
|
+
// src/utils/ledgerDmkTransport.ts
|
|
422
|
+
function isLedgerDmkBleTransport(transport) {
|
|
423
|
+
const normalized = transport?.toUpperCase();
|
|
424
|
+
return normalized === "BLE" || normalized?.endsWith("_BLE") === true;
|
|
259
425
|
}
|
|
260
|
-
function
|
|
261
|
-
return
|
|
426
|
+
function isLedgerBleConnectionType(connectionType) {
|
|
427
|
+
return connectionType === "ble";
|
|
262
428
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
|
|
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
|
+
}
|
|
266
451
|
}
|
|
267
452
|
}
|
|
268
|
-
function
|
|
269
|
-
if (
|
|
270
|
-
|
|
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);
|
|
271
467
|
}
|
|
272
468
|
}
|
|
273
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
|
+
|
|
274
478
|
// src/adapter/LedgerAdapter.ts
|
|
275
479
|
function formatDeviceMismatchError(expected, actual) {
|
|
276
480
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
277
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
|
+
}
|
|
278
490
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
279
491
|
constructor(connector, options) {
|
|
280
492
|
this.vendor = "ledger";
|
|
281
493
|
this.emitter = new import_hwk_adapter_core2.TypedEventEmitter();
|
|
282
|
-
// Device cache: tracks discovered devices from connector events
|
|
283
494
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
284
|
-
// Session tracking: maps connectId -> sessionId
|
|
285
495
|
this._sessions = /* @__PURE__ */ new Map();
|
|
286
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
|
+
*/
|
|
287
515
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
288
516
|
this._connectingPromise = null;
|
|
289
517
|
// ---------------------------------------------------------------------------
|
|
@@ -306,47 +534,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
306
534
|
payload: { connectId: data.connectId }
|
|
307
535
|
});
|
|
308
536
|
};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
this.
|
|
313
|
-
this.
|
|
537
|
+
// Forward low-level connector 'ui-event' (the four EConnectorInteraction values)
|
|
538
|
+
// to the public hw.emitter so consumers only need to subscribe in one place
|
|
539
|
+
// (hw.on instead of also reaching into connector.on).
|
|
540
|
+
this.uiEventForwarder = (event) => {
|
|
541
|
+
this.emitter.emit("ui-event", event);
|
|
314
542
|
};
|
|
315
543
|
this.connector = connector;
|
|
316
544
|
this._handleSelectDevice = options?.handleSelectDevice ?? false;
|
|
317
|
-
this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue(
|
|
318
|
-
emit: (event, data) => this.emitter.emit(event, data),
|
|
319
|
-
uiRegistry: this._uiRegistry
|
|
320
|
-
});
|
|
545
|
+
this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue();
|
|
321
546
|
this.registerEventListeners();
|
|
322
547
|
}
|
|
323
|
-
/**
|
|
324
|
-
* Classify a method's interruptibility.
|
|
325
|
-
* - Signing / typed data / transaction → 'confirm' (user may decide via preemption UI)
|
|
326
|
-
* - Read-only queries (getAddress / getPublicKey / getMasterFingerprint) → 'safe'
|
|
327
|
-
* (auto-cancels any pending read for the same device)
|
|
328
|
-
*/
|
|
329
|
-
static _getInterruptibility(method) {
|
|
330
|
-
if (method.toLowerCase().includes("sign")) return "confirm";
|
|
331
|
-
return "safe";
|
|
332
|
-
}
|
|
333
|
-
// ---------------------------------------------------------------------------
|
|
334
548
|
// Transport
|
|
335
|
-
// ---------------------------------------------------------------------------
|
|
336
|
-
// Transport is decided at connector creation time. These methods
|
|
337
|
-
// satisfy the IHardwareWallet interface with sensible defaults.
|
|
338
549
|
get activeTransport() {
|
|
339
|
-
return this.connector.connectionType
|
|
550
|
+
return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
|
|
340
551
|
}
|
|
341
552
|
getAvailableTransports() {
|
|
342
553
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
343
554
|
}
|
|
344
|
-
|
|
555
|
+
// Connector is bound at construction; switching requires a new adapter.
|
|
556
|
+
switchTransport(_type) {
|
|
557
|
+
return Promise.resolve();
|
|
345
558
|
}
|
|
346
|
-
// ---------------------------------------------------------------------------
|
|
347
559
|
// Lifecycle
|
|
348
|
-
|
|
349
|
-
|
|
560
|
+
init(_config) {
|
|
561
|
+
return Promise.resolve();
|
|
350
562
|
}
|
|
351
563
|
/**
|
|
352
564
|
* Clear cached device/session state without tearing down the adapter.
|
|
@@ -359,6 +571,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
359
571
|
this._connectingPromise = null;
|
|
360
572
|
this._uiRegistry.reset();
|
|
361
573
|
this._jobQueue.clear();
|
|
574
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
362
575
|
}
|
|
363
576
|
async dispose() {
|
|
364
577
|
this._uiRegistry.reset();
|
|
@@ -367,6 +580,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
367
580
|
this.connector.reset();
|
|
368
581
|
this._discoveredDevices.clear();
|
|
369
582
|
this._sessions.clear();
|
|
583
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
370
584
|
this.emitter.removeAllListeners();
|
|
371
585
|
}
|
|
372
586
|
uiResponse(response) {
|
|
@@ -375,10 +589,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
375
589
|
// ---------------------------------------------------------------------------
|
|
376
590
|
// Device management
|
|
377
591
|
// ---------------------------------------------------------------------------
|
|
378
|
-
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
|
+
}
|
|
379
600
|
await this._ensureDevicePermission();
|
|
380
601
|
const devices = await this.connector.searchDevices();
|
|
381
|
-
|
|
602
|
+
this._discoveredDevices.clear();
|
|
382
603
|
for (const d of devices) {
|
|
383
604
|
if (d.connectId) {
|
|
384
605
|
this._discoveredDevices.set(d.connectId, this.connectorDeviceToDeviceInfo(d));
|
|
@@ -387,11 +608,26 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
387
608
|
if (this._discoveredDevices.size === 0) {
|
|
388
609
|
await this._ensureDevicePermission();
|
|
389
610
|
}
|
|
611
|
+
debugLog(
|
|
612
|
+
`[LedgerAdapter] searchDevices() return count=${this._discoveredDevices.size} ids=[${[
|
|
613
|
+
...this._discoveredDevices.keys()
|
|
614
|
+
].join(",")}]`
|
|
615
|
+
);
|
|
390
616
|
return Array.from(this._discoveredDevices.values());
|
|
391
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
|
+
}
|
|
392
623
|
async connectDevice(connectId) {
|
|
393
|
-
await this._ensureDevicePermission(connectId);
|
|
394
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);
|
|
395
631
|
const session = await this.connector.connect(connectId);
|
|
396
632
|
this._sessions.set(connectId, session.sessionId);
|
|
397
633
|
if (session.deviceInfo) {
|
|
@@ -427,7 +663,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
427
663
|
// Chain call helper
|
|
428
664
|
// ---------------------------------------------------------------------------
|
|
429
665
|
async callChain(connectId, deviceId, chain, method, params, skipFingerprint = false) {
|
|
430
|
-
await this._ensureDevicePermission(connectId, deviceId);
|
|
431
666
|
try {
|
|
432
667
|
const result = await this.connectorCall(connectId, method, params, {
|
|
433
668
|
chain,
|
|
@@ -439,45 +674,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
439
674
|
return this.errorToFailure(err);
|
|
440
675
|
}
|
|
441
676
|
}
|
|
442
|
-
/**
|
|
443
|
-
* Batch version of callChain — checks permission once,
|
|
444
|
-
* fingerprint is verified on the first call inside connectorCall.
|
|
445
|
-
*/
|
|
446
|
-
async callChainBatch(connectId, deviceId, chain, method, params, onProgress, skipFingerprint = false) {
|
|
447
|
-
await this._ensureDevicePermission(connectId, deviceId);
|
|
448
|
-
const results = [];
|
|
449
|
-
for (let i = 0; i < params.length; i++) {
|
|
450
|
-
try {
|
|
451
|
-
const result = await this.connectorCall(connectId, method, params[i], {
|
|
452
|
-
chain,
|
|
453
|
-
deviceId,
|
|
454
|
-
// Only verify fingerprint on the first call in the batch
|
|
455
|
-
skipFingerprint: skipFingerprint || i > 0
|
|
456
|
-
});
|
|
457
|
-
results.push(result);
|
|
458
|
-
onProgress?.({ index: i, total: params.length });
|
|
459
|
-
} catch (err) {
|
|
460
|
-
return this.errorToFailure(err);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return (0, import_hwk_adapter_core2.success)(results);
|
|
464
|
-
}
|
|
465
677
|
// ---------------------------------------------------------------------------
|
|
466
678
|
// EVM chain methods
|
|
467
679
|
// ---------------------------------------------------------------------------
|
|
468
680
|
evmGetAddress(connectId, deviceId, params) {
|
|
469
681
|
return this.callChain(connectId, deviceId, "evm", "evmGetAddress", params);
|
|
470
682
|
}
|
|
471
|
-
evmGetAddresses(connectId, deviceId, params, onProgress) {
|
|
472
|
-
return this.callChainBatch(
|
|
473
|
-
connectId,
|
|
474
|
-
deviceId,
|
|
475
|
-
"evm",
|
|
476
|
-
"evmGetAddress",
|
|
477
|
-
params,
|
|
478
|
-
onProgress
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
683
|
evmSignTransaction(connectId, deviceId, params) {
|
|
482
684
|
return this.callChain(connectId, deviceId, "evm", "evmSignTransaction", params);
|
|
483
685
|
}
|
|
@@ -493,16 +695,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
493
695
|
btcGetAddress(connectId, deviceId, params) {
|
|
494
696
|
return this.callChain(connectId, deviceId, "btc", "btcGetAddress", params);
|
|
495
697
|
}
|
|
496
|
-
btcGetAddresses(connectId, deviceId, params, onProgress) {
|
|
497
|
-
return this.callChainBatch(
|
|
498
|
-
connectId,
|
|
499
|
-
deviceId,
|
|
500
|
-
"btc",
|
|
501
|
-
"btcGetAddress",
|
|
502
|
-
params,
|
|
503
|
-
onProgress
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
698
|
btcGetPublicKey(connectId, deviceId, params) {
|
|
507
699
|
return this.callChain(connectId, deviceId, "btc", "btcGetPublicKey", params);
|
|
508
700
|
}
|
|
@@ -530,16 +722,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
530
722
|
solGetAddress(connectId, deviceId, params) {
|
|
531
723
|
return this.callChain(connectId, deviceId, "sol", "solGetAddress", params);
|
|
532
724
|
}
|
|
533
|
-
solGetAddresses(connectId, deviceId, params, onProgress) {
|
|
534
|
-
return this.callChainBatch(
|
|
535
|
-
connectId,
|
|
536
|
-
deviceId,
|
|
537
|
-
"sol",
|
|
538
|
-
"solGetAddress",
|
|
539
|
-
params,
|
|
540
|
-
onProgress
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
725
|
solSignTransaction(connectId, deviceId, params) {
|
|
544
726
|
return this.callChain(connectId, deviceId, "sol", "solSignTransaction", params);
|
|
545
727
|
}
|
|
@@ -550,38 +732,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
550
732
|
// TRON chain methods
|
|
551
733
|
// ---------------------------------------------------------------------------
|
|
552
734
|
tronGetAddress(connectId, deviceId, params) {
|
|
553
|
-
return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params
|
|
554
|
-
}
|
|
555
|
-
tronGetAddresses(connectId, deviceId, params, onProgress) {
|
|
556
|
-
return this.callChainBatch(
|
|
557
|
-
connectId,
|
|
558
|
-
deviceId,
|
|
559
|
-
"tron",
|
|
560
|
-
"tronGetAddress",
|
|
561
|
-
params,
|
|
562
|
-
onProgress,
|
|
563
|
-
true
|
|
564
|
-
);
|
|
735
|
+
return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params);
|
|
565
736
|
}
|
|
566
737
|
tronSignTransaction(connectId, deviceId, params) {
|
|
567
|
-
return this.callChain(
|
|
568
|
-
connectId,
|
|
569
|
-
deviceId,
|
|
570
|
-
"tron",
|
|
571
|
-
"tronSignTransaction",
|
|
572
|
-
params,
|
|
573
|
-
true
|
|
574
|
-
);
|
|
738
|
+
return this.callChain(connectId, deviceId, "tron", "tronSignTransaction", params);
|
|
575
739
|
}
|
|
576
740
|
tronSignMessage(connectId, deviceId, params) {
|
|
577
|
-
return this.callChain(
|
|
578
|
-
connectId,
|
|
579
|
-
deviceId,
|
|
580
|
-
"tron",
|
|
581
|
-
"tronSignMessage",
|
|
582
|
-
params,
|
|
583
|
-
true
|
|
584
|
-
);
|
|
741
|
+
return this.callChain(connectId, deviceId, "tron", "tronSignMessage", params);
|
|
585
742
|
}
|
|
586
743
|
on(event, listener) {
|
|
587
744
|
this.emitter.on(event, listener);
|
|
@@ -590,30 +747,41 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
590
747
|
this.emitter.off(event, listener);
|
|
591
748
|
}
|
|
592
749
|
cancel(connectId) {
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
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
|
+
}
|
|
596
775
|
}
|
|
597
776
|
// ---------------------------------------------------------------------------
|
|
598
777
|
// Chain fingerprint
|
|
599
778
|
// ---------------------------------------------------------------------------
|
|
600
779
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
601
|
-
debugLog(
|
|
602
|
-
"[LedgerAdapter] getChainFingerprint called, chain:",
|
|
603
|
-
chain,
|
|
604
|
-
"connectId:",
|
|
605
|
-
connectId || "(empty)",
|
|
606
|
-
"sessions:",
|
|
607
|
-
this._sessions.size
|
|
608
|
-
);
|
|
609
|
-
await this._ensureDevicePermission(connectId, deviceId);
|
|
610
|
-
debugLog("[LedgerAdapter] getChainFingerprint permission ok, computing fingerprint");
|
|
611
780
|
try {
|
|
612
781
|
const fingerprint = await this._computeChainFingerprint(
|
|
613
782
|
chain,
|
|
614
|
-
(method, params) => this.connectorCall(connectId, method, params)
|
|
783
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
615
784
|
);
|
|
616
|
-
debugLog("[LedgerAdapter] getChainFingerprint result:", fingerprint?.substring(0, 20));
|
|
617
785
|
return (0, import_hwk_adapter_core2.success)(fingerprint);
|
|
618
786
|
} catch (err) {
|
|
619
787
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
@@ -684,77 +852,226 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
684
852
|
if (signal?.aborted) {
|
|
685
853
|
_LedgerAdapter._throwIfAborted(signal);
|
|
686
854
|
}
|
|
855
|
+
const waitPromise = this._uiRegistry.wait(
|
|
856
|
+
import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
857
|
+
);
|
|
687
858
|
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
688
859
|
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
689
860
|
payload: {
|
|
861
|
+
vendor: "ledger",
|
|
862
|
+
reason: "device-not-found",
|
|
690
863
|
message: "Please connect and unlock your Ledger device"
|
|
691
864
|
}
|
|
692
865
|
});
|
|
693
|
-
const waitPromise = this._uiRegistry.wait(
|
|
694
|
-
import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
695
|
-
);
|
|
696
866
|
let payload;
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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 {
|
|
703
879
|
payload = await waitPromise;
|
|
704
|
-
} finally {
|
|
705
|
-
signal.removeEventListener("abort", onAbort);
|
|
706
880
|
}
|
|
707
|
-
}
|
|
708
|
-
|
|
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
|
+
});
|
|
709
891
|
}
|
|
710
892
|
if (!payload?.confirmed) {
|
|
711
893
|
throw Object.assign(new Error("User cancelled Ledger connection"), {
|
|
712
|
-
_tag:
|
|
894
|
+
_tag: ERROR_TAG.UserAborted,
|
|
895
|
+
code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
|
|
713
896
|
});
|
|
714
897
|
}
|
|
898
|
+
await new Promise((resolve) => {
|
|
899
|
+
setTimeout(resolve, 800);
|
|
900
|
+
});
|
|
715
901
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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;
|
|
723
914
|
}
|
|
724
|
-
if (this.
|
|
725
|
-
return
|
|
915
|
+
if (this._btcHighIndexConfirmedThisSession) {
|
|
916
|
+
return { ...params, showOnDevice: true };
|
|
726
917
|
}
|
|
727
|
-
|
|
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
|
+
});
|
|
728
935
|
try {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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;
|
|
732
944
|
}
|
|
733
945
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
+
}
|
|
737
1010
|
if (devices.length > 0) {
|
|
738
|
-
|
|
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
|
+
}
|
|
739
1025
|
}
|
|
740
|
-
if (
|
|
741
|
-
|
|
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
|
+
);
|
|
742
1033
|
}
|
|
1034
|
+
await this._waitForDeviceConnect(internalSignal);
|
|
1035
|
+
confirms += 1;
|
|
743
1036
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
"No Ledger device found after multiple attempts. Please connect and unlock your device."
|
|
747
|
-
),
|
|
748
|
-
{ _tag: "DeviceNotRecognizedError" }
|
|
749
|
-
);
|
|
1037
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1038
|
+
throw new Error("_doConnect aborted");
|
|
750
1039
|
}
|
|
751
|
-
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
|
+
}
|
|
752
1061
|
const chosenConnectId = devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices);
|
|
1062
|
+
return this._connectDeviceOrThrow(chosenConnectId);
|
|
1063
|
+
}
|
|
1064
|
+
async _connectDeviceOrThrow(chosenConnectId) {
|
|
753
1065
|
const result = await this.connectDevice(chosenConnectId);
|
|
754
1066
|
if (!result.success) {
|
|
755
|
-
|
|
756
|
-
|
|
1067
|
+
const payload = result.payload;
|
|
1068
|
+
const rethrow = Object.assign(new Error(payload.error), {
|
|
1069
|
+
code: payload.code
|
|
757
1070
|
});
|
|
1071
|
+
if (payload._tag !== void 0) {
|
|
1072
|
+
rethrow._tag = payload._tag;
|
|
1073
|
+
}
|
|
1074
|
+
throw rethrow;
|
|
758
1075
|
}
|
|
759
1076
|
return chosenConnectId;
|
|
760
1077
|
}
|
|
@@ -765,18 +1082,35 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
765
1082
|
);
|
|
766
1083
|
return devices[0].connectId;
|
|
767
1084
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE
|
|
774
|
-
|
|
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
|
+
}
|
|
775
1109
|
const chosen = devices.find((d) => d.connectId === response?.sdkConnectId);
|
|
776
1110
|
if (!chosen) {
|
|
777
1111
|
throw Object.assign(
|
|
778
1112
|
new Error(`Selected sdkConnectId '${response?.sdkConnectId}' not in discovered list`),
|
|
779
|
-
{ _tag:
|
|
1113
|
+
{ _tag: ERROR_TAG.DeviceNotRecognized }
|
|
780
1114
|
);
|
|
781
1115
|
}
|
|
782
1116
|
return chosen.connectId;
|
|
@@ -789,30 +1123,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
789
1123
|
* 3. Calls connector.call()
|
|
790
1124
|
* 4. On disconnect error: clears stale session, re-connects, retries once
|
|
791
1125
|
*/
|
|
792
|
-
async connectorCall(connectId, method, params, fingerprint) {
|
|
1126
|
+
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId) {
|
|
793
1127
|
debugLog("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
|
|
794
1128
|
const queueKey = connectId || "__ledger_default__";
|
|
795
|
-
const interruptibility = _LedgerAdapter._getInterruptibility(method);
|
|
796
1129
|
return this._jobQueue.enqueue(
|
|
797
1130
|
queueKey,
|
|
798
|
-
async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint),
|
|
799
|
-
{
|
|
1131
|
+
async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId),
|
|
1132
|
+
{
|
|
1133
|
+
label: method,
|
|
1134
|
+
rejectIfBusy: true,
|
|
1135
|
+
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
1136
|
+
}
|
|
800
1137
|
);
|
|
801
1138
|
}
|
|
802
1139
|
/**
|
|
803
|
-
* Race a promise against an abort signal.
|
|
804
|
-
* signal
|
|
805
|
-
* 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').
|
|
806
1142
|
*/
|
|
807
|
-
|
|
1143
|
+
_abortable(signal, promise) {
|
|
1144
|
+
const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
808
1145
|
if (signal.aborted) {
|
|
809
|
-
return Promise.reject(
|
|
810
|
-
signal.reason ?? new Error("Aborted")
|
|
811
|
-
);
|
|
1146
|
+
return Promise.reject(getAbortReason());
|
|
812
1147
|
}
|
|
813
1148
|
return new Promise((resolve, reject) => {
|
|
814
1149
|
const onAbort = () => {
|
|
815
|
-
reject(
|
|
1150
|
+
reject(getAbortReason());
|
|
816
1151
|
};
|
|
817
1152
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
818
1153
|
promise.then(
|
|
@@ -834,40 +1169,49 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
834
1169
|
}
|
|
835
1170
|
}
|
|
836
1171
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
837
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint) {
|
|
1172
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId) {
|
|
838
1173
|
_LedgerAdapter._throwIfAborted(signal);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1174
|
+
await this._ensureDevicePermission(
|
|
1175
|
+
connectId,
|
|
1176
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
1177
|
+
signal
|
|
842
1178
|
);
|
|
843
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);
|
|
844
1192
|
const sessionId = this._sessions.get(resolvedConnectId);
|
|
845
|
-
debugLog(
|
|
846
|
-
"[LedgerAdapter] connectorCall resolved:",
|
|
847
|
-
method,
|
|
848
|
-
"resolvedConnectId:",
|
|
849
|
-
resolvedConnectId,
|
|
850
|
-
"sessionId:",
|
|
851
|
-
sessionId
|
|
852
|
-
);
|
|
853
1193
|
if (!sessionId) {
|
|
854
1194
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
855
|
-
_tag:
|
|
1195
|
+
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
856
1196
|
});
|
|
857
1197
|
}
|
|
858
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
859
|
-
const fp = await _LedgerAdapter._abortable(
|
|
860
|
-
signal,
|
|
861
|
-
this._verifyDeviceFingerprintWithSession(sessionId, fingerprint.deviceId, fingerprint.chain)
|
|
862
|
-
);
|
|
863
|
-
if (!fp.success) {
|
|
864
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
865
|
-
code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
1198
|
try {
|
|
870
|
-
|
|
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));
|
|
871
1215
|
} catch (err) {
|
|
872
1216
|
if (signal.aborted) throw err;
|
|
873
1217
|
const errObj = err;
|
|
@@ -877,37 +1221,243 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
877
1221
|
errorCode: errObj?.errorCode,
|
|
878
1222
|
statusCode: errObj?.statusCode,
|
|
879
1223
|
isDisconnected: isDeviceDisconnectedError(err),
|
|
880
|
-
isLocked: isDeviceLockedError(err)
|
|
1224
|
+
isLocked: isDeviceLockedError(err),
|
|
1225
|
+
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1226
|
+
isStuckApp: isStuckAppStateError(err)
|
|
881
1227
|
});
|
|
882
|
-
if (
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
+
}
|
|
886
1268
|
}
|
|
887
|
-
if (isDeviceLockedError(err)) {
|
|
888
|
-
|
|
889
|
-
_LedgerAdapter.
|
|
890
|
-
|
|
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;
|
|
891
1315
|
}
|
|
892
1316
|
if (isTimeoutError(err)) {
|
|
893
1317
|
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
894
1318
|
this._discoveredDevices.delete(resolvedConnectId);
|
|
895
|
-
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
|
+
});
|
|
896
1337
|
}
|
|
897
1338
|
throw err;
|
|
898
1339
|
}
|
|
899
1340
|
}
|
|
900
|
-
/**
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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);
|
|
906
1402
|
const retrySessionId = this._sessions.get(retryConnectId);
|
|
907
1403
|
if (!retrySessionId) {
|
|
908
1404
|
throw originalErr;
|
|
909
1405
|
}
|
|
910
|
-
|
|
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
|
+
}
|
|
911
1461
|
}
|
|
912
1462
|
/**
|
|
913
1463
|
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
@@ -921,7 +1471,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
921
1471
|
* - No connectId (searchDevices): environment-level permission
|
|
922
1472
|
* - With connectId (business methods): device-level permission
|
|
923
1473
|
*/
|
|
924
|
-
async _ensureDevicePermission(connectId, deviceId) {
|
|
1474
|
+
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
1475
|
+
if (signal?.aborted) {
|
|
1476
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1477
|
+
}
|
|
925
1478
|
const transportType = this.activeTransport ?? "hid";
|
|
926
1479
|
const waitPromise = this._uiRegistry.wait(
|
|
927
1480
|
import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
@@ -931,10 +1484,37 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
931
1484
|
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
932
1485
|
payload: { transportType, connectId, deviceId }
|
|
933
1486
|
});
|
|
934
|
-
|
|
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;
|
|
935
1514
|
if (!granted) {
|
|
936
|
-
throw Object.assign(new Error("Device permission denied"), {
|
|
937
|
-
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
|
|
938
1518
|
});
|
|
939
1519
|
}
|
|
940
1520
|
}
|
|
@@ -944,89 +1524,61 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
944
1524
|
*/
|
|
945
1525
|
errorToFailure(err) {
|
|
946
1526
|
debugError("[LedgerAdapter] error:", err);
|
|
1527
|
+
const tag = err && typeof err === "object" ? err._tag : void 0;
|
|
947
1528
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
948
1529
|
const e = err;
|
|
949
|
-
|
|
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);
|
|
950
1532
|
}
|
|
951
1533
|
const mapped = mapLedgerError(err);
|
|
952
|
-
return ledgerFailure(mapped.code, mapped.message, mapped.appName);
|
|
1534
|
+
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
953
1535
|
}
|
|
954
1536
|
registerEventListeners() {
|
|
955
1537
|
this.connector.on("device-connect", this.deviceConnectHandler);
|
|
956
1538
|
this.connector.on("device-disconnect", this.deviceDisconnectHandler);
|
|
957
|
-
this.connector.on("ui-
|
|
958
|
-
this.connector.on("ui-event", this.uiEventHandler);
|
|
1539
|
+
this.connector.on("ui-event", this.uiEventForwarder);
|
|
959
1540
|
}
|
|
960
1541
|
unregisterEventListeners() {
|
|
961
1542
|
this.connector.off("device-connect", this.deviceConnectHandler);
|
|
962
1543
|
this.connector.off("device-disconnect", this.deviceDisconnectHandler);
|
|
963
|
-
this.connector.off("ui-
|
|
964
|
-
this.connector.off("ui-event", this.uiEventHandler);
|
|
965
|
-
}
|
|
966
|
-
handleUiEvent(event) {
|
|
967
|
-
if (!event.type) return;
|
|
968
|
-
const payload = event.payload;
|
|
969
|
-
const deviceInfo = payload ? this.extractDeviceInfoFromPayload(payload) : this.unknownDevice();
|
|
970
|
-
switch (event.type) {
|
|
971
|
-
case "ui-request_confirmation":
|
|
972
|
-
this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_BUTTON, {
|
|
973
|
-
type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_BUTTON,
|
|
974
|
-
payload: { device: deviceInfo }
|
|
975
|
-
});
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
1544
|
+
this.connector.off("ui-event", this.uiEventForwarder);
|
|
978
1545
|
}
|
|
979
1546
|
// ---------------------------------------------------------------------------
|
|
980
1547
|
// Device info mapping
|
|
981
1548
|
// ---------------------------------------------------------------------------
|
|
982
1549
|
connectorDeviceToDeviceInfo(device) {
|
|
983
|
-
const isBle = device.connectId && /^[0-9A-Fa-f]{4}$/.test(device.connectId);
|
|
984
1550
|
return {
|
|
985
1551
|
vendor: "ledger",
|
|
986
1552
|
model: device.model ?? "unknown",
|
|
1553
|
+
modelName: device.modelName,
|
|
987
1554
|
firmwareVersion: "",
|
|
988
1555
|
deviceId: device.deviceId,
|
|
989
1556
|
connectId: device.connectId,
|
|
990
1557
|
label: device.name,
|
|
991
|
-
connectionType:
|
|
1558
|
+
connectionType: this.connector.connectionType,
|
|
1559
|
+
rssi: device.rssi,
|
|
1560
|
+
isConnectable: device.isConnectable,
|
|
1561
|
+
serialNumber: device.serialNumber,
|
|
992
1562
|
capabilities: device.capabilities
|
|
993
1563
|
};
|
|
994
1564
|
}
|
|
995
|
-
extractDeviceInfoFromPayload(payload) {
|
|
996
|
-
return {
|
|
997
|
-
vendor: "ledger",
|
|
998
|
-
model: payload.model ?? "unknown",
|
|
999
|
-
firmwareVersion: "",
|
|
1000
|
-
deviceId: payload.deviceId ?? payload.id ?? "",
|
|
1001
|
-
connectId: payload.connectId ?? payload.path ?? "",
|
|
1002
|
-
label: payload.label,
|
|
1003
|
-
connectionType: "usb"
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
unknownDevice() {
|
|
1007
|
-
return {
|
|
1008
|
-
vendor: "ledger",
|
|
1009
|
-
model: "unknown",
|
|
1010
|
-
firmwareVersion: "",
|
|
1011
|
-
deviceId: "",
|
|
1012
|
-
connectId: "",
|
|
1013
|
-
connectionType: "usb"
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
1565
|
};
|
|
1017
|
-
//
|
|
1018
|
-
//
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
_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;
|
|
1028
1577
|
var LedgerAdapter = _LedgerAdapter;
|
|
1029
1578
|
|
|
1579
|
+
// src/connector/LedgerConnectorBase.ts
|
|
1580
|
+
var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
|
|
1581
|
+
|
|
1030
1582
|
// src/device/LedgerDeviceManager.ts
|
|
1031
1583
|
var LedgerDeviceManager = class {
|
|
1032
1584
|
constructor(dmk) {
|
|
@@ -1063,7 +1615,9 @@ var LedgerDeviceManager = class {
|
|
|
1063
1615
|
if (resolved) return;
|
|
1064
1616
|
resolved = true;
|
|
1065
1617
|
this._discovered.clear();
|
|
1066
|
-
debugLog(
|
|
1618
|
+
debugLog(
|
|
1619
|
+
`[DMK] enumerate count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
|
|
1620
|
+
);
|
|
1067
1621
|
for (const d of devices) {
|
|
1068
1622
|
this._discovered.set(d.id, d);
|
|
1069
1623
|
}
|
|
@@ -1073,8 +1627,10 @@ var LedgerDeviceManager = class {
|
|
|
1073
1627
|
devices.map((d) => ({
|
|
1074
1628
|
path: d.id,
|
|
1075
1629
|
type: d.deviceModel.model,
|
|
1630
|
+
modelName: d.deviceModel.name,
|
|
1076
1631
|
name: d.name,
|
|
1077
|
-
transport: d.transport
|
|
1632
|
+
transport: d.transport,
|
|
1633
|
+
rssi: d.rssi
|
|
1078
1634
|
}))
|
|
1079
1635
|
);
|
|
1080
1636
|
} else {
|
|
@@ -1095,8 +1651,10 @@ var LedgerDeviceManager = class {
|
|
|
1095
1651
|
devices.map((d) => ({
|
|
1096
1652
|
path: d.id,
|
|
1097
1653
|
type: d.deviceModel.model,
|
|
1654
|
+
modelName: d.deviceModel.name,
|
|
1098
1655
|
name: d.name,
|
|
1099
|
-
transport: d.transport
|
|
1656
|
+
transport: d.transport,
|
|
1657
|
+
rssi: d.rssi
|
|
1100
1658
|
}))
|
|
1101
1659
|
);
|
|
1102
1660
|
}
|
|
@@ -1120,8 +1678,10 @@ var LedgerDeviceManager = class {
|
|
|
1120
1678
|
descriptor: {
|
|
1121
1679
|
path: d.id,
|
|
1122
1680
|
type: d.deviceModel.model,
|
|
1681
|
+
modelName: d.deviceModel.name,
|
|
1123
1682
|
name: d.name,
|
|
1124
|
-
transport: d.transport
|
|
1683
|
+
transport: d.transport,
|
|
1684
|
+
rssi: d.rssi
|
|
1125
1685
|
}
|
|
1126
1686
|
});
|
|
1127
1687
|
}
|
|
@@ -1140,6 +1700,54 @@ var LedgerDeviceManager = class {
|
|
|
1140
1700
|
this._listenSub?.unsubscribe();
|
|
1141
1701
|
this._listenSub = null;
|
|
1142
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
|
+
}
|
|
1143
1751
|
requestDevice() {
|
|
1144
1752
|
if (this._discoverySub) {
|
|
1145
1753
|
return Promise.resolve();
|
|
@@ -1157,11 +1765,33 @@ var LedgerDeviceManager = class {
|
|
|
1157
1765
|
});
|
|
1158
1766
|
return Promise.resolve();
|
|
1159
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
|
+
}
|
|
1160
1786
|
/** Connect to a previously discovered device. Returns sessionId. */
|
|
1161
1787
|
async connect(deviceId) {
|
|
1162
1788
|
const device = this._discovered.get(deviceId);
|
|
1163
1789
|
if (!device) {
|
|
1164
|
-
|
|
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;
|
|
1165
1795
|
}
|
|
1166
1796
|
const sessionId = await this._dmk.connect({ device });
|
|
1167
1797
|
this._sessions.set(deviceId, sessionId);
|
|
@@ -1200,6 +1830,19 @@ var LedgerDeviceManager = class {
|
|
|
1200
1830
|
this._sessionToDevice.clear();
|
|
1201
1831
|
this._dmk.close();
|
|
1202
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
|
+
}
|
|
1203
1846
|
};
|
|
1204
1847
|
|
|
1205
1848
|
// src/signer/SignerManager.ts
|
|
@@ -1211,11 +1854,12 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
|
|
|
1211
1854
|
|
|
1212
1855
|
// src/signer/deviceActionToPromise.ts
|
|
1213
1856
|
var import_device_management_kit = require("@ledgerhq/device-management-kit");
|
|
1214
|
-
var
|
|
1215
|
-
function deviceActionToPromise(action, onInteraction, timeoutMs =
|
|
1857
|
+
var IDLE_WATCHDOG_MS = 65e3;
|
|
1858
|
+
function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller) {
|
|
1216
1859
|
return new Promise((resolve, reject) => {
|
|
1217
1860
|
let settled = false;
|
|
1218
1861
|
let lastStep;
|
|
1862
|
+
const observedSteps = [];
|
|
1219
1863
|
let sub;
|
|
1220
1864
|
let timer = null;
|
|
1221
1865
|
const cancelAction = () => {
|
|
@@ -1228,44 +1872,59 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1228
1872
|
} catch {
|
|
1229
1873
|
}
|
|
1230
1874
|
};
|
|
1231
|
-
const
|
|
1875
|
+
const armIdleWatchdog = () => {
|
|
1232
1876
|
if (timer) clearTimeout(timer);
|
|
1233
1877
|
if (timeoutMs > 0) {
|
|
1234
1878
|
timer = setTimeout(() => {
|
|
1235
1879
|
if (!settled) {
|
|
1236
1880
|
settled = true;
|
|
1237
1881
|
cancelAction();
|
|
1238
|
-
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
|
+
);
|
|
1239
1887
|
}
|
|
1240
1888
|
}, timeoutMs);
|
|
1241
1889
|
}
|
|
1242
1890
|
};
|
|
1243
|
-
|
|
1891
|
+
armIdleWatchdog();
|
|
1244
1892
|
if (onRegisterCanceller) {
|
|
1245
|
-
onRegisterCanceller(() => {
|
|
1246
|
-
if (settled)
|
|
1893
|
+
onRegisterCanceller((reason) => {
|
|
1894
|
+
if (settled) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1247
1897
|
settled = true;
|
|
1248
1898
|
if (timer) clearTimeout(timer);
|
|
1249
1899
|
cancelAction();
|
|
1250
|
-
|
|
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);
|
|
1251
1913
|
});
|
|
1252
1914
|
}
|
|
1253
|
-
debugLog("[DMK-Observable] subscribing to action.observable...");
|
|
1254
1915
|
sub = action.observable.subscribe({
|
|
1255
1916
|
next: (state) => {
|
|
1256
|
-
if (settled)
|
|
1257
|
-
|
|
1917
|
+
if (settled) {
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
armIdleWatchdog();
|
|
1258
1921
|
const step = state?.intermediateValue?.step;
|
|
1259
|
-
if (step)
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
hasOutput: state.status === import_device_management_kit.DeviceActionStatus.Completed,
|
|
1266
|
-
hasError: state.status === import_device_management_kit.DeviceActionStatus.Error
|
|
1267
|
-
})
|
|
1268
|
-
);
|
|
1922
|
+
if (step) {
|
|
1923
|
+
lastStep = step;
|
|
1924
|
+
if (observedSteps[observedSteps.length - 1] !== step) {
|
|
1925
|
+
observedSteps.push(step);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1269
1928
|
if (state.status === import_device_management_kit.DeviceActionStatus.Completed) {
|
|
1270
1929
|
settled = true;
|
|
1271
1930
|
if (timer) clearTimeout(timer);
|
|
@@ -1277,10 +1936,14 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1277
1936
|
if (timer) clearTimeout(timer);
|
|
1278
1937
|
onInteraction?.("interaction-complete");
|
|
1279
1938
|
sub?.unsubscribe();
|
|
1280
|
-
|
|
1939
|
+
rejectWithStepContext(state.error, lastStep, observedSteps, reject);
|
|
1281
1940
|
} else if (state.status === import_device_management_kit.DeviceActionStatus.Pending && onInteraction) {
|
|
1282
1941
|
const interaction = state.intermediateValue?.requiredUserInteraction;
|
|
1283
1942
|
if (interaction && interaction !== "none") {
|
|
1943
|
+
if (interaction === "unlock-device" && timer) {
|
|
1944
|
+
clearTimeout(timer);
|
|
1945
|
+
timer = null;
|
|
1946
|
+
}
|
|
1284
1947
|
onInteraction(String(interaction));
|
|
1285
1948
|
}
|
|
1286
1949
|
}
|
|
@@ -1290,7 +1953,7 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1290
1953
|
settled = true;
|
|
1291
1954
|
if (timer) clearTimeout(timer);
|
|
1292
1955
|
sub?.unsubscribe();
|
|
1293
|
-
|
|
1956
|
+
rejectWithStepContext(err, lastStep, observedSteps, reject);
|
|
1294
1957
|
}
|
|
1295
1958
|
},
|
|
1296
1959
|
complete: () => {
|
|
@@ -1303,15 +1966,25 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
|
|
|
1303
1966
|
});
|
|
1304
1967
|
});
|
|
1305
1968
|
}
|
|
1306
|
-
function
|
|
1307
|
-
if (err && typeof err === "object" && lastStep) {
|
|
1969
|
+
function rejectWithStepContext(err, lastStep, observedSteps, reject) {
|
|
1970
|
+
if (err && typeof err === "object" && (lastStep || observedSteps.length > 0)) {
|
|
1308
1971
|
try {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
+
}
|
|
1315
1988
|
} catch {
|
|
1316
1989
|
}
|
|
1317
1990
|
}
|
|
@@ -1377,7 +2050,8 @@ var SignerManager = class _SignerManager {
|
|
|
1377
2050
|
async getOrCreate(sessionId) {
|
|
1378
2051
|
debugLog("[DMK] SignerManager.getOrCreate:", { sessionId });
|
|
1379
2052
|
const builder = await this._builderFn({ dmk: this._dmk, sessionId });
|
|
1380
|
-
|
|
2053
|
+
const builderWithContext = builder.withContextModule ? builder.withContextModule(_SignerManager._createContextModule()) : builder;
|
|
2054
|
+
return new SignerEth(builderWithContext.build());
|
|
1381
2055
|
}
|
|
1382
2056
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this
|
|
1383
2057
|
invalidate(_sessionId) {
|
|
@@ -1386,10 +2060,24 @@ var SignerManager = class _SignerManager {
|
|
|
1386
2060
|
clearAll() {
|
|
1387
2061
|
}
|
|
1388
2062
|
static _defaultBuilder() {
|
|
1389
|
-
return (args) =>
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
+
}
|
|
1392
2079
|
};
|
|
2080
|
+
return contextModule;
|
|
1393
2081
|
}
|
|
1394
2082
|
};
|
|
1395
2083
|
|
|
@@ -1499,6 +2187,7 @@ async function _getEthSigner(ctx, sessionId) {
|
|
|
1499
2187
|
const signerManager = await ctx.getSignerManager();
|
|
1500
2188
|
const signer = await signerManager.getOrCreate(sessionId);
|
|
1501
2189
|
signer.onInteraction = (interaction) => {
|
|
2190
|
+
debugLog("[LedgerConnector] evm.onInteraction:", interaction);
|
|
1502
2191
|
ctx.emit("ui-event", {
|
|
1503
2192
|
type: collapseSignerInteraction(interaction),
|
|
1504
2193
|
payload: { sessionId }
|
|
@@ -1699,6 +2388,7 @@ async function btcSignTransaction(ctx, sessionId, params) {
|
|
|
1699
2388
|
const path = normalizePath(params.path || "84'/0'/0'");
|
|
1700
2389
|
const purpose = path.split("/")[0]?.replace(/'/g, "");
|
|
1701
2390
|
const template = _purposeToTemplate(purpose, DefaultDescriptorTemplate);
|
|
2391
|
+
debugLog("[LedgerConnector] btcSignTransaction wallet:", { path, purpose, template });
|
|
1702
2392
|
const wallet = new DefaultWallet(path, template);
|
|
1703
2393
|
const signedTxHex = await btcSigner.signTransaction(wallet, params.psbt);
|
|
1704
2394
|
return { serializedTx: (0, import_hwk_adapter_core7.stripHex)(signedTxHex) };
|
|
@@ -1723,8 +2413,10 @@ async function btcSignPsbt(ctx, sessionId, params) {
|
|
|
1723
2413
|
const path = normalizePath(params.path || "84'/0'/0'");
|
|
1724
2414
|
const purpose = path.split("/")[0]?.replace(/'/g, "");
|
|
1725
2415
|
const template = _purposeToTemplate(purpose, DefaultDescriptorTemplate);
|
|
2416
|
+
debugLog("[LedgerConnector] btcSignPsbt wallet:", { path, purpose, template });
|
|
1726
2417
|
const wallet = new DefaultWallet(path, template);
|
|
1727
2418
|
const signatures = await btcSigner.signPsbt(wallet, params.psbt);
|
|
2419
|
+
debugLog("[LedgerConnector] btcSignPsbt signatures received:", signatures.length);
|
|
1728
2420
|
const signedPsbtHex = _applySignaturesToPsbt(params.psbt, signatures);
|
|
1729
2421
|
return { signedPsbt: signedPsbtHex };
|
|
1730
2422
|
} catch (err) {
|
|
@@ -1771,6 +2463,7 @@ async function _createBtcSigner(ctx, sessionId) {
|
|
|
1771
2463
|
const sdkSigner = new SignerBtcBuilder({ dmk, sessionId }).build();
|
|
1772
2464
|
const signer = new SignerBtc(sdkSigner);
|
|
1773
2465
|
signer.onInteraction = (interaction) => {
|
|
2466
|
+
debugLog("[LedgerConnector] btc.onInteraction:", interaction);
|
|
1774
2467
|
ctx.emit("ui-event", {
|
|
1775
2468
|
type: collapseSignerInteraction(interaction),
|
|
1776
2469
|
payload: { sessionId }
|
|
@@ -1941,6 +2634,7 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
1941
2634
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
1942
2635
|
const signer = new SignerSol(sdkSigner);
|
|
1943
2636
|
signer.onInteraction = (interaction) => {
|
|
2637
|
+
debugLog("[LedgerConnector] sol.onInteraction:", interaction);
|
|
1944
2638
|
ctx.emit("ui-event", {
|
|
1945
2639
|
type: collapseSignerInteraction(interaction),
|
|
1946
2640
|
payload: { sessionId }
|
|
@@ -1953,14 +2647,15 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
1953
2647
|
}
|
|
1954
2648
|
|
|
1955
2649
|
// src/connector/chains/tron.ts
|
|
1956
|
-
var
|
|
2650
|
+
var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
|
|
1957
2651
|
var import_hw_app_trx = __toESM(require("@ledgerhq/hw-app-trx"));
|
|
1958
2652
|
|
|
1959
|
-
// src/connector/chains/
|
|
1960
|
-
var
|
|
2653
|
+
// src/connector/chains/legacyChainCall.ts
|
|
2654
|
+
var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
|
|
1961
2655
|
|
|
1962
2656
|
// src/app/AppManager.ts
|
|
1963
2657
|
var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
|
|
2658
|
+
var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
|
|
1964
2659
|
var APP_NAME_MAP = {
|
|
1965
2660
|
ETH: "Ethereum",
|
|
1966
2661
|
BTC: "Bitcoin",
|
|
@@ -1996,9 +2691,16 @@ var AppManager = class {
|
|
|
1996
2691
|
* 5. Poll until the device confirms the target app is running.
|
|
1997
2692
|
*/
|
|
1998
2693
|
/**
|
|
1999
|
-
* @param onConfirmOnDevice Called
|
|
2000
|
-
*
|
|
2001
|
-
*
|
|
2694
|
+
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
2695
|
+
* device is about to display "Open <app>" on screen and wait for the
|
|
2696
|
+
* user's button press. UI consumers should show their "open app" prompt
|
|
2697
|
+
* in response. NOT called when the target app is already open (no user
|
|
2698
|
+
* interaction needed in that case).
|
|
2699
|
+
*
|
|
2700
|
+
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
2701
|
+
* has physically confirmed on the device, so anything that runs AFTER
|
|
2702
|
+
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
2703
|
+
* Hence the callback must fire BEFORE that await.
|
|
2002
2704
|
*/
|
|
2003
2705
|
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
2004
2706
|
const currentApp = await this._getCurrentApp(sessionId);
|
|
@@ -2009,8 +2711,8 @@ var AppManager = class {
|
|
|
2009
2711
|
await this._closeCurrentApp(sessionId);
|
|
2010
2712
|
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
2011
2713
|
}
|
|
2012
|
-
await this._openApp(sessionId, targetAppName);
|
|
2013
2714
|
onConfirmOnDevice?.();
|
|
2715
|
+
await this._openApp(sessionId, targetAppName);
|
|
2014
2716
|
await this._waitForApp(sessionId, targetAppName);
|
|
2015
2717
|
}
|
|
2016
2718
|
// ---------------------------------------------------------------------------
|
|
@@ -2022,9 +2724,34 @@ var AppManager = class {
|
|
|
2022
2724
|
command: new import_device_management_kit2.GetAppAndVersionCommand()
|
|
2023
2725
|
});
|
|
2024
2726
|
if ((0, import_device_management_kit2.isSuccessCommandResult)(result)) {
|
|
2727
|
+
debugLog("[AppManager] currentApp:", result.data.name);
|
|
2025
2728
|
return result.data.name;
|
|
2026
2729
|
}
|
|
2027
|
-
|
|
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
|
+
);
|
|
2028
2755
|
}
|
|
2029
2756
|
async _openApp(sessionId, appName) {
|
|
2030
2757
|
const result = await this._dmk.sendCommand({
|
|
@@ -2033,8 +2760,11 @@ var AppManager = class {
|
|
|
2033
2760
|
});
|
|
2034
2761
|
if (!(0, import_device_management_kit2.isSuccessCommandResult)(result)) {
|
|
2035
2762
|
const { statusCode } = result;
|
|
2763
|
+
const hasStatusCode2 = statusCode != null && statusCode !== "";
|
|
2764
|
+
debugLog("[AppManager] openApp failed:", appName, "statusCode:", statusCode);
|
|
2036
2765
|
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
2037
|
-
_tag:
|
|
2766
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
2767
|
+
code: hasStatusCode2 ? void 0 : import_hwk_adapter_core9.HardwareErrorCode.AppNotInstalled,
|
|
2038
2768
|
errorCode: String(statusCode ?? ""),
|
|
2039
2769
|
statusCode,
|
|
2040
2770
|
appName
|
|
@@ -2042,6 +2772,7 @@ var AppManager = class {
|
|
|
2042
2772
|
}
|
|
2043
2773
|
}
|
|
2044
2774
|
async _closeCurrentApp(sessionId) {
|
|
2775
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
2045
2776
|
await this._dmk.sendCommand({
|
|
2046
2777
|
sessionId,
|
|
2047
2778
|
command: new import_device_management_kit2.CloseAppCommand()
|
|
@@ -2052,15 +2783,23 @@ var AppManager = class {
|
|
|
2052
2783
|
* or throw after `_maxRetries` attempts.
|
|
2053
2784
|
*/
|
|
2054
2785
|
async _waitForApp(sessionId, expectedAppName) {
|
|
2786
|
+
let lastSeen = "";
|
|
2055
2787
|
for (let i = 0; i < this._maxRetries; i++) {
|
|
2056
2788
|
await this._wait();
|
|
2057
2789
|
const current = await this._getCurrentApp(sessionId);
|
|
2790
|
+
lastSeen = current;
|
|
2058
2791
|
if (current === expectedAppName) {
|
|
2059
2792
|
return;
|
|
2060
2793
|
}
|
|
2061
2794
|
}
|
|
2795
|
+
debugLog(
|
|
2796
|
+
"[AppManager] waitForApp exhausted: expected=",
|
|
2797
|
+
expectedAppName,
|
|
2798
|
+
"lastSeen=",
|
|
2799
|
+
lastSeen
|
|
2800
|
+
);
|
|
2062
2801
|
throw new Error(
|
|
2063
|
-
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries`
|
|
2802
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
2064
2803
|
);
|
|
2065
2804
|
}
|
|
2066
2805
|
_isDashboard(appName) {
|
|
@@ -2071,46 +2810,92 @@ var AppManager = class {
|
|
|
2071
2810
|
}
|
|
2072
2811
|
};
|
|
2073
2812
|
|
|
2074
|
-
// src/connector/chains/
|
|
2813
|
+
// src/connector/chains/legacyChainCall.ts
|
|
2075
2814
|
function isLegacyWrongAppError(err, _appName) {
|
|
2076
2815
|
return isWrongAppError(err);
|
|
2077
2816
|
}
|
|
2078
|
-
async function
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2817
|
+
async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
2818
|
+
const { appName, needsConfirmation } = options;
|
|
2819
|
+
let openAppPromptShown = false;
|
|
2820
|
+
const onAppOpenPrompt = () => {
|
|
2821
|
+
openAppPromptShown = true;
|
|
2082
2822
|
ctx.emit("ui-event", {
|
|
2083
|
-
type:
|
|
2823
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOpenApp,
|
|
2084
2824
|
payload: { sessionId }
|
|
2085
2825
|
});
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2826
|
+
};
|
|
2827
|
+
const closeOpenAppUiIfShown = () => {
|
|
2828
|
+
if (openAppPromptShown) {
|
|
2829
|
+
ctx.emit("ui-event", {
|
|
2830
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
|
|
2831
|
+
payload: { sessionId }
|
|
2832
|
+
});
|
|
2833
|
+
openAppPromptShown = false;
|
|
2834
|
+
}
|
|
2835
|
+
};
|
|
2836
|
+
try {
|
|
2837
|
+
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
2838
|
+
} catch (err) {
|
|
2839
|
+
debugLog(
|
|
2840
|
+
"[LegacyChainCall] pre-flight ensureAppOpen failed:",
|
|
2841
|
+
appName,
|
|
2842
|
+
err?.message
|
|
2843
|
+
);
|
|
2844
|
+
closeOpenAppUiIfShown();
|
|
2845
|
+
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
2846
|
+
}
|
|
2847
|
+
const runOnce = async () => {
|
|
2848
|
+
let confirmEmitted = false;
|
|
2849
|
+
if (needsConfirmation) {
|
|
2104
2850
|
ctx.emit("ui-event", {
|
|
2105
|
-
type:
|
|
2851
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
|
|
2106
2852
|
payload: { sessionId }
|
|
2107
2853
|
});
|
|
2854
|
+
confirmEmitted = true;
|
|
2855
|
+
}
|
|
2856
|
+
try {
|
|
2108
2857
|
return await action(sessionId);
|
|
2858
|
+
} finally {
|
|
2859
|
+
if (confirmEmitted || openAppPromptShown) {
|
|
2860
|
+
ctx.emit("ui-event", {
|
|
2861
|
+
type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
|
|
2862
|
+
payload: { sessionId }
|
|
2863
|
+
});
|
|
2864
|
+
openAppPromptShown = false;
|
|
2865
|
+
}
|
|
2109
2866
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2867
|
+
};
|
|
2868
|
+
try {
|
|
2869
|
+
return await runOnce();
|
|
2870
|
+
} catch (err) {
|
|
2871
|
+
if (!isLegacyWrongAppError(err, appName)) {
|
|
2872
|
+
debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
|
|
2873
|
+
ctx.invalidateSession(sessionId);
|
|
2874
|
+
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
2875
|
+
}
|
|
2876
|
+
debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
|
|
2877
|
+
try {
|
|
2878
|
+
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
2879
|
+
} catch (switchErr) {
|
|
2880
|
+
debugLog(
|
|
2881
|
+
"[LegacyChainCall] retry ensureAppOpen failed:",
|
|
2882
|
+
appName,
|
|
2883
|
+
switchErr?.message
|
|
2884
|
+
);
|
|
2885
|
+
closeOpenAppUiIfShown();
|
|
2886
|
+
throw ctx.wrapError(switchErr, { defaultAppName: appName });
|
|
2887
|
+
}
|
|
2888
|
+
ctx.clearAllSigners();
|
|
2889
|
+
const result = await runOnce();
|
|
2890
|
+
debugLog("[LegacyChainCall] retry succeeded:", appName);
|
|
2891
|
+
return result;
|
|
2112
2892
|
}
|
|
2113
2893
|
}
|
|
2894
|
+
async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
|
|
2895
|
+
const dmk = await ctx.getOrCreateDmk();
|
|
2896
|
+
const appManager = new AppManager(dmk);
|
|
2897
|
+
await appManager.ensureAppOpen(sessionId, appName, onPrompt);
|
|
2898
|
+
}
|
|
2114
2899
|
|
|
2115
2900
|
// src/transport/DmkTransport.ts
|
|
2116
2901
|
var import_hw_transport = __toESM(require("@ledgerhq/hw-transport"));
|
|
@@ -2140,83 +2925,75 @@ var DmkTransport = class extends import_hw_transport.default {
|
|
|
2140
2925
|
// src/connector/chains/tron.ts
|
|
2141
2926
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
2142
2927
|
const path = normalizePath(params.path);
|
|
2143
|
-
|
|
2144
|
-
return
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2928
|
+
const showOnDevice = params.showOnDevice ?? false;
|
|
2929
|
+
return withLegacyChainCall(
|
|
2930
|
+
ctx,
|
|
2931
|
+
sessionId,
|
|
2932
|
+
{
|
|
2933
|
+
appName: "Tron",
|
|
2934
|
+
// Only show "confirm on device" UI when the device is actually going
|
|
2935
|
+
// to display the address for the user to verify.
|
|
2936
|
+
needsConfirmation: showOnDevice
|
|
2937
|
+
},
|
|
2938
|
+
async (sid) => {
|
|
2939
|
+
const trx = await _createTrx(ctx, sid);
|
|
2940
|
+
const result = await trx.getAddress(path, showOnDevice);
|
|
2941
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
2151
2942
|
}
|
|
2152
|
-
|
|
2153
|
-
ctx.emit("ui-event", {
|
|
2154
|
-
type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
|
|
2155
|
-
payload: { sessionId: sid }
|
|
2156
|
-
});
|
|
2157
|
-
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
2158
|
-
});
|
|
2943
|
+
);
|
|
2159
2944
|
}
|
|
2160
2945
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
2161
2946
|
if (!params.rawTxHex) {
|
|
2162
2947
|
throw Object.assign(
|
|
2163
2948
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
2164
|
-
{ code:
|
|
2949
|
+
{ code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
|
|
2165
2950
|
);
|
|
2166
2951
|
}
|
|
2167
2952
|
const path = normalizePath(params.path);
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
payload: { sessionId: sid }
|
|
2183
|
-
});
|
|
2184
|
-
return { signature };
|
|
2185
|
-
});
|
|
2953
|
+
return withLegacyChainCall(
|
|
2954
|
+
ctx,
|
|
2955
|
+
sessionId,
|
|
2956
|
+
{ appName: "Tron", needsConfirmation: true },
|
|
2957
|
+
async (sid) => {
|
|
2958
|
+
const trx = await _createTrx(ctx, sid);
|
|
2959
|
+
const signature = await trx.signTransaction(
|
|
2960
|
+
path,
|
|
2961
|
+
params.rawTxHex,
|
|
2962
|
+
params.tokenSignatures ?? []
|
|
2963
|
+
);
|
|
2964
|
+
return { signature };
|
|
2965
|
+
}
|
|
2966
|
+
);
|
|
2186
2967
|
}
|
|
2187
2968
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
2188
2969
|
const path = normalizePath(params.path);
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
payload: { sessionId: sid }
|
|
2200
|
-
});
|
|
2201
|
-
return { signature };
|
|
2202
|
-
});
|
|
2970
|
+
return withLegacyChainCall(
|
|
2971
|
+
ctx,
|
|
2972
|
+
sessionId,
|
|
2973
|
+
{ appName: "Tron", needsConfirmation: true },
|
|
2974
|
+
async (sid) => {
|
|
2975
|
+
const trx = await _createTrx(ctx, sid);
|
|
2976
|
+
const signature = await trx.signPersonalMessage(path, params.messageHex);
|
|
2977
|
+
return { signature };
|
|
2978
|
+
}
|
|
2979
|
+
);
|
|
2203
2980
|
}
|
|
2204
2981
|
async function _createTrx(ctx, sessionId) {
|
|
2205
2982
|
const dmk = await ctx.getOrCreateDmk();
|
|
2206
2983
|
return new import_hw_app_trx.default(new DmkTransport(dmk, sessionId));
|
|
2207
2984
|
}
|
|
2208
|
-
async function _ensureTronAppOpen(ctx, sessionId) {
|
|
2209
|
-
const dmk = await ctx.getOrCreateDmk();
|
|
2210
|
-
const appManager = new AppManager(dmk);
|
|
2211
|
-
await appManager.ensureAppOpen(sessionId, "Tron", () => {
|
|
2212
|
-
ctx.emit("ui-event", {
|
|
2213
|
-
type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOpenApp,
|
|
2214
|
-
payload: { sessionId }
|
|
2215
|
-
});
|
|
2216
|
-
});
|
|
2217
|
-
}
|
|
2218
2985
|
|
|
2219
2986
|
// src/connector/LedgerConnectorBase.ts
|
|
2987
|
+
var METHOD_PREFIX_TO_APP_NAME = {
|
|
2988
|
+
evm: "Ethereum",
|
|
2989
|
+
btc: "Bitcoin",
|
|
2990
|
+
sol: "Solana",
|
|
2991
|
+
tron: "Tron"
|
|
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;
|
|
2220
2997
|
async function defaultLedgerKitImporter(pkg) {
|
|
2221
2998
|
switch (pkg) {
|
|
2222
2999
|
case "@ledgerhq/device-management-kit":
|
|
@@ -2240,16 +3017,6 @@ var LedgerConnectorBase = class {
|
|
|
2240
3017
|
this._dmk = null;
|
|
2241
3018
|
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
2242
3019
|
// ---------------------------------------------------------------------------
|
|
2243
|
-
// ConnectId <-> DMK path mapping
|
|
2244
|
-
//
|
|
2245
|
-
// DMK uses internal paths (BLE MAC, USB UUID) that may change across sessions.
|
|
2246
|
-
// _resolveConnectId() maps these to stable external IDs (BLE: "A58F", USB: same).
|
|
2247
|
-
// This bidirectional map is the SINGLE SOURCE OF TRUTH for all connectId usage.
|
|
2248
|
-
// ---------------------------------------------------------------------------
|
|
2249
|
-
this._connectIdToPath = /* @__PURE__ */ new Map();
|
|
2250
|
-
// "A58F" -> "D5:75:7D:4B:51:E8"
|
|
2251
|
-
this._pathToConnectId = /* @__PURE__ */ new Map();
|
|
2252
|
-
// ---------------------------------------------------------------------------
|
|
2253
3020
|
// Per-session DeviceAction cancellers
|
|
2254
3021
|
//
|
|
2255
3022
|
// Each chain handler registers its active DeviceAction's canceller via
|
|
@@ -2258,17 +3025,30 @@ var LedgerConnectorBase = class {
|
|
|
2258
3025
|
// unsubscribes the observable and releases DMK's IntentQueue slot.
|
|
2259
3026
|
// ---------------------------------------------------------------------------
|
|
2260
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();
|
|
2261
3040
|
this._createTransport = createTransport;
|
|
2262
3041
|
this.connectionType = options?.connectionType ?? "usb";
|
|
2263
3042
|
this._providedDmk = options?.dmk;
|
|
2264
3043
|
this._importLedgerKit = options?.importLedgerKit ?? defaultLedgerKitImporter;
|
|
3044
|
+
this._requirePreFlightScan = options?.requirePreFlightScan ?? false;
|
|
2265
3045
|
if (this._providedDmk) {
|
|
2266
3046
|
this._initManagers(this._providedDmk);
|
|
2267
3047
|
}
|
|
2268
3048
|
this._ctx = {
|
|
2269
3049
|
emit: (event, data) => this._emit(event, data),
|
|
2270
3050
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
2271
|
-
wrapError: (err) => this._wrapError(err),
|
|
3051
|
+
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
2272
3052
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
2273
3053
|
getDeviceManager: () => this._getDeviceManager(),
|
|
2274
3054
|
getSignerManager: () => this._getSignerManager(),
|
|
@@ -2279,38 +3059,23 @@ var LedgerConnectorBase = class {
|
|
|
2279
3059
|
importLedgerKit: this._importLedgerKit
|
|
2280
3060
|
};
|
|
2281
3061
|
}
|
|
2282
|
-
// "D5:75:7D:4B:51:E8" -> "A58F"
|
|
2283
|
-
/** Register a connectId <-> path mapping from a device descriptor. */
|
|
2284
|
-
_registerDeviceId(descriptor) {
|
|
2285
|
-
const connectId = this._resolveConnectId(descriptor);
|
|
2286
|
-
this._connectIdToPath.set(connectId, descriptor.path);
|
|
2287
|
-
this._pathToConnectId.set(descriptor.path, connectId);
|
|
2288
|
-
return connectId;
|
|
2289
|
-
}
|
|
2290
|
-
/** Get DMK path from external connectId. Falls back to connectId itself. */
|
|
2291
|
-
_getPathForConnectId(connectId) {
|
|
2292
|
-
return this._connectIdToPath.get(connectId) ?? connectId;
|
|
2293
|
-
}
|
|
2294
|
-
/** Get external connectId from DMK path. Falls back to path itself. */
|
|
2295
|
-
_getConnectIdForPath(path) {
|
|
2296
|
-
return this._pathToConnectId.get(path) ?? path;
|
|
2297
|
-
}
|
|
2298
3062
|
// ---------------------------------------------------------------------------
|
|
2299
3063
|
// Protected — hooks for subclasses
|
|
2300
3064
|
// ---------------------------------------------------------------------------
|
|
2301
3065
|
/**
|
|
2302
3066
|
* Resolve the connectId for a discovered device descriptor.
|
|
2303
3067
|
* Default: use the DMK path (ephemeral UUID).
|
|
2304
|
-
* Override in subclasses
|
|
3068
|
+
* Override in subclasses only when the public connectId differs from the transport path.
|
|
2305
3069
|
*/
|
|
2306
3070
|
_resolveConnectId(descriptor) {
|
|
2307
3071
|
return descriptor.path;
|
|
2308
3072
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
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) {
|
|
2314
3079
|
let descriptors = await dm.enumerate();
|
|
2315
3080
|
if (descriptors.length === 0) {
|
|
2316
3081
|
try {
|
|
@@ -2319,137 +3084,313 @@ var LedgerConnectorBase = class {
|
|
|
2319
3084
|
}
|
|
2320
3085
|
descriptors = await dm.enumerate();
|
|
2321
3086
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
+
);
|
|
2331
3112
|
return result;
|
|
2332
3113
|
}
|
|
2333
3114
|
// ---------------------------------------------------------------------------
|
|
2334
3115
|
// IConnector -- Connection
|
|
2335
3116
|
// ---------------------------------------------------------------------------
|
|
2336
3117
|
async connect(deviceId) {
|
|
2337
|
-
const
|
|
2338
|
-
|
|
2339
|
-
const dmkPath = deviceId ? this._getPathForConnectId(deviceId) : void 0;
|
|
2340
|
-
let targetPath = dmkPath;
|
|
3118
|
+
const callerSuppliedConnectId = Boolean(deviceId);
|
|
3119
|
+
let targetPath = deviceId;
|
|
2341
3120
|
if (!targetPath) {
|
|
2342
|
-
const
|
|
2343
|
-
if (
|
|
3121
|
+
const discovered = await this.searchDevices();
|
|
3122
|
+
if (discovered.length === 0) {
|
|
2344
3123
|
throw new Error(
|
|
2345
|
-
`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.`
|
|
2346
3125
|
);
|
|
2347
3126
|
}
|
|
2348
|
-
targetPath =
|
|
3127
|
+
targetPath = discovered[0].deviceId;
|
|
2349
3128
|
}
|
|
2350
|
-
const externalConnectId =
|
|
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
|
+
};
|
|
2351
3179
|
const doConnect = async (path) => {
|
|
2352
|
-
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);
|
|
2353
3198
|
const session = {
|
|
2354
3199
|
sessionId,
|
|
2355
3200
|
deviceInfo: {
|
|
2356
3201
|
vendor: "ledger",
|
|
2357
|
-
model: "unknown",
|
|
3202
|
+
model: info?.model ?? "unknown",
|
|
3203
|
+
modelName: info?.modelName,
|
|
2358
3204
|
firmwareVersion: "unknown",
|
|
2359
3205
|
deviceId: path,
|
|
2360
3206
|
connectId: externalConnectId,
|
|
2361
3207
|
connectionType: this.connectionType,
|
|
3208
|
+
rssi: info?.rssi,
|
|
2362
3209
|
capabilities: { persistentDeviceIdentity: false }
|
|
2363
3210
|
}
|
|
2364
3211
|
};
|
|
3212
|
+
const name = dm.getDeviceName(path) ?? "Ledger";
|
|
2365
3213
|
this._emit("device-connect", {
|
|
2366
|
-
device: {
|
|
2367
|
-
connectId: externalConnectId,
|
|
2368
|
-
deviceId: path,
|
|
2369
|
-
name: "Ledger"
|
|
2370
|
-
}
|
|
3214
|
+
device: { connectId: externalConnectId, deviceId: path, name }
|
|
2371
3215
|
});
|
|
2372
3216
|
return session;
|
|
2373
3217
|
};
|
|
2374
3218
|
try {
|
|
2375
3219
|
return await doConnect(targetPath);
|
|
2376
|
-
} catch {
|
|
3220
|
+
} catch (err) {
|
|
2377
3221
|
this._resetSignersAndSessions();
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
const descriptors = await dm2.enumerate();
|
|
2383
|
-
if (descriptors.length === 0) {
|
|
2384
|
-
throw new Error(
|
|
2385
|
-
`No Ledger device found after retry. Make sure the device is connected${this.connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
|
|
2386
|
-
);
|
|
3222
|
+
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
3223
|
+
const tag = err?._tag;
|
|
3224
|
+
if (isKnownConnectionTag(tag)) {
|
|
3225
|
+
throw err;
|
|
2387
3226
|
}
|
|
2388
|
-
|
|
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;
|
|
3234
|
+
}
|
|
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;
|
|
2389
3241
|
}
|
|
2390
|
-
|
|
3242
|
+
throw new Error("Ledger device appears unplugged. Plug in the device and try again.");
|
|
2391
3243
|
}
|
|
2392
3244
|
}
|
|
2393
3245
|
async disconnect(sessionId) {
|
|
2394
3246
|
if (!this._deviceManager) return;
|
|
2395
3247
|
const deviceId = this._deviceManager.getDeviceId(sessionId);
|
|
2396
3248
|
this._signerManager?.invalidate(sessionId);
|
|
3249
|
+
this._unwatchSessionState(sessionId);
|
|
2397
3250
|
await this._deviceManager.disconnect(sessionId);
|
|
2398
3251
|
if (deviceId) {
|
|
2399
3252
|
this._emit("device-disconnect", { connectId: deviceId });
|
|
2400
3253
|
}
|
|
2401
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
|
+
}
|
|
2402
3321
|
// ---------------------------------------------------------------------------
|
|
2403
3322
|
// IConnector -- Method dispatch
|
|
2404
3323
|
// ---------------------------------------------------------------------------
|
|
2405
3324
|
async call(sessionId, method, params) {
|
|
2406
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) {
|
|
3347
|
+
const ctx = this._ctxForMethod(method);
|
|
2407
3348
|
switch (method) {
|
|
2408
3349
|
// EVM
|
|
2409
3350
|
case "evmGetAddress":
|
|
2410
|
-
return evmGetAddress(
|
|
3351
|
+
return evmGetAddress(ctx, sessionId, params);
|
|
2411
3352
|
case "evmSignTransaction":
|
|
2412
|
-
return evmSignTransaction(
|
|
3353
|
+
return evmSignTransaction(ctx, sessionId, params);
|
|
2413
3354
|
case "evmSignMessage":
|
|
2414
|
-
return evmSignMessage(
|
|
3355
|
+
return evmSignMessage(ctx, sessionId, params);
|
|
2415
3356
|
case "evmSignTypedData":
|
|
2416
|
-
return evmSignTypedData(
|
|
3357
|
+
return evmSignTypedData(ctx, sessionId, params);
|
|
2417
3358
|
// BTC
|
|
2418
3359
|
case "btcGetAddress":
|
|
2419
|
-
return btcGetAddress(
|
|
3360
|
+
return btcGetAddress(ctx, sessionId, params);
|
|
2420
3361
|
case "btcGetPublicKey":
|
|
2421
|
-
return btcGetPublicKey(
|
|
3362
|
+
return btcGetPublicKey(ctx, sessionId, params);
|
|
2422
3363
|
case "btcSignTransaction":
|
|
2423
|
-
return btcSignTransaction(
|
|
3364
|
+
return btcSignTransaction(ctx, sessionId, params);
|
|
2424
3365
|
case "btcSignPsbt":
|
|
2425
|
-
return btcSignPsbt(
|
|
3366
|
+
return btcSignPsbt(ctx, sessionId, params);
|
|
2426
3367
|
case "btcSignMessage":
|
|
2427
|
-
return btcSignMessage(
|
|
3368
|
+
return btcSignMessage(ctx, sessionId, params);
|
|
2428
3369
|
case "btcGetMasterFingerprint":
|
|
2429
3370
|
return btcGetMasterFingerprint(
|
|
2430
|
-
|
|
3371
|
+
ctx,
|
|
2431
3372
|
sessionId,
|
|
2432
3373
|
params
|
|
2433
3374
|
);
|
|
2434
3375
|
// SOL
|
|
2435
3376
|
case "solGetAddress":
|
|
2436
|
-
return solGetAddress(
|
|
3377
|
+
return solGetAddress(ctx, sessionId, params);
|
|
2437
3378
|
case "solSignTransaction":
|
|
2438
|
-
return solSignTransaction(
|
|
3379
|
+
return solSignTransaction(ctx, sessionId, params);
|
|
2439
3380
|
case "solSignMessage":
|
|
2440
|
-
return solSignMessage(
|
|
3381
|
+
return solSignMessage(ctx, sessionId, params);
|
|
2441
3382
|
// TRON
|
|
2442
3383
|
case "tronGetAddress":
|
|
2443
|
-
return tronGetAddress(
|
|
3384
|
+
return tronGetAddress(ctx, sessionId, params);
|
|
2444
3385
|
case "tronSignTransaction":
|
|
2445
|
-
return tronSignTransaction(
|
|
3386
|
+
return tronSignTransaction(ctx, sessionId, params);
|
|
2446
3387
|
case "tronSignMessage": {
|
|
2447
3388
|
const p = params;
|
|
2448
3389
|
const internalParams = {
|
|
2449
3390
|
path: p.path,
|
|
2450
3391
|
messageHex: p.messageHex
|
|
2451
3392
|
};
|
|
2452
|
-
return tronSignMessage(
|
|
3393
|
+
return tronSignMessage(ctx, sessionId, internalParams);
|
|
2453
3394
|
}
|
|
2454
3395
|
default:
|
|
2455
3396
|
throw new Error(`LedgerConnector: unknown method "${method}"`);
|
|
@@ -2542,14 +3483,13 @@ var LedgerConnectorBase = class {
|
|
|
2542
3483
|
}
|
|
2543
3484
|
/**
|
|
2544
3485
|
* Replace an old session with a new one after app switch.
|
|
2545
|
-
*
|
|
2546
|
-
* and emits device-connect so the adapter updates its _sessions Map.
|
|
3486
|
+
* Emits device-connect so the adapter updates its _sessions Map.
|
|
2547
3487
|
*/
|
|
2548
3488
|
_replaceSession(oldSessionId, _newSessionId) {
|
|
2549
3489
|
const dm = this._deviceManager;
|
|
2550
3490
|
if (!dm) return;
|
|
2551
3491
|
const oldDeviceId = dm.getDeviceId(oldSessionId);
|
|
2552
|
-
const connectId = oldDeviceId
|
|
3492
|
+
const connectId = oldDeviceId;
|
|
2553
3493
|
this._signerManager?.invalidate(oldSessionId);
|
|
2554
3494
|
if (connectId) {
|
|
2555
3495
|
this._emit("device-connect", {
|
|
@@ -2562,13 +3502,18 @@ var LedgerConnectorBase = class {
|
|
|
2562
3502
|
}
|
|
2563
3503
|
}
|
|
2564
3504
|
/**
|
|
2565
|
-
* Light reset: clear signer/session state but keep DMK
|
|
3505
|
+
* Light reset: clear signer/session state but keep DMK alive.
|
|
2566
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().
|
|
2567
3511
|
*/
|
|
2568
3512
|
_resetSignersAndSessions() {
|
|
2569
3513
|
debugLog("[DMK] _resetSignersAndSessions called");
|
|
2570
3514
|
this._signerManager?.clearAll();
|
|
2571
3515
|
this._signerManager = null;
|
|
3516
|
+
this._deviceManager?.disposeKeepingDmk();
|
|
2572
3517
|
this._deviceManager = null;
|
|
2573
3518
|
}
|
|
2574
3519
|
_resetAll() {
|
|
@@ -2580,13 +3525,18 @@ var LedgerConnectorBase = class {
|
|
|
2580
3525
|
}
|
|
2581
3526
|
}
|
|
2582
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();
|
|
2583
3535
|
this._signerManager?.clearAll();
|
|
2584
3536
|
this._deviceManager?.dispose();
|
|
2585
3537
|
this._deviceManager = null;
|
|
2586
3538
|
this._signerManager = null;
|
|
2587
3539
|
this._dmk = null;
|
|
2588
|
-
this._connectIdToPath.clear();
|
|
2589
|
-
this._pathToConnectId.clear();
|
|
2590
3540
|
}
|
|
2591
3541
|
// ---------------------------------------------------------------------------
|
|
2592
3542
|
// Private -- Events
|
|
@@ -2605,16 +3555,36 @@ var LedgerConnectorBase = class {
|
|
|
2605
3555
|
// ---------------------------------------------------------------------------
|
|
2606
3556
|
// Private -- Error handling
|
|
2607
3557
|
// ---------------------------------------------------------------------------
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
3558
|
+
/**
|
|
3559
|
+
* Return a per-call ctx with the chain's Ledger app name pre-bound to
|
|
3560
|
+
* wrapError, so chain handlers don't need to repeat `{ defaultAppName: 'X' }`
|
|
3561
|
+
* at every catch site. Falls through unchanged for unknown methods.
|
|
3562
|
+
*/
|
|
3563
|
+
_ctxForMethod(method) {
|
|
3564
|
+
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
3565
|
+
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
3566
|
+
if (!defaultAppName) return this._ctx;
|
|
3567
|
+
return {
|
|
3568
|
+
...this._ctx,
|
|
3569
|
+
wrapError: (err, opts) => this._wrapError(err, { defaultAppName, ...opts })
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
_wrapError(err, opts) {
|
|
2611
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);
|
|
2612
3581
|
Object.assign(error, {
|
|
2613
3582
|
code: mapped.code,
|
|
2614
3583
|
appName: mapped.appName,
|
|
2615
3584
|
_tag: src._tag,
|
|
2616
3585
|
errorCode: src.errorCode,
|
|
2617
|
-
_lastStep: src._lastStep
|
|
3586
|
+
_lastStep: src._lastStep,
|
|
3587
|
+
_deviceActionSteps: src._deviceActionSteps
|
|
2618
3588
|
});
|
|
2619
3589
|
Object.defineProperty(error, "originalError", {
|
|
2620
3590
|
value: err,
|
|
@@ -2625,13 +3595,6 @@ var LedgerConnectorBase = class {
|
|
|
2625
3595
|
return error;
|
|
2626
3596
|
}
|
|
2627
3597
|
};
|
|
2628
|
-
|
|
2629
|
-
// src/utils/bleIdentity.ts
|
|
2630
|
-
function extractBleHexId(name) {
|
|
2631
|
-
if (!name) return void 0;
|
|
2632
|
-
const match = name.match(/\b([0-9A-Fa-f]{4})$/);
|
|
2633
|
-
return match ? match[1].toUpperCase() : void 0;
|
|
2634
|
-
}
|
|
2635
3598
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2636
3599
|
0 && (module.exports = {
|
|
2637
3600
|
AppManager,
|
|
@@ -2643,12 +3606,15 @@ function extractBleHexId(name) {
|
|
|
2643
3606
|
SignerEth,
|
|
2644
3607
|
SignerManager,
|
|
2645
3608
|
SignerSol,
|
|
3609
|
+
debugLog,
|
|
2646
3610
|
deviceActionToPromise,
|
|
2647
|
-
extractBleHexId,
|
|
2648
|
-
isDebugEnabled,
|
|
2649
3611
|
isDeviceLockedError,
|
|
3612
|
+
isLedgerBleConnectionType,
|
|
3613
|
+
isLedgerBleDescriptor,
|
|
3614
|
+
isLedgerDmkBleTransport,
|
|
2650
3615
|
ledgerFailure,
|
|
2651
3616
|
mapLedgerError,
|
|
2652
|
-
|
|
3617
|
+
offSdkEvent,
|
|
3618
|
+
onSdkEvent
|
|
2653
3619
|
});
|
|
2654
3620
|
//# sourceMappingURL=index.js.map
|