Skip to content

Commit

Permalink
Enhanced and commented code
Browse files Browse the repository at this point in the history
  • Loading branch information
diraneyya committed Sep 28, 2022
1 parent 367cd85 commit 5696d87
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 8 deletions.
28 changes: 22 additions & 6 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,39 @@ const app = Fastify({
logger: false,
});

// Use CORS and SSE plugins
await app.register(cors, {});
await app.register(sse);

// The SseEventDispatcher class maintains a list of active
// SSE connections and allows you to dispatch an event string
// to all the connected clients at once when needed.
// Please remember to call the "connect" method on the object
// with the Fastify reply object when your SSE endpoint is
// invoked.
const sseEventDispatcher = new SseEventDispatcher();

// This is the SSE endpoint we use to receive Server-Side events
// in the browser (check the index.html file).
app.get("/events", (req, reply) => {
sseEventDispatcher.connect(reply)
});

// Using the event dispatcher object defined above, we can use
// the root endpoint to dispatch the string 'ROOT_ACCESSED' as
// an event using SSE.
app.get("/", (req, reply) => {
sseEventDispatcher.dispatch('ROOT_ACCESSED');
reply.send({ success: true, active_clients: sseEventDispatcher.length });
reply.send({ success: true, active_clients: sseEventDispatcher.connections });
});

// Using the event dispatcher object defined above, we can use
// all other accesses to dispatch the string 'UNKNOWN_ENDPOINT'
// as an event using SSE.
app.get("/*", (req, reply) => {
sseEventDispatcher.dispatch('UNKNOWN_ENDPOINT');
reply.send({ success: false, active_clients: sseEventDispatcher.length });
reply.send({ success: false, active_clients: sseEventDispatcher.connections });
})

app.get("/events", async (req, reply) => {
sseEventDispatcher.connect(reply)
});

// The server waiting loop.
await app.listen({ port: 3000 });
19 changes: 17 additions & 2 deletions utilities.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
export class SseEventDispatcher {
// Private property: stores a dynamic array to connected clients
// An item in this array is a reply object in Fastify.
// (https://www.fastify.io/docs/latest/Reference/Reply/)
#clients = [];

// This private method sneaks into the "hidden" symbol property
// of the reply object which hides the inner details of the SSE
// plugin including the stream and whether it is still readable.
// This code was written by reverse-engineering the reply object
// in the debugger.
#isSseStreamReadable(reply) {
let sseProp = Object.getOwnPropertySymbols(reply)
.find(sym => sym.toString() === 'Symbol(sse)');

return (sseProp !== undefined && reply[sseProp]?.stream?.readable === true);
}

// The main method of this class. Dispatches an event string to
// all connected clients and removes disconnected ones from the
// clients array using the Array.prototype.filter method.
dispatch(event) {
this.#clients = this.#clients.filter(reply => {
if (!this.#isSseStreamReadable(reply))
Expand All @@ -18,13 +29,17 @@ export class SseEventDispatcher {
});
}

get length() {
// Returns the number of active connections, note that this count
// is only accurate immediately after calling "dispatch".
get connections() {
return this.#clients.length;
}

// Adds a new connection when the SSE endpoint is invoked. Pass the
// Fastify reply object to this method.
connect(reply) {
this.#clients.unshift(reply);
// Send the first data
// Send the first data, which contains the options (check the docs)
reply.sse('STREAM_STARTED', {});
}
}
Expand Down

0 comments on commit 5696d87

Please sign in to comment.