UNPKG

nexusse

Version:

A Pub/Sub HUB for Server-Sent Event

193 lines (149 loc) 5.41 kB
const Express = require("express") const bodyParser = require("body-parser") const cors = require("cors") const events = require('events') const Subscriber = require('./Subscriber') const Subscribers = require('./Subscribers') const defaultConfig = require('./config') const appName = 'Nexusse' // Mandatory headers and http status to keep connection open const httpResponseHeaders = { 'Content-Type': 'text/event-stream', 'Connection': 'keep-alive', 'Cache-Control': 'no-cache' } let nexusse = undefined function createNexusse(config = null) { nexusse = new NexusseCore(config) } class NexusseCore { constructor(config = null) { this.config = defaultConfig if (config && (typeof config !== 'number')) { if (config.constructor === ({}).constructor) { for (const property in config) { // noinspection JSUnfilteredForInLoop this.set(property, config[property]) } } } // Main app this.app = Express() // Create an object of EventEmitter class from events module this.eventEmitter = new events.EventEmitter() // Object for managing the list of subscribers this.subscribers = new Subscribers(this.config) // Set cors and bodyParser middleware this.app.use(cors()) this.app.use(bodyParser.json()) this.app.use(bodyParser.urlencoded({ extended: false })) // Define endpoints this.app.post('/publish', bodyParser.json(), this.publish.bind(this)) this.app.get('/subscribe', this.subscriptionHandler.bind(this)) this.app.get('/status', ((req, res) => res.json(JSON.stringify(this.subscribers.status())))) this.startKeepAliveTimer() } get(option) { switch (option) { default: return this.config.get(option) } } set(option, value) { switch (option) { case 'keepAliveInterval': if (typeof value !== "number") { return } let intValue = parseInt(value) if (!intValue || intValue < 5) { return } this.config.set(option, value) this .stopKeepAliveTimer() .startKeepAliveTimer() break default: this.config.set(option, value) } return this } startKeepAliveTimer() { // Try to keep the subscribers connected this.keepAliveTimer = setInterval(() => { this.eventEmitter.emit('keep-alive') }, this.get('keepAliveInterval') * 1000) return this } stopKeepAliveTimer() { // Try to keep the subscribers connected clearInterval(this.keepAliveTimer) return this } subscriptionHandler(req, res) { // Write the response header to keep the connection open res.writeHead(200, httpResponseHeaders) let subscriberId = (new Date()).getTime().toString() + Math.random() * 1000000000 let subscriber = new Subscriber(this.config, subscriberId, res, req.query.topics || []) // Create a new client object to be added to the clients map. this.subscribers.add(subscriber) const keepAliveListener = () => { subscriber.keepAlive() } req.on('close', () => { this.subscribers.remove(subscriber) this.eventEmitter.off('keep-alive', keepAliveListener) }) this.eventEmitter.on('keep-alive', keepAliveListener) res.write(`data:connected\n\n`) } // Middleware for PORT /publish endpoint async publish(req, res) { const publishPayload = req.body console.log(req.body) try { this.subscribers.notify(publishPayload) } catch (nexusseError) { console.error(`${nexusseError.message} (code: ${nexusseError.code}). The notification was not sent.`) res.writeHead(nexusseError.code || 500, (nexusseError.code && nexusseError.message) || undefined) res.end() return } res.writeHead(200) res.end() } listen(port = null, options = null) { let defaultOptions = () => console.log(`${appName} server listening on port ${this.get('port')}`) let _port = port || this.get('port') // If the user has chosen a port at the time of listening // for connections, then override the configuration port // in the configuration object. this.set('port', _port) this.app.listen(_port, options || defaultOptions()) } } class NexusssApi { constructor(config = null) { createNexusse(config) } // noinspection JSUnusedGlobalSymbols startKeepAliveTimer() { return nexusse.startKeepAliveTimer() } // noinspection JSUnusedGlobalSymbols stopKeepAliveTimer() { return nexusse.stopKeepAliveTimer() } get(option) { return nexusse.get(option) } // noinspection JSUnusedGlobalSymbols set(option, value) { return nexusse.set(option, value) } listen(port = null, options = null) { nexusse.listen(port, options) } } module.exports = NexusssApi