@metamask/eth-ledger-bridge-keyring
Version:
A MetaMask compatible keyring, for ledger hardware wallets
170 lines • 9.84 kB
JavaScript
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _LedgerDmkTransportMiddleware_instances, _LedgerDmkTransportMiddleware_sdk, _LedgerDmkTransportMiddleware_sessionId, _LedgerDmkTransportMiddleware_managedSessionId, _LedgerDmkTransportMiddleware_cachedSigner, _LedgerDmkTransportMiddleware_assignSession;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LedgerDmkTransportMiddleware = void 0;
const device_signer_kit_ethereum_1 = require("@ledgerhq/device-signer-kit-ethereum");
/**
* LedgerDmkTransportMiddleware is a middleware to communicate with the
* Ledger device via DMK.
* It adapts the new DMK Signer ETH to the existing bridging architectural patterns.
*/
class LedgerDmkTransportMiddleware {
constructor(sdk) {
_LedgerDmkTransportMiddleware_instances.add(this);
_LedgerDmkTransportMiddleware_sdk.set(this, void 0);
_LedgerDmkTransportMiddleware_sessionId.set(this, void 0);
/**
* Session created by this middleware's `connect()` call. Only managed
* sessions are disconnected automatically; IDs assigned via `setSessionId`
* are owned by the host.
*/
_LedgerDmkTransportMiddleware_managedSessionId.set(this, void 0);
_LedgerDmkTransportMiddleware_cachedSigner.set(this, null);
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_sdk, sdk, "f");
}
/**
* Starts device discovery using the configured DMK transport.
*
* @param args - Optional DMK discovery options.
* @returns An observable that emits discovered devices.
*/
startDiscovering(...args) {
return __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sdk, "f").startDiscovering(...args);
}
/**
* Set the session ID used for subsequent DMK commands.
*
* Binds the middleware to an existing DMK session without disconnecting
* any prior session. If the current session was created by this middleware's
* `connect()`, that managed session is released when replaced.
*
* Invalidates any cached signer so the next `getEthSigner()` call builds a
* fresh signer bound to the new session.
*
* @param sessionId - The session ID for the connected Ledger device.
*/
async setSessionId(sessionId) {
if (__classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sessionId, "f") === sessionId) {
return;
}
const managedToRelease = __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_managedSessionId, "f");
__classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_instances, "m", _LedgerDmkTransportMiddleware_assignSession).call(this, sessionId);
if (managedToRelease && managedToRelease !== sessionId) {
await __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sdk, "f").disconnect({ sessionId: managedToRelease });
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_managedSessionId, undefined, "f");
}
}
/**
* Method to retrieve the session ID.
*
* @returns The session ID.
* @throws {Error} If `setSessionId` or `connect` has not been called yet.
*/
getSessionId() {
if (!__classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sessionId, "f")) {
throw new Error('Session ID not set. Call connect() or setSessionId() first.');
}
return __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sessionId, "f");
}
/**
* Connects to a discovered device and stores the resulting session ID.
*
* @param args - The DMK connection arguments.
* @returns The created session ID.
*/
async connect(...args) {
const previousManagedSessionId = __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_managedSessionId, "f");
const sessionId = await __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sdk, "f").connect(...args);
__classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_instances, "m", _LedgerDmkTransportMiddleware_assignSession).call(this, sessionId);
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_managedSessionId, sessionId, "f");
if (previousManagedSessionId && previousManagedSessionId !== sessionId) {
await __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sdk, "f").disconnect({ sessionId: previousManagedSessionId });
}
return sessionId;
}
/**
* Build (or return the cached) Ethereum signer for the current session.
*
* The signer is memoized per session ID. `getEthSigner()` is called from
* five different operations on the bridge (`getPublicKey`,
* `deviceSignTransaction`, `deviceSignMessage`, `deviceSignTypedData`,
* `deviceSignDelegationAuthorization`); a single bridge session therefore
* needs at most one signer instance.
*
* Rebuilding is not free: `SignerEthBuilder.build()` allocates a fresh
* `DefaultContextModule` from `@ledgerhq/context-module`, which in its
* constructor builds a DI container and instantiates the default loaders
* (calldata, dynamic-network, external-plugin, gated-signing, nft, proxy,
* safe, token, trusted-name, typed-data, reporter, transaction-check, plus
* the field loaders, typed-data loader and blind-signing reporter). See
* `node_modules/@ledgerhq/context-module/lib/esm/src/DefaultContextModule.js`.
*
* Neither `DefaultSignerEth` nor `DefaultContextModule` expose a
* `dispose()` / `disconnect()` / `destroy()` method (verified against
* `node_modules/@ledgerhq/device-signer-kit-ethereum/lib/esm/internal/DefaultSignerEth.js`
* and the `DefaultContextModule` source above). If any of those loaders
* hold subscriptions to device-session state, re-creating the signer on
* every call would leak them. Caching is therefore defensive against
* subscription leaks, not just a micro-optimization.
*
* The cache is invalidated by `setSessionId()` when the session changes
* and by `dispose()` on shutdown.
*
* @returns An Ethereum signer instance bound to the current session.
*/
getEthSigner() {
const sessionId = this.getSessionId();
if (__classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_cachedSigner, "f")?.sessionId === sessionId) {
return __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_cachedSigner, "f").signer;
}
const signer = new device_signer_kit_ethereum_1.SignerEthBuilder({
dmk: __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sdk, "f"),
sessionId,
}).build();
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_cachedSigner, { sessionId, signer }, "f");
return signer;
}
/**
* Clears the stored session ID and signer cache without disconnecting from
* DMK.
*
* Use when the bridge does not own the DMK instance and the host manages
* session lifecycle externally.
*/
clearSession() {
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_sessionId, undefined, "f");
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_managedSessionId, undefined, "f");
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_cachedSigner, null, "f");
}
/**
* Disconnect the current session and clear cached state.
*
* Silently no-ops when no session has been established.
*
* @returns A promise that resolves when the session is closed.
*/
async dispose() {
const sessionToDisconnect = __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_managedSessionId, "f") ?? __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sessionId, "f");
if (sessionToDisconnect) {
await __classPrivateFieldGet(this, _LedgerDmkTransportMiddleware_sdk, "f").disconnect({ sessionId: sessionToDisconnect });
this.clearSession();
}
}
}
exports.LedgerDmkTransportMiddleware = LedgerDmkTransportMiddleware;
_LedgerDmkTransportMiddleware_sdk = new WeakMap(), _LedgerDmkTransportMiddleware_sessionId = new WeakMap(), _LedgerDmkTransportMiddleware_managedSessionId = new WeakMap(), _LedgerDmkTransportMiddleware_cachedSigner = new WeakMap(), _LedgerDmkTransportMiddleware_instances = new WeakSet(), _LedgerDmkTransportMiddleware_assignSession = function _LedgerDmkTransportMiddleware_assignSession(sessionId) {
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_cachedSigner, null, "f");
__classPrivateFieldSet(this, _LedgerDmkTransportMiddleware_sessionId, sessionId, "f");
};
//# sourceMappingURL=ledger-dmk-transport-middleware.cjs.map