fix: proper stream cleanup and transport close on disconnect

1. createWebReadableStream: Add closed flag and cancel() callback to
   prevent double-close crashes when SSE streams end. This fixes the
   "Controller is already closed" error that occurs when the SDK calls
   response.body.cancel() on 202 responses.

2. DELETE handler: Call serverTransport.close() after terminateSession()
   to properly cleanup the transport and cancel pending reconnections.
   - terminateSession() notifies the server (sends DELETE request)
   - close() cleans up client-side resources (abort controller, timeouts)
   - Both are needed for a complete disconnect
This commit is contained in:
Felix Weinberger 2025-11-28 19:39:49 +00:00
commit fe57d7e952
No known key found for this signature in database

View file

@ -241,18 +241,31 @@ const authMiddleware = (
* This is necessary for the EventSource polyfill which expects web streams
*/
const createWebReadableStream = (nodeStream: any): ReadableStream => {
let closed = false;
return new ReadableStream({
start(controller) {
nodeStream.on("data", (chunk: any) => {
controller.enqueue(chunk);
if (!closed) {
controller.enqueue(chunk);
}
});
nodeStream.on("end", () => {
controller.close();
if (!closed) {
closed = true;
controller.close();
}
});
nodeStream.on("error", (err: any) => {
controller.error(err);
if (!closed) {
closed = true;
controller.error(err);
}
});
},
cancel() {
closed = true;
nodeStream.destroy();
},
});
};
@ -526,6 +539,7 @@ app.delete(
res.status(404).end("Transport not found for sessionId " + sessionId);
} else {
await serverTransport.terminateSession();
await serverTransport.close();
webAppTransports.delete(sessionId);
serverTransports.delete(sessionId);
sessionHeaderHolders.delete(sessionId);