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