{"version":3,"file":"ledger-dmk-transport-middleware.mjs","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"]}