Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,9 @@ out
.yarn/install-state.gz
.pnp.*

# IDE
.vscode/
.idea/

.DS_Store
dist/
31 changes: 29 additions & 2 deletions src/client/sse.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -12,8 +12,21 @@ describe("SSEClientTransport", () => {
let resourceBaseUrl: URL;
let authBaseUrl: URL;
let lastServerRequest: IncomingMessage;
const serverRequests: Record<string, IncomingMessage[]> = {};
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;
Expand Down Expand Up @@ -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;
Expand All @@ -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" });
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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();
Expand All @@ -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 () => {
Expand Down
9 changes: 3 additions & 6 deletions src/client/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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')) {
Expand Down