Skip to content

Instantly share code, notes, and snippets.

@rauschma
Last active May 3, 2024 21:07
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rauschma/5ac9c3b8ed708f244ad1f3ef6e6721cb to your computer and use it in GitHub Desktop.
Save rauschma/5ac9c3b8ed708f244ad1f3ef6e6721cb to your computer and use it in GitHub Desktop.

Using server-sent events

Why and how?

Important gotcha

  • The server sends a stream to the browser. Conceptually, that stream is infinite: the server never closes it, only the client, by breaking the connection.
  • If the stream is ever closed by the server (restart etc.), then the client immediately tries to reconnect.

Sketch: hot-reloading via server-sent events

  • The server watches a directory and server-sends timestamps when something changes.
  • The current timestamp is embedded in each HTML file that is served.
  • A script in each HTML file listens to the server-sent events.
    • If the embedded timestamp is different from the received data: location.reload()

Acknowledgement

Thanks for feedback:

const evtSource = new EventSource('/server-sent-events');
// - 'message' is the default event
// - Servers can create other events via an `event:` field before one or more `data:` fields
evtSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log(data);
});
const readables = new Set();
function startServer() {
// ···
server.get('/server-sent-events', async (request, reply) => {
reply.type('text/event-stream').code(200);
const readable = new SimpleReadable();
// Optional: send the current full status
// The server closes the readable for us – if the connection is broken
readable.addListener('close', () => {
readables.delete(readable);
});
// This is how we send incremental updates:
readables.add(readable);
return readable; // send to client
});
// ···
}
function sendIncrementalUpdate(date) {
const data = {
dateString: date.toISOString(),
};
// Must not contain newlines (apart from those at the end)!
const chunk = `data: ${JSON.stringify(data)}\n\n`;
for (const readable of readables) {
readable.push(chunk);
}
}
//========== Helper class
/**
* Use `.push()` to add data chunks to instances of this class.
*/
class SimpleReadable extends stream.Readable {
_read() {
// do nothing
}
closeSimpleReadable() {
this.push(null);
}
}
@mtrefzer
Copy link

Where should SimpleReadable.closeSimpleReadable() be called?
I assume inside the CloseListener.

BTW it looks really nice and awesome lean.

@rauschma
Copy link
Author

@mtrefzer The server closes the Readables for us; we don’t have to close them ourselves in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment