Server Sent Events with SvelteKit
How to use Server Sent Events with SvelteKit
Create a SSE endpoint (+server.ts)
// Set containing all the subscribers
const subscribers = new Set<ReadableStreamDefaultController>();
// Create a string from an object
function create_message_string(message: object) {
return (
Object.entries(message)
.map(([key, value]) => `${key}: ${value}`)
.join('\n') + '\n\n'
);
}
export async function GET() {
let _controller: ReadableStreamDefaultController;
const stream = new ReadableStream({
// the controller is the current connection
start(controller) {
_controller = controller;
// Add the controller to the set of subscribers
subscribers.add(_controller);
// If you want to close the connection: controller.close();
},
cancel() {
// Remove the controller from the set of subscribers
subscribers.delete(_controller);
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
}
});
}
export const POST = async ({ request }) => {
const body = await request.json();
// create the string to send to the client
const data = create_message_string({
data: JSON.stringify(body)
});
// send the data to all the subscribers
for (const item of subscribers) {
item.enqueue(data);
}
return new Response('Message sent to subscribers', { status: 200 });
};
Client-side code (+page.svelte or component)
<script>
import { onMount } from 'svelte';
// list of messages and the value of the input
let messages: string[] = [];
let value = '';
// subscribe to the SSE endpoint on mount
onMount(subscribe);
function subscribe() {
// create event source and listen to messages
const event_source = new EventSource('/sse');
event_source.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
// push the message to the list
messages = [...messages, data.value];
});
return () => event_source.close();
}
// send message to the server
async function send() {
const res = await fetch(`/sse`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
value
})
});
if (!res.ok) throw Error('Error');
value = '';
}
</script>
<form on:submit|preventDefault={send}>
<input type="text" bind:value />
<button type="submit">Send</button>
</form>
<ol>
{#each messages as message}
<li>{message}</li>
{/each}
</ol>
Keep message history on the server
// Add to the top of +server.ts
const messageHistory: string[] = [];
// Add right after subscribers.add()
messageHistory.forEach((element) => {
controller.enqueue(element);
});
// Add before "for" inside POST
messageHistory.push(data);
Create rooms
const rooms = new Map(); // [{roomId: '123', users: [1,2,3]}]
// User room1
let connection1 = "connectionData1"
const room1 = rooms.get('room1') || new Set()
room1.add(connection1)
rooms.set('room1', room1)
// User 1 disconnect
const room11 = rooms.get('room1') || new Set()
room11.delete(connection1)
if(room11.size === 0) rooms.delete('room1')
Auth
- Return a
connection id
when the user connects - send a post request to an endpoint with the following data:
user credentials (session id)
,connection id
,events
to subscribe to
type Subscriber = {
uuid: string;
controller: ReadableStreamDefaultController;
};
const subscribers = new Set<Subscriber>();
// on connect
subscriber = { uuid: crypto.randomUUID(), controller };
subscribers.add(subscriber);
// on cancel
subscribers.delete(subscriber);
// send data
const subscriber = Array.from(subscribers).find((s) => s.uuid === body.id);
subscriber.controller.enqueue(`data: hello\n\n`);
Subscribe to a specific event
On the server specify an event name (id)
controller.enqueue('event: id\n');
controller.enqueue(`data: hello\n\n`);
// you can also additionaly send over a unique id for every message
controller.enqueue(`id: some uuid\n\n`);
On the client listen to it like this
const sse = new EventSource('/sse');
sse.addEventListener('id', (event) => {
// do something with event.data
});
(default type is message)
Cloudflare Issues
If you face any issues with timeouts read this:
Are Server-sent events SSE supported, or will they trigger HTTP 524 timeouts?
You can send :ping\n\n
if you need to send a ping/heartbeat every couple of seconds