UNPKG

@steelbreeze/broker

Version:

Lightweight publish and subscribe using Server-Sent Events for node and express

83 lines (67 loc) 2.72 kB
import { Router, Request, Response } from 'express'; // internal interface used to manage the content associated with a topic interface Topic { lastEventId: number; data: string | undefined; subscribers: Response[]; } // internatl interface used to manage a dictionary of topics keyed on topic name interface Topics { [id: string]: Topic; } // find or create a topic given its name function getTopic(topics: Topics, topic: string): Topic { return topics[topic] || (topics[topic] = { lastEventId: -1, data: undefined, subscribers: [] }); } // write a single message to a client function sendEvent(client: Response, eventId: number, data: string): void { setImmediate((client: Response, eventId: number, data: string) => { client.write(`id:${eventId}\ndata:${data}\n\n`); }, client, eventId, data); } /** * Creates an instance of a message broker server. * Many message broker servers may be created, each bound to a different base url. * @param cacheLastMessage When true, subscribers will receive the last message upon subscrition. * @returns Returns an express Router for use within an express application. */ export function server(cacheLastMessage: boolean = false): Router { const router = Router(); const topics: Topics = {}; // GET method is used to subscribe by EventSource clients router.get('*', (req: Request, res: Response) => { var topic = getTopic(topics, req.url); // remove the subscription when the connection closes req.on('close', () => { topic.subscribers.splice(topic.subscribers.indexOf(res), 1); }); // create the subscription topic.subscribers.push(res); // set the response headers to specify this is an event stream res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // update the client with the last message if required if (cacheLastMessage && topic.data) { sendEvent(res, topic.lastEventId, topic.data); } }); router.post('*', (req: Request, res: Response) => { var topic = getTopic(topics, req.url); var body: Array<Buffer> = []; // read the post body req.on('data', (chunk: Buffer): void => { body.push(chunk); }); req.on('end', (): void => { // update the topic with the new event details topic.data = Buffer.concat(body).toString(); topic.lastEventId = (topic.lastEventId === Number.MAX_VALUE ? -1 : topic.lastEventId) + 1; // queue dispatch of event to all current subscribers topic.subscribers.forEach((subscriber) => { sendEvent(subscriber, topic.lastEventId, topic.data!); // NOTE: as we have just set topic.data we know it's not undefined }); }); // send response to publisher res.sendStatus(200); }); return router }