@steelbreeze/broker
Version:
Lightweight publish and subscribe using Server-Sent Events for node and express
95 lines (80 loc) • 3.58 kB
text/typescript
import * as http from 'http';
const EventSource = require('eventsource');
/** Specifies the location of a message broker server. */
export interface HTTPConfig {
/** The host that the message brokers express application is running on. */
host: string;
/** The port of the host that the message brokers express application is running on. */
port: number;
/** The base URL of the message broker. */
path: string;
}
export interface ErrorHandler {
(err: Error): void;
}
// A message provide by the server using Server-Sent Events
interface ServerMessage {
type: string;
data: string;
lastEventId: number;
origin: string;
}
/** A message provided to a subscriber callback */
export interface ClientMessage {
/** The topic name that the message was published on. */
topicName: string;
/** The message broker server allocated id for the message; monotonically increments per topic. */
id: number;
/** The data published by the publisher. */
data: string;
}
export interface ClientCallback {
(message: ClientMessage): void;
}
/**
* Returns the client API to the message broker, providing publish and subscribe operations.
* @param config Configuration specifying the server, port and base URL path of the message broker server.
* @returns Returns a client providing publish and subscribe operations.
*/
export function client(config: HTTPConfig) {
return {
/**
* Publishes a message to a message broker for other client to receive based on their subscriptions.
* @param topicName The topic to publish on. This may be one or more URL segments.
* @param data The data to publish on the topic
* @param onError Optional error handler callback
*/
publish: (topicName: string, data: string, onError: ErrorHandler | undefined = undefined): void => {
var post = http.request({ hostname: config.host, port: config.port, path: `${config.path}/${topicName}`, method: 'POST', headers: { 'Content-Type': 'text/plain', 'Content-Length': Buffer.byteLength(data) } });
// trap and propigate error messages as required
post.on('error', (err: Error) => {
if (onError) {
onError(err);
}
});
// send message to the server
post.write(data);
post.end();
},
/**
* Registers a subscription to messages on the given topic. The callback is called each time a message to published on the same topic.
* @param topicName The topic to subscribe to. This may be one or more URL segments.
* @param callback The function to call when data is publised on the topic; passing a message object.
*/
subscribe: (topicName: string, callback: ClientCallback | undefined): void => {
var eventSource = new EventSource(`http://${config.host}:${config.port}${config.path}/${topicName}`);
var lastEventId = -1;
// process messages from the server
eventSource.onmessage = (event: ServerMessage): void => {
// perform a duplicate message check
if (event.lastEventId !== lastEventId) {
lastEventId = event.lastEventId;
// propigate message to the client
if (callback) {
callback({ topicName: topicName, id: event.lastEventId, data: event.data });
}
}
}
}
}
}