UNPKG

celeritas

Version:

This is an API service framework which supports API requests over HTTP & WebSockets.

184 lines (156 loc) 5.04 kB
'use strict'; const ws = require('ws'); const uuid = require('uuid'); const validator = require('validator'); const bluebird = require('bluebird'); const CallUtilities = require("./utilities.js"); class CeleritasWS { constructor (app, httpServer) { this.app = app; this.server = new ws.Server({server: httpServer}); this.server.on('connection', (client) => { this.onConnection(client); }); return this; } onConnection (client) { client.id = uuid(); client.ip = client.upgradeReq.headers['x-forwarded-for'] || client._socket.remoteAddress; client.subscriptions = client.subscriptions || []; client.authorization = null; client.user = null; client.subscribe = (id, value = null) => { client.subscriptions.push((value !== null) ? {[id]: value} : id); //When a ws client subscribes to an event, attempt to replay any events they may have missed. this.app.replay(client); return true; } client.unsubscribe = (id, value = null) => { if (value === null) { var index = client.subscriptions.indexOf(id); if (index !== -1) client.subscriptions.splice(client.subscriptions.indexOf(id), 1); return (index !== -1); } else { var removed = false; for (var i in client.subscriptions) { if (JSON.stringify(client.subscriptions[i]) == JSON.stringify({[id]: value})) { client.subscriptions.splice(i, 1); removed = true; } } return removed; } } client.event = (subscription, event) => { var output = { subscription: subscription, type: "EVENT", event: event, timeUtc: new Date }; if (client.readyState === ws.OPEN) { return new Promise((resolve, reject) => { client.send(JSON.stringify(output), (err) => { resolve(!err); }) }) } else return Promise.resolve(false); } client.on('message', (message) => { this.onMessage(client, message); }); client.on('close', () => { this.app._log.info("PID " + process.pid + ": [WS] Client ['" + client.id + "'] has disconnected."); }) this.app._log.info("PID " + process.pid + ": [WS] Client ['" + client.id + "'] has connected."); //When a client connects, attempt to replay any events they may have missed. this.app.replay(client); return client; } onMessage (client, message) { try { var message = JSON.parse(message); } catch (err) { this.app._request("ws", new Error("Websocket messages must be formatted as valid JSON!"), client); return; } CallUtilities.validate("native", "WebsocketMessage", { id: validator.isUUID, route: String, get: {type: Object, required: false}, post: {type: Object, required: false}, authorization: {type: String, required: false}, compression: {type: String, required: false}, cache: {type: Number, required: false}, returnInput: {type: Boolean, required: false} }, message) .then(async (message) => { //message may be instanceof Error. let call = await this.app._request("ws", message, client); return this.app._defaultCompleteHandler(call); }); } broadcast (subscription, event) { return new Promise((resolve, reject) => { var results = []; var status = { checked: 0, number: this.server.clients.length }; if (status.number == 0) resolve(results); for (var i in this.server.clients) { ((client) => { var isSubscribed = (subscription == "*"); if (!isSubscribed) { switch (typeof subscription) { case "string": { isSubscribed = (client.subscriptions.indexOf(subscription) !== -1); break; } case "object": { for (var i in client.subscriptions) { if (typeof client.subscriptions[i] == "object") { //if the subscription key:value pair matches perfectly, they are subscribed. if (JSON.stringify(client.subscriptions[i]) == JSON.stringify(subscription)) isSubscribed = true; //If either the client has key:* or the event has subcription key:*, then that client will receive the event. var key = Object.keys(subscription)[0]; if (subscription[key] == "*" && key in client.subscriptions[i]) isSubscribed = true; if (client.subscriptions[i][key] == "*" && key in subscription) isSubscribed = true; } } break; } } } var isDone = (status, results) => { status.checked++; if (status.checked == status.number) resolve(results); } if (isSubscribed) { client.event(subscription, event) .then((result) => { if (result === true) { results.push(client); this.app._log.info("PID " + process.pid + ": [WS] Succcessfully broadcasted message for subscription " + JSON.stringify(subscription) + " to client '" + client.id + "'."); } isDone(status, results); }) } else isDone(status, results); })(this.server.clients[i]); } }) } } module.exports = CeleritasWS;