You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix memory leak when app-level retries are exhausted in `createSSE`. Previously, when all reconnect attempts were used up and the `EventSource` was permanently closed, `currentCleanup` was never called — leaving the `EventSource` instance and its event listeners alive in memory, and the `source` signal pointing to a stale handle. Now an `else if` branch explicitly calls `currentCleanup()`, clears the reference, and sets the `source` signal to `undefined`.
Copy file name to clipboardExpand all lines: packages/sse/README.md
+52Lines changed: 52 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -154,6 +154,58 @@ SSEReadyState.CLOSED; // 2
154
154
155
155
`EventSource` has native browser-level reconnection built in. For transient network drops the browser automatically retries. The `reconnect` option in `createSSE` is for _application-level_ reconnection — it fires only when `readyState` becomes `SSEReadyState.CLOSED`, meaning the browser has given up entirely. You generally do not need `reconnect: true` for normal usage.
156
156
157
+
### A note on server disconnection detection
158
+
159
+
`EventSource`**does not reliably detect when a server silently stops responding**. If the server process crashes or the network path is severed without a proper TCP close handshake, the browser never fires an `error` event and `readyState` stays `OPEN` indefinitely — the connection looks healthy even though no messages will ever arrive.
160
+
161
+
The only robust workaround is **application-level heartbeats**: the server sends a lightweight event at a fixed interval, and the client starts a timer that triggers a reconnect if no heartbeat is received within the expected window.
162
+
163
+
```ts
164
+
import { createSSE } from"@solid-primitives/sse";
165
+
import { onCleanup } from"solid-js";
166
+
167
+
const HEARTBEAT_TIMEOUT_MS =15_000; // reconnect if silent for 15 s
168
+
169
+
function createSSEWithHeartbeat(url:string) {
170
+
let timer:ReturnType<typeofsetTimeout> |undefined;
171
+
172
+
const { reconnect, ...rest } =createSSE(url, {
173
+
// The server emits `event: heartbeat\ndata: \n\n` every ~10 s.
174
+
// Any regular message also resets the timer.
175
+
events: { heartbeat: resetTimer },
176
+
onMessage: resetTimer,
177
+
reconnect: true,
178
+
});
179
+
180
+
function resetTimer() {
181
+
clearTimeout(timer);
182
+
timer=setTimeout(() => {
183
+
// No heartbeat received — assume the server is gone.
184
+
reconnect();
185
+
}, HEARTBEAT_TIMEOUT_MS);
186
+
}
187
+
188
+
onCleanup(() => {
189
+
clearTimeout(timer);
190
+
timer=undefined;
191
+
});
192
+
resetTimer(); // arm the first timeout immediately
193
+
194
+
return { reconnect, ...rest };
195
+
}
196
+
```
197
+
198
+
On the server, emit a periodic heartbeat event well within the client timeout:
199
+
200
+
```js
201
+
// Express / Node.js example
202
+
setInterval(() => {
203
+
res.write("event: heartbeat\ndata: \n\n");
204
+
}, 10_000); // every 10 s, safely below the 15 s client timeout
205
+
```
206
+
207
+
> **Why SSE comment lines are not enough** — SSE comment lines (e.g. `: keep-alive`) reset the browser's internal TCP idle timer but are _not_ exposed to JavaScript listeners. Use a named `event: heartbeat` or a plain `data:` event if you need the client to observe the heartbeat.
208
+
157
209
## Integration with `@solid-primitives/event-bus`
158
210
159
211
Because `bus.emit` matches the `(event: MessageEvent) => void` shape of `onMessage`, you can wire them directly:
0 commit comments