UNPKG

@metamask/eth-ledger-bridge-keyring

Version:
1 lines 9.01 kB
{"version":3,"file":"ledger-dmk-transport-middleware.cjs","sourceRoot":"","sources":["../../src/dmk/ledger-dmk-transport-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DeviceManagementKit } from '@ledgerhq/device-management-kit';\nimport { SignerEthBuilder } from '@ledgerhq/device-signer-kit-ethereum';\n\ntype StartDiscoveringParameters = Parameters<\n DeviceManagementKit['startDiscovering']\n>;\n\ntype StartDiscoveringResult = ReturnType<\n DeviceManagementKit['startDiscovering']\n>;\n\ntype ConnectParameters = Parameters<DeviceManagementKit['connect']>;\n\ntype ConnectResult = ReturnType<DeviceManagementKit['connect']>;\n\ntype EthSigner = ReturnType<SignerEthBuilder['build']>;\n\ntype CachedSigner = { sessionId: string; signer: EthSigner };\n\n/**\n * LedgerDmkTransportMiddleware is a middleware to communicate with the\n * Ledger device via DMK.\n * It adapts the new DMK Signer ETH to the existing bridging architectural patterns.\n */\nexport class LedgerDmkTransportMiddleware {\n readonly #sdk: DeviceManagementKit;\n\n #sessionId?: string;\n\n /**\n * Session created by this middleware's `connect()` call. Only managed\n * sessions are disconnected automatically; IDs assigned via `setSessionId`\n * are owned by the host.\n */\n #managedSessionId?: string;\n\n #cachedSigner: CachedSigner | null = null;\n\n constructor(sdk: DeviceManagementKit) {\n this.#sdk = sdk;\n }\n\n /**\n * Starts device discovery using the configured DMK transport.\n *\n * @param args - Optional DMK discovery options.\n * @returns An observable that emits discovered devices.\n */\n startDiscovering(\n ...args: StartDiscoveringParameters\n ): StartDiscoveringResult {\n return this.#sdk.startDiscovering(...args);\n }\n\n /**\n * Set the session ID used for subsequent DMK commands.\n *\n * Binds the middleware to an existing DMK session without disconnecting\n * any prior session. If the current session was created by this middleware's\n * `connect()`, that managed session is released when replaced.\n *\n * Invalidates any cached signer so the next `getEthSigner()` call builds a\n * fresh signer bound to the new session.\n *\n * @param sessionId - The session ID for the connected Ledger device.\n */\n async setSessionId(sessionId: string): Promise<void> {\n if (this.#sessionId === sessionId) {\n return;\n }\n\n const managedToRelease = this.#managedSessionId;\n this.#assignSession(sessionId);\n\n if (managedToRelease && managedToRelease !== sessionId) {\n await this.#sdk.disconnect({ sessionId: managedToRelease });\n this.#managedSessionId = undefined;\n }\n }\n\n /**\n * Method to retrieve the session ID.\n *\n * @returns The session ID.\n * @throws {Error} If `setSessionId` or `connect` has not been called yet.\n */\n getSessionId(): string {\n if (!this.#sessionId) {\n throw new Error(\n 'Session ID not set. Call connect() or setSessionId() first.',\n );\n }\n return this.#sessionId;\n }\n\n /**\n * Connects to a discovered device and stores the resulting session ID.\n *\n * @param args - The DMK connection arguments.\n * @returns The created session ID.\n */\n async connect(...args: ConnectParameters): ConnectResult {\n const previousManagedSessionId = this.#managedSessionId;\n const sessionId = await this.#sdk.connect(...args);\n this.#assignSession(sessionId);\n this.#managedSessionId = sessionId;\n\n if (previousManagedSessionId && previousManagedSessionId !== sessionId) {\n await this.#sdk.disconnect({ sessionId: previousManagedSessionId });\n }\n\n return sessionId;\n }\n\n /**\n * Build (or return the cached) Ethereum signer for the current session.\n *\n * The signer is memoized per session ID. `getEthSigner()` is called from\n * five different operations on the bridge (`getPublicKey`,\n * `deviceSignTransaction`, `deviceSignMessage`, `deviceSignTypedData`,\n * `deviceSignDelegationAuthorization`); a single bridge session therefore\n * needs at most one signer instance.\n *\n * Rebuilding is not free: `SignerEthBuilder.build()` allocates a fresh\n * `DefaultContextModule` from `@ledgerhq/context-module`, which in its\n * constructor builds a DI container and instantiates the default loaders\n * (calldata, dynamic-network, external-plugin, gated-signing, nft, proxy,\n * safe, token, trusted-name, typed-data, reporter, transaction-check, plus\n * the field loaders, typed-data loader and blind-signing reporter). See\n * `node_modules/@ledgerhq/context-module/lib/esm/src/DefaultContextModule.js`.\n *\n * Neither `DefaultSignerEth` nor `DefaultContextModule` expose a\n * `dispose()` / `disconnect()` / `destroy()` method (verified against\n * `node_modules/@ledgerhq/device-signer-kit-ethereum/lib/esm/internal/DefaultSignerEth.js`\n * and the `DefaultContextModule` source above). If any of those loaders\n * hold subscriptions to device-session state, re-creating the signer on\n * every call would leak them. Caching is therefore defensive against\n * subscription leaks, not just a micro-optimization.\n *\n * The cache is invalidated by `setSessionId()` when the session changes\n * and by `dispose()` on shutdown.\n *\n * @returns An Ethereum signer instance bound to the current session.\n */\n getEthSigner(): EthSigner {\n const sessionId = this.getSessionId();\n if (this.#cachedSigner?.sessionId === sessionId) {\n return this.#cachedSigner.signer;\n }\n const signer = new SignerEthBuilder({\n dmk: this.#sdk,\n sessionId,\n }).build();\n this.#cachedSigner = { sessionId, signer };\n return signer;\n }\n\n /**\n * Clears the stored session ID and signer cache without disconnecting from\n * DMK.\n *\n * Use when the bridge does not own the DMK instance and the host manages\n * session lifecycle externally.\n */\n clearSession(): void {\n this.#sessionId = undefined;\n this.#managedSessionId = undefined;\n this.#cachedSigner = null;\n }\n\n /**\n * Disconnect the current session and clear cached state.\n *\n * Silently no-ops when no session has been established.\n *\n * @returns A promise that resolves when the session is closed.\n */\n async dispose(): Promise<void> {\n const sessionToDisconnect = this.#managedSessionId ?? this.#sessionId;\n if (sessionToDisconnect) {\n await this.#sdk.disconnect({ sessionId: sessionToDisconnect });\n this.clearSession();\n }\n }\n\n #assignSession(sessionId: string): void {\n this.#cachedSigner = null;\n this.#sessionId = sessionId;\n }\n}\n"]}