App-wide server-side push in SvelteKit using Server-Sent Events
28 July 2023 • 4-minute read
The usage of a Node.js PassThrough
Stream is the key to tying together
the pieces of the server-side push solution in this blog entry.
This, however, still feels constrained by the fact that SSEs need to reside in a GET endpoint. A question logically follows:
is it possible for disparate parts of the server to fire off messages intended for dispatch by
the client side without the client sending a message first, i.e, trigger server-side pushes from anywhere?
I have implemented a solution in SvelteKit and again, the key here is the PassThrough
Stream.
Solution
The server-side solution is obvious. Instantiate an exportable PassThrough
Stream object somewhere
and whenever there's some domain action that requires a server-side push, simply import this PassThrough
object
to the call site and write data (the message) into it. A better design would be to encapsulate this Stream object in some class
and write methods for writing into it, like in the following snippet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The SSE GET endpoint is simple. Just import the PassThrough
Stream object here and attach an
on.("data", ...)
handler for the controller
to enqueue.
Don't forget the data:
prefix and the two newlines.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The client-side solution asks if it's possible to listen to the SSE endpoint and
dispatch the received messages regardless of the user's current location in the website.
I discovered that SvelteKit's hooks.client
file is capable of doing this.
This is where an EventSource
would be constructed with the URL of the SSE GET endpoint
as argument.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The hooks.client
file is a special file in SvelteKit whose code,
specifically client-side code, will run when the app starts up,
so you can take advantage of Svelte's reactivity features here.
Improvements
To prevent bloating the hooks.client
file,
you can import a function here instead that handles all the different messages you receive
from the SSE endpoint.
The message_stream
object is mutable, so instead of exposing it and then attaching
an on.("data", ...)
handler to it, the class encapsulating it can instead have a
function that takes in a closure as argument that captures the data
object written into
message_stream
.
Unlike the entry last time where the SSE
is busy sending data on short intervals, a more general, app-wide use of SSEs like this may lead to the SSE pushing
messages sparsely over time and may result to the disconnection of the client to the GET endpoint due to timeout.
Prevent this by sending 'keep-alive' messages. This can simply be ":/n/n".
Use setInterval()
for this with interval set to as small as 15 seconds.
This periodic code should live in SvelteKit's hooks.server
file, the server-side
equivalent of the hooks.client
file.