UNPKG

websocketiot

Version:

npm module for creating node.js-esp websocket IOT project

229 lines 9.85 kB
import { objify, getUniqueID, stringify, extension, fileName, isType, } from './common.js'; // !TODO: /* ! ! MAKE CODE WORK ON ES AND COMMONJS ! ! */ import http from 'http'; import express from 'express'; import nedb from 'nedb'; import { WebSocketServer } from 'ws'; import * as fs from 'fs'; //* connection object class Connection { // statics prop static scream = (msg) => { Object.keys(this.connections).forEach((connection) => { this.connections[connection].send(msg); }); }; static connections = {}; // store the connection static addConnection = (connection) => Connection.connections[connection.id] = connection; // syntax sugar to add connection static send = (ws, msg) => ws.send(stringify(msg)); static sendError = (ws, error) => ws.send(stringify({ message: "error", error })); static logRawFunc = (device, ...params) => console.dirxml(device + ':', ...params); static log = (...params) => this.logRawFunc('uknown', ...params); static attachMsgHandler = (ws, run, request, authenticated = () => false, connection) => { // TODO: make the "no request found msg" ws.on('message', (msg) => { const objMsg = objify(msg.toString()); if (objMsg instanceof Error) { this.sendError(ws, "request must be in json format!"); return; } if (authenticated() != (objMsg.message != Connection.msgLiterals.auth.message)) { Connection.sendError(ws, authenticated() ? `this device already connected as ${connection?.device}. an relogin attemp is forbids` : "please authenticate this connection before preceeding"); connection?.log("reloggin attemp"); return; } if (request && (objMsg.message != request)) return; //* in case you forgot again, the `request` parameter is the "id" for the request handler. so if its not the correct request it will return like in below if (!this.checkFormat(objMsg, this.msgLiterals.basic)) return; run(objMsg, connection); }); }; static checkFormat = (req, type, ws) => { var formated = isType(req, type); if (!formated && ws) this.sendError(ws, 'bad format, format is ' + stringify(type)); return formated; }; // instence prop ws; id; connectionType = 'uknown'; connection = this; isDecoy; get device() { return this.connectionType + ":" + this.id; } ; saveData; data; run; log(...params) { Connection.logRawFunc(this.device, ...params); } ; send(msg) { if (!this.isDecoy) this.ws.send(stringify(msg)); } ; sendError(error) { if (!this.isDecoy) Connection.sendError(this.ws, error); } ; attachMsgHandler(run, request) { Connection.attachMsgHandler(this.ws, run, request, () => true, this.connection); } ; checkFormat(req, type, sendError = true) { return Connection.checkFormat(req, type, sendError ? this.ws : undefined); } constructor(ws, run, isDecoy = false, id = getUniqueID(), saveData = false, connectionType, data = undefined) { this.ws = ws; this.run = run; this.isDecoy = isDecoy; this.id = id; this.saveData = saveData; this.data = data; if (connectionType) this.connectionType = connectionType; Connection.addConnection(this); this.attachMsgHandler(this.run); // TODO: function to save in the db // TODO: ws terminator // TODO: function to delete or add this db } // message instance literal static msgLiterals = { basic: { message: '' }, auth: { type: 'uknown', message: 'authRequest' }, }; } // subClass client, for client class Client extends Connection { // overrides connection = this; connectionType = 'client'; static scream = (msg) => { Object.keys(this.clients).forEach((connection) => { this.clients[connection].send(msg); }); }; // static prop static clients = {}; // store the connection static addClient = (client) => Client.clients[client.id] = client; // syntax sugar to add connection static clientHandler = async (req, connection) => { connection?.log("client code is undefined"); }; constructor(ws, id, run) { // client handler super(ws, Client.clientHandler, false, id); Client.addClient(this); } } // subClass device, for robots class Device extends Connection { // overrides connection = this; connectionType = 'device'; get device() { return this.deviceKind + ":" + this.id; } ; static scream = (msg, deviceKind) => { Object.keys(this.devices).filter((val) => this.devices[val].deviceKind === deviceKind).forEach((device) => this.devices[device].send(msg)); }; // static prop // list of devices type static DeviceKinds = {}; static addDeviceKinds = (deviceClass) => Device.DeviceKinds[deviceClass.name] = deviceClass; static devices = {}; // store the connection static addDevice = (device) => Device.devices[device.id] = device; // syntax sugar to add connection constructor(ws, run, id, saveData = false) { super(ws, run, false, id, saveData); Device.addDevice(this); } } class IOTServer { port = 8080; app = express(); server = http.createServer(this.app); wss = new WebSocketServer({ server: this.server }); ; dbPath = `./src/db/iot.db`; db = new nedb(); saveDB = false; usePublic = false; publicPath = `./src/public`; useRouter = false; routersPath = `dist/router`; //in folder initOnStart = true; init = () => this.server.listen(this.port, () => { console.dir(`server is up on port ${this.port}!`); }); //start ws server; runExtraAuth = () => { }; extraAuth(run) { this.runExtraAuth = run; } constructor(option = {}) { for (const [key, value] of Object.entries(option)) if (value !== undefined) this[key] = value; const { port, app, server, wss, dbPath, db, saveDB, usePublic, publicPath, useRouter, routersPath, initOnStart } = this; if (this.saveDB) // if the saveDb is true, then save it { this.db = new nedb({ filename: dbPath, autoload: true }); db.persistence.setAutocompactionInterval(5000); } db.loadDatabase(); if (usePublic) //if its use public folder, then use it app.use(express.static(publicPath)); // listen to public folder as all express should do if (useRouter) //use all the router in the routerPath fs.readdir(routersPath, (err, files) => { if (err) { console.warn(err); return; } files.forEach((file) => { if (extension(file) !== 'js') return; import(`${"file://" + routersPath.replaceAll("\\", "/").replaceAll(" ", "%20")}/${file}`).then(module => app.use(`/${fileName(file)}`, module.router)); }); }); if (initOnStart) this.init(); // use auth protocol on websocket wss.on('connection', (ws) => this.authProtocol(ws, this)); } // authenticate who is connecting authProtocol(ws, server) { Connection.log("websocketiot connection been made"); // is the connection already authenticated or not var connection; var authenticated = false; // send the identification request just in case const identifyMsg = { message: "auth is requested" }; Connection.send(ws, identifyMsg); Connection.attachMsgHandler(ws, (req) => { if (connection) return; Connection.log(req); if (!Connection.checkFormat(req, Connection.msgLiterals.auth, ws)) return; // authenticate the connection // if its an client switch (req.type) { case 'client': connection = new Client(ws, req.id); break; case 'device': if (!req.deviceKind) { // *badRequest Connection.sendError(ws, 'device kind is not specified'); return; } // checking if the device type is legit let typeFound = false; Object.keys(Device.DeviceKinds).forEach((key) => { if (key === req.deviceKind) typeFound = true; }); if (!typeFound) return; connection = new Device.DeviceKinds[req.deviceKind](ws, req.id); break; default: Connection.sendError(ws, 'device type is not valid, device type avilable is "client" or "device" only'); return; } ; connection.log("connected as ", connection.connectionType); server.runExtraAuth(); connection.send({ message: `connected as ${connection.device}`, id: connection.id }); authenticated = true; }, Connection.msgLiterals.auth.message, () => authenticated); } } export { IOTServer, Connection, Client, Device }; export default IOTServer; // const Server = (new IOTServer({ usePublic: true })); //# sourceMappingURL=index.js.map