celeritas
Version:
This is an API service framework which supports API requests over HTTP & WebSockets.
184 lines (156 loc) • 5.04 kB
JavaScript
;
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;