diff --git a/src/wrapper/Client.ts b/src/wrapper/Client.ts index 741cf2e..c00aec3 100644 --- a/src/wrapper/Client.ts +++ b/src/wrapper/Client.ts @@ -1,5 +1,4 @@ import type { x402Client } from "@x402/fetch"; -import type { Mppx } from "mppx/client"; import { Supplier } from "../core/index.js"; import { NoOpAuthProvider } from "../core/auth/NoOpAuthProvider.js"; @@ -8,16 +7,16 @@ import { AgentMailClient as FernAgentMailClient } from "../Client.js"; import { type GetPaymentCredentials, WebsocketsClient } from "./WebsocketsClient.js"; import { getPaymentCredentials as getX402Credentials } from "./x402.js"; -import { getPaymentCredentials as getMppCredentials } from "./mpp.js"; +import { type MppxClient, getPaymentCredentials as getMppCredentials } from "./mppx.js"; type SharedOptions = Omit; export declare namespace AgentMailClient { export type Options = SharedOptions & ( - | { x402: x402Client; mpp?: never; apiKey?: never } - | { mpp: Mppx.Mppx; x402?: never; apiKey?: never } - | { apiKey?: Supplier; x402?: never; mpp?: never } + | { x402: x402Client; mppx?: never; apiKey?: never } + | { mppx: MppxClient; x402?: never; apiKey?: never } + | { apiKey?: Supplier; x402?: never; mppx?: never } ); export type RequestOptions = FernAgentMailClient.RequestOptions; } @@ -40,8 +39,8 @@ export class AgentMailClient extends FernAgentMailClient { authProvider: new NoOpAuthProvider(), fetch: async (input: RequestInfo | URL, init?: RequestInit) => { if (!wrappedFetch) { - const mod = await import("@x402/fetch"); - wrappedFetch = mod.wrapFetchWithPayment(fetch, x402); + const { wrapFetchWithPayment } = await import("@x402/fetch"); + wrappedFetch = wrapFetchWithPayment(fetch, x402); } return wrappedFetch(input, init); }, @@ -54,13 +53,13 @@ export class AgentMailClient extends FernAgentMailClient { super(fernOptions); this._getPaymentCredentials = (wsUrl) => getX402Credentials(wsUrl, x402); - } else if (options.mpp) { - const { mpp, ...rest } = options; + } else if (options.mppx) { + const { mppx, ...rest } = options; const fernOptions = { ...rest, authProvider: new NoOpAuthProvider(), - fetch: mpp.fetch, + fetch: mppx.fetch, }; if (!fernOptions.environment && !fernOptions.baseUrl) { @@ -69,7 +68,7 @@ export class AgentMailClient extends FernAgentMailClient { super(fernOptions); - this._getPaymentCredentials = (wsUrl) => getMppCredentials(wsUrl, mpp); + this._getPaymentCredentials = (wsUrl) => getMppCredentials(wsUrl, mppx); } else { let fernOptions: FernAgentMailClient.Options = options; diff --git a/src/wrapper/mpp-types.d.ts b/src/wrapper/mpp-types.d.ts deleted file mode 100644 index 6efe36c..0000000 --- a/src/wrapper/mpp-types.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare module "mppx/client" { - export namespace Mppx { - type Mppx = { - fetch: typeof globalThis.fetch; - transport: { - setCredential(request: Request, credential: string): Request; - }; - createCredential(response: Response): Promise; - }; - } -} diff --git a/src/wrapper/mpp.ts b/src/wrapper/mpp.ts deleted file mode 100644 index 08d95ec..0000000 --- a/src/wrapper/mpp.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Mppx } from "mppx/client"; -import { probe402 } from "./probe402.js"; - -export async function getPaymentCredentials(wsUrl: string, mpp: Mppx.Mppx): Promise> { - const response = await probe402(wsUrl); - - const credential = await mpp.createCredential(response); - const signed = mpp.transport.setCredential(new Request(wsUrl), credential); - - const headers: Record = {}; - signed.headers.forEach((value, key) => { - headers[key] = value; - }); - return headers; -} diff --git a/src/wrapper/mppx.ts b/src/wrapper/mppx.ts new file mode 100644 index 0000000..d61053e --- /dev/null +++ b/src/wrapper/mppx.ts @@ -0,0 +1,22 @@ +import { probe402, wsToHttp } from "./util.js"; + +export interface MppxClient { + fetch: typeof globalThis.fetch; + transport: { + setCredential(request: Request, credential: string): Request; + }; + createCredential(response: Response): Promise; +} + +export async function getPaymentCredentials(wsUrl: string, mppx: MppxClient): Promise> { + const response = await probe402(wsUrl); + + const credential = await mppx.createCredential(response); + const signed = mppx.transport.setCredential(new Request(wsToHttp(wsUrl)), credential); + + const headers: Record = {}; + signed.headers.forEach((value: string, key: string) => { + headers[key] = value; + }); + return headers; +} diff --git a/src/wrapper/probe402.ts b/src/wrapper/util.ts similarity index 59% rename from src/wrapper/probe402.ts rename to src/wrapper/util.ts index 1ff87be..39609ee 100644 --- a/src/wrapper/probe402.ts +++ b/src/wrapper/util.ts @@ -1,9 +1,14 @@ +export function wsToHttp(wsUrl: string): string { + return wsUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://"); +} + export async function probe402(wsUrl: string): Promise { - const httpUrl = wsUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://"); + const httpUrl = wsToHttp(wsUrl); const response = await fetch(httpUrl); if (response.status !== 402) { throw new Error(`Expected 402 from ${httpUrl} but got ${response.status}`); } + return response; } diff --git a/src/wrapper/x402.ts b/src/wrapper/x402.ts index cb1e7bc..cae25c7 100644 --- a/src/wrapper/x402.ts +++ b/src/wrapper/x402.ts @@ -1,5 +1,5 @@ import type { x402Client } from "@x402/fetch"; -import { probe402 } from "./probe402.js"; +import { probe402 } from "./util.js"; export async function getPaymentCredentials(wsUrl: string, client: x402Client): Promise> { const { x402HTTPClient } = await import("@x402/fetch"); diff --git a/tests/unit/wrapper/Client.test.ts b/tests/unit/wrapper/Client.test.ts index 335ae37..d0baa91 100644 --- a/tests/unit/wrapper/Client.test.ts +++ b/tests/unit/wrapper/Client.test.ts @@ -102,7 +102,7 @@ describe("AgentMailClient environment selection", () => { }); }); - describe("with mpp", () => { + describe("with mppx", () => { const mockMppFetch = vi.fn().mockResolvedValue(new Response()); const mockMppClient = { fetch: mockMppFetch, @@ -111,22 +111,22 @@ describe("AgentMailClient environment selection", () => { }; it("should derive ProdMpp environment", async () => { - const client = new AgentMailClient({ mpp: mockMppClient }); + const client = new AgentMailClient({ mppx: mockMppClient }); const env = await Supplier.get(client["_options"].environment); expect(env).toEqual(AgentMailEnvironment.ProdMpp); }); it("should not override explicitly set environment", async () => { const client = new AgentMailClient({ - mpp: mockMppClient, + mppx: mockMppClient, environment: AgentMailEnvironment.EuProd, }); const env = await Supplier.get(client["_options"].environment); expect(env).toEqual(AgentMailEnvironment.EuProd); }); - it("should use mpp.fetch as the fetch implementation", () => { - const client = new AgentMailClient({ mpp: mockMppClient }); + it("should use mppx.fetch as the fetch implementation", () => { + const client = new AgentMailClient({ mppx: mockMppClient }); expect(client["_options"].fetch).toBe(mockMppFetch); }); }); diff --git a/tests/unit/wrapper/WebsocketsClient.test.ts b/tests/unit/wrapper/WebsocketsClient.test.ts index 741ce46..b78d204 100644 --- a/tests/unit/wrapper/WebsocketsClient.test.ts +++ b/tests/unit/wrapper/WebsocketsClient.test.ts @@ -2,7 +2,7 @@ import { AgentMailClient } from "../../../src/wrapper/Client"; import { WebsocketsClient as FernWebsocketsClient } from "../../../src/api/resources/websockets/client/Client"; import type { WebsocketsSocket } from "../../../src/api/resources/websockets/client/Socket"; import * as x402Helpers from "../../../src/wrapper/x402"; -import * as mppHelpers from "../../../src/wrapper/mpp"; +import * as mppHelpers from "../../../src/wrapper/mppx"; function mockConnect() { return vi.spyOn(FernWebsocketsClient.prototype, "connect").mockResolvedValue({} as WebsocketsSocket); @@ -94,7 +94,7 @@ describe("WebsocketsClient wrapper", () => { }); }); - describe("with mpp", () => { + describe("with mppx", () => { const mockMppClient = { fetch: vi.fn(), transport: { setCredential: vi.fn() }, @@ -105,7 +105,7 @@ describe("WebsocketsClient wrapper", () => { it("should call getPaymentCredentials and pass as queryParams", async () => { const spy = vi.spyOn(mppHelpers, "getPaymentCredentials").mockResolvedValue(mockCredentials); - const client = new AgentMailClient({ mpp: mockMppClient }); + const client = new AgentMailClient({ mppx: mockMppClient }); await client.websockets.connect(); expect(spy).toHaveBeenCalled(); @@ -121,7 +121,7 @@ describe("WebsocketsClient wrapper", () => { it("should let user queryParams override payment credentials", async () => { const spy = vi.spyOn(mppHelpers, "getPaymentCredentials").mockResolvedValue({ Authorization: "from-mpp" }); - const client = new AgentMailClient({ mpp: mockMppClient }); + const client = new AgentMailClient({ mppx: mockMppClient }); await client.websockets.connect({ queryParams: { Authorization: "user-override" } }); expect(connectSpy).toHaveBeenCalledWith(