Skip to content

Commit 3c3340c

Browse files
committed
fix(server): add keepalive timer in replayEvents() and fix test
The replayEvents() code path (client reconnects with Last-Event-ID) was missing keepalive timer setup, so reconnecting clients would lose keepalive protection and get dropped again at the next proxy idle timeout. Also fixes the cleanup test to actually prove that close() clears the timer by asserting vi.getTimerCount() drops to 0, instead of relying on the catch fallback which would self-clear anyway. Addresses PR review feedback from @felixweinberger on PR #1726.
1 parent 3fdfb75 commit 3c3340c

File tree

2 files changed

+15
-2
lines changed

2 files changed

+15
-2
lines changed

packages/server/src/server/streamableHttp.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
551551
},
552552
cancel: () => {
553553
// Stream was cancelled by client
554+
this._clearKeepAliveTimer();
554555
// Cleanup will be handled by the mapping
555556
}
556557
});
@@ -582,6 +583,18 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
582583
}
583584
});
584585

586+
// Start keepalive timer for the replayed stream so reconnecting
587+
// clients remain protected from proxy idle timeouts
588+
if (this._keepAliveInterval !== undefined) {
589+
this._keepAliveTimer = setInterval(() => {
590+
try {
591+
streamController!.enqueue(encoder.encode(': keepalive\n\n'));
592+
} catch {
593+
this._clearKeepAliveTimer();
594+
}
595+
}, this._keepAliveInterval);
596+
}
597+
585598
return new Response(readable, { headers });
586599
} catch (error) {
587600
this.onerror?.(error as Error);

packages/server/test/server/streamableHttp.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,12 +1037,12 @@ describe('Zod v4', () => {
10371037
const getRes = await transport.handleRequest(getReq);
10381038

10391039
expect(getRes.status).toBe(200);
1040+
expect(vi.getTimerCount()).toBe(1);
10401041

10411042
// Close the transport, which should clear the interval
10421043
await transport.close();
10431044

1044-
// Advancing timers after close should not throw
1045-
vi.advanceTimersByTime(200);
1045+
expect(vi.getTimerCount()).toBe(0);
10461046
});
10471047
});
10481048
});

0 commit comments

Comments
 (0)