diff --git a/.gitignore b/.gitignore index 6c4bf1a6..236a41ba 100644 --- a/.gitignore +++ b/.gitignore @@ -128,5 +128,9 @@ out .yarn/install-state.gz .pnp.* +# IDE +.vscode/ +.idea/ + .DS_Store dist/ diff --git a/src/client/sse.test.ts b/src/client/sse.test.ts index 3e3abe68..25859569 100644 --- a/src/client/sse.test.ts +++ b/src/client/sse.test.ts @@ -1,4 +1,4 @@ -import { createServer, type IncomingMessage, type Server } from "http"; +import { createServer, IncomingMessage, Server, ServerResponse } from "http"; import { AddressInfo } from "net"; import { JSONRPCMessage } from "../types.js"; import { SSEClientTransport } from "./sse.js"; @@ -12,8 +12,21 @@ describe("SSEClientTransport", () => { let resourceBaseUrl: URL; let authBaseUrl: URL; let lastServerRequest: IncomingMessage; + const serverRequests: Record = {}; let sendServerMessage: ((message: string) => void) | null = null; + const recordServerRequest = (req: IncomingMessage, res: ServerResponse) => { + lastServerRequest = req; + + const key = `${req.method} ${req.url}`; + serverRequests[key] = serverRequests[key] || []; + serverRequests[key].push(req); + + res.on('finish', () => { + console.log(`[server] ${req.method} ${req.url} -> ${res.statusCode} ${res.statusMessage}`); + }); + }; + beforeEach((done) => { // Reset state lastServerRequest = null as unknown as IncomingMessage; @@ -606,6 +619,8 @@ describe("SSEClientTransport", () => { authServer.close(); authServer = createServer((req, res) => { + recordServerRequest(req, res); + if (req.url === "/.well-known/oauth-authorization-server") { res.writeHead(404).end(); return; @@ -618,7 +633,7 @@ describe("SSEClientTransport", () => { req.on("end", () => { const params = new URLSearchParams(body); if (params.get("grant_type") === "refresh_token" && - params.get("refresh_token") === "refresh-token" && + params.get("refresh_token")?.includes("refresh-token") && params.get("client_id") === "test-client-id" && params.get("client_secret") === "test-client-secret") { res.writeHead(200, { "Content-Type": "application/json" }); @@ -649,6 +664,7 @@ describe("SSEClientTransport", () => { let connectionAttempts = 0; resourceServer = createServer((req, res) => { + recordServerRequest(req, res); lastServerRequest = req; if (req.url === "/.well-known/oauth-protected-resource") { @@ -698,6 +714,14 @@ describe("SSEClientTransport", () => { transport = new SSEClientTransport(resourceBaseUrl, { authProvider: mockAuthProvider, + eventSourceInit: { + fetch: (url, init) => { + return fetch(url, { ...init, headers: { + ...init?.headers, + 'X-Custom-Header': 'custom-value' + } }); + } + }, }); await transport.start(); @@ -709,6 +733,9 @@ describe("SSEClientTransport", () => { }); expect(connectionAttempts).toBe(1); expect(lastServerRequest.headers.authorization).toBe("Bearer new-token"); + expect(serverRequests["GET /"]).toHaveLength(2); + expect(serverRequests["GET /"] + .every(req => req.headers["x-custom-header"] === "custom-value")).toBe(true); }); it("refreshes expired token during POST request", async () => { diff --git a/src/client/sse.ts b/src/client/sse.ts index 568a5159..ed6e39f5 100644 --- a/src/client/sse.ts +++ b/src/client/sse.ts @@ -35,11 +35,6 @@ export type SSEClientTransportOptions = { /** * Customizes the initial SSE request to the server (the request that begins the stream). - * - * NOTE: Setting this property will prevent an `Authorization` header from - * being automatically attached to the SSE request, if an `authProvider` is - * also given. This can be worked around by setting the `Authorization` header - * manually. */ eventSourceInit?: EventSourceInit; @@ -135,7 +130,9 @@ export class SSEClientTransport implements Transport { headers.set("Accept", "text/event-stream"); const response = await fetchImpl(url, { ...init, - headers, + headers: { + ...Object.fromEntries(headers.entries()), + } }) if (response.status === 401 && response.headers.has('www-authenticate')) {