Skip to content

Commit aec92db

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 c04a6f0 commit aec92db

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
@@ -552,6 +552,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
552552
},
553553
cancel: () => {
554554
// Stream was cancelled by client
555+
this._clearKeepAliveTimer();
555556
// Cleanup will be handled by the mapping
556557
}
557558
});
@@ -583,6 +584,18 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
583584
}
584585
});
585586

587+
// Start keepalive timer for the replayed stream so reconnecting
588+
// clients remain protected from proxy idle timeouts
589+
if (this._keepAliveInterval !== undefined) {
590+
this._keepAliveTimer = setInterval(() => {
591+
try {
592+
streamController!.enqueue(encoder.encode(': keepalive\n\n'));
593+
} catch {
594+
this._clearKeepAliveTimer();
595+
}
596+
}, this._keepAliveInterval);
597+
}
598+
586599
return new Response(readable, { headers });
587600
} catch (error) {
588601
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
@@ -1074,12 +1074,12 @@ describe('Zod v4', () => {
10741074
const getRes = await transport.handleRequest(getReq);
10751075

10761076
expect(getRes.status).toBe(200);
1077+
expect(vi.getTimerCount()).toBe(1);
10771078

10781079
// Close the transport, which should clear the interval
10791080
await transport.close();
10801081

1081-
// Advancing timers after close should not throw
1082-
vi.advanceTimersByTime(200);
1082+
expect(vi.getTimerCount()).toBe(0);
10831083
});
10841084
});
10851085
});

0 commit comments

Comments
 (0)