UNPKG

@showcomposer/broker

Version:

communication core for ShowComposer

262 lines (261 loc) 9.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); // SC broker // Init global variables const config = { port: 6789 }; const subscriber = {}; const sendTypes = ["INIT", "INIT_REUSE", "PING", "SET", "ASSIGN", "DEL", "SUB", "UNSUB", "DUMP", "SMSG"]; const responseTypes = ["INIT_ACK", "PONG", "SET_RES", "ASSIGN_RES", "DEL_RES", "SUB_RES", "UNSUB_RES", "DUMP_RES"]; const dataTypes = ["STATIC", "LIVE", "TICK", "LINK"]; const net = require("net"); const readline = require("readline"); const uuid = require("uuid/v4"); const data_1 = require("./data"); // Logging const nodelogging_1 = require("@hibas123/nodelogging"); const data = new data_1.SCData(); // Client class, handling incoming connections class Client { // Constructor run by every new connection constructor(socket) { this.reqId = 0; this.reqWait = 0; this.reqArray = {}; this.subscriptions = []; this.uuid = uuid(); this.socket = socket; // Setup regular ping this.pingInt = setInterval(() => { this.ping(); }, 2000); // Prepare Input processing this.inputReader = readline.createInterface({ input: this.socket, }); this.inputReader.on("line", (l) => { this.handleLine(l); }); // Add this receiver to subscibers subscriber[this.uuid] = this; // Initialize obj in data data.setPlain("LIVE", "system.connections." + this.uuid + ".state", "UP"); data.setPlain("LIVE", "system.connections." + this.uuid + ".time_established", Date.now()); this.socket.setKeepAlive(true); this.socket.setNoDelay(); // Handle closing socket.on("close", () => { nodelogging_1.Logging.log("Con. " + this.uuid + " closed"); this.close(); }); socket.on("error", (err) => { nodelogging_1.Logging.error(err); this.destroy(); }); } // Periodically Ping ping() { const start = process.hrtime.bigint(); this.send("PING", "", (c) => { if (c[1] === "PONG") { this.pingResponse = (process.hrtime.bigint() - start) / BigInt(1000); this.pingSuccess = Date.now(); nodelogging_1.Logging.debug("Ping: " + this.pingResponse + "\xB5s"); data.setPlain("LIVE", "system.connections." + this.uuid + ".latency", this.pingResponse); data.setPlain("LIVE", "system.connections." + this.uuid + ".last_ping", this.pingSuccess); } }); } // Close everything close() { // unsubscribe from all subscriptions this.subscriptions.forEach((s) => { data.unsub(s); }); delete subscriber[this.uuid]; clearInterval(this.pingInt); if (!this.socket.destroyed) { this.socket.destroy(); } data.setPlain("LIVE", "system.connections." + this.uuid + ".state", "CLOSED"); data.setPlain("LIVE", "system.connections." + this.uuid + ".time_closed", Date.now()); } destroy() { // unsubscribe from all subscriptions this.subscriptions.forEach((s) => { data.unsub(s); }); delete subscriber[this.uuid]; clearInterval(this.pingInt); if (!this.socket.destroyed) { this.socket.destroy(); } data.setPlain("LIVE", "system.connections." + this.uuid + ".state", "DESTROYED"); data.setPlain("LIVE", "system.connections." + this.uuid + ".time_closed", Date.now()); } // Handle input handleLine(c) { const m = c.toString("utf8").split(" "); if (m.length < 2) { return; } const id = parseInt(m[0], 10); if (isNaN(id)) { return; } if (!m[2]) { m[2] = ""; } // Determine if it's new req or response if (responseTypes.includes(m[1])) { // It's a response // Check if id exists and handle cb if (this.reqArray[m[0]]) { this.reqArray[m[0]](m); delete this.reqArray[m[0]]; } } if (sendTypes.includes(m[1])) { // it's a req let res = false; let ret; switch (m[1]) { case "SET": if (m[4] === "1") { res = true; } // m[2] is data types if (!dataTypes.includes(m[2])) { if (res) { this.sendRes(id, "SET_RES", "E INVALID_TYPE"); } nodelogging_1.Logging.warning("SET_RES " + id + " E INVALID_TYPE"); return; } // m[3] is necessary as it contains data if (!m[3]) { if (res) { this.sendRes(id, "SET_RES", "E NO_DATA"); } nodelogging_1.Logging.warning("SET_RES " + id + " E NO_DATA"); return; } // Execute Set command and return/log response. ret = data.set(m[2], m[3]); if (res) { this.sendRes(id, "SET_RES", ret); } break; case "ASSIGN": if (m[5] === "1") { res = true; } // m[2] is data types if (!dataTypes.includes(m[2])) { if (res) { this.sendRes(id, "ASSIGN_RES", "E INVALID_TYPE"); } nodelogging_1.Logging.warning("ASSIGN_RES " + id + " E INVALID_TYPE"); return; } // m[3] is necessary as it contains key if (!m[3]) { if (res) { this.sendRes(id, "ASSIGN_RES", "E NO_KEY"); } nodelogging_1.Logging.warning("ASSIGN_RES " + id + " E NO_KEY"); return; } // m[4] is necessary as it contains value if (!m[4]) { if (res) { this.sendRes(id, "ASSIGN_RES", "E NO_VALUE"); } nodelogging_1.Logging.warning("ASSIGN_RES " + id + " E NO_VALUE"); return; } // Execute Set command and return/log response. ret = data.assign(m[2], m[3], m[4]); if (res) { this.sendRes(id, "ASSIGN_RES", ret); } break; case "SUB": // Check if key is present if (!m[2]) { nodelogging_1.Logging.warning("SUB_RES " + id + " E NO_KEY"); return; } // this needed as t const s = data.sub(m[2], this.subs, this); this.subscriptions.push(s.t); this.sendRes(id, "SUB_RES", s.id.toString()); break; case "DUMP": // Check if key is present if (!m[2]) { nodelogging_1.Logging.warning("DUMP_RES " + id + " E NO_KEY"); return; } const d = data.dump(m[2]); this.sendRes(id, "DUMP_RES", d); case "UNSUB": if (!m[2]) { nodelogging_1.Logging.warning("UNSUB " + id + " E NO_ID"); return; } if (data.unsubId(m[2]) === true) { this.sendRes(id, "UNSUB_RES", "0 OK"); } else { this.sendRes(id, "UNSUB_RES", "1 ERR"); } break; } } // Else: drop } // Build pkg send(type = "PING", payload = "", cb = (res) => undefined) { try { this.reqId++; this.socket.write(this.reqId + " " + type + " " + payload + "\r\n"); this.reqArray[this.reqId] = cb; } catch (e) { nodelogging_1.Logging.error(e); this.destroy(); } } // Build pkg sendNoResRaw(payload = "") { try { this.reqId++; this.socket.write(this.reqId + " " + payload + "\r\n"); } catch (e) { nodelogging_1.Logging.error(e); this.destroy(); } } // Build pkg res sendRes(id = 0, type = "PONG", payload = "") { try { this.socket.write(id + " " + type + " " + payload + "\r\n"); } catch (e) { nodelogging_1.Logging.error(e); this.destroy(); } } // Subscriber for all changes subs(m, d, id, t) { t.sendNoResRaw("SMSG " + id + " " + d); } } // Create Server const server = new net.Server(); server.on("connection", (s) => { const c = new Client(s); }); server.listen(config.port, () => { nodelogging_1.Logging.log("Listening on port " + config.port); process.send({ status: "listening" }); });