UNPKG

method-web-socket-expose

Version:

Method WebSocket. A simple wrap on top of websocket for client and server communication by serving methods in both sides in an asynchronous response.

531 lines (489 loc) 12.7 kB
var BufferStream = require('./BufferStream'); var http = require('http'); var https = require('https'); var WebSocketClient = require('./client'); var WebSocketServer = require('ws').Server; function isValid(obj, props) { if (typeof obj != 'object') return false; var exist=0; props.map(prop=>{ if(obj[prop]!=undefined) exist++; }); if (exist < props.length) return false; return true; } var ws = (function () { /** * PRIVATE SINGLETON **/ var WS = { servers: {}, getServer:function (name) { return this.servers[name]; } }; function getServer(name) { //console.log(WS.servers) return WS.servers[name]; } function setWsServer(name, wsServer) { if (!name) do{ name= "server-"+randomString(4); }while(!name); WS.servers[name] = wsServer; } /** * GENERAL METHODS * **/ function randomString(length) { var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.split(''); if (! length) { length = Math.floor(Math.random() * chars.length); } var str = ''; for (var i = 0; i < length; i++) { str += chars[Math.floor(Math.random() * chars.length)]; } return str; } /** * CONSTRUCTOR WsClients * new WsClients() **/ function WsClients(server) { this.server = server; this.storage = {}; this.indexs = []; this._events= { remove:[], finished: [], close:[], create:[] } } WsClients.prototype = { get length(){ return this.indexs.length; } } WsClients.prototype.forEach = function (_fn) { if(typeof _fn !== 'function') return null; for (var i = 0; i < this.indexs.length; i++) { _fn(this.storage[ this.indexs[i] ]); } } WsClients.prototype.map = function (_fn){ if(typeof _fn !== 'function') return null; var res = []; for (var i = this.indexs.length - 1; i >= 0; i--) { res.push( _fn(this.storage[ this.indexs[i] ]) ); } return res; } WsClients.prototype.find = function (_fn){ if(typeof _fn !== 'function') return null; for (var i = this.indexs.length - 1; i >= 0; i--) { var res = _fn(this.storage[ this.indexs[i] ]); if (res) { return this.storage[ this.indexs[i] ]; } } } WsClients.prototype.findIndex = function (_fn){ if(typeof _fn !== 'function') return null; for (var i = this.indexs.length - 1; i >= 0; i--) { var res = _fn(this.storage[ this.indexs[i] ]); if (res) { return this.indexs[i]; } } } WsClients.prototype.add = function (wsClient) { var index = randomString(5); if (this.storage[index]) { console.log("Error:addWsClient. err:index exist#62"); return null; } this.indexs.push(index); wsClient["identificator"] = index; this.storage[index] = wsClient; wsClient.wsServer = this.server; this.dispatchEvent("create", wsClient); return index; } WsClients.prototype.removeIn = function (id) { this.indexs.splice(this.indexs.indexOf(id), 1); delete this.storage[id]; return true; } WsClients.prototype.remove = function (wsClient) { console.log("--- REMOVE(wsClient) ---"); if (typeof wsClient !== 'object') { console.log("Error. remove. err:wsClient not valid #83"); return null; } var id = this.findIndex(indexed=>{ return indexed == wsClient; }); if( !this.removeIn(id) ){ console.log("Error. remove. err:id wsClient #83"); return null; } this.dispatchEvent("remove", wsClient); return true; } WsClients.prototype.addEventListener = function addEventListener(eveName, _fn) { var events = this._events[eveName]; if (!events) { return 0; } events.push(_fn); return this; } WsClients.prototype.dispatchEvent = function dispatchEvent(eveName, wsClient) { var wsClients = this; switch(eveName){ case "close": clientEventController.call(this, wsClient, "close"); break; case "remove": clientEventController.call(this, wsClient, "remove"); break; case "create": clientEventController.call(this, wsClient, "create"); break; default: console.log("Invalid event: "+eveName); break; } } /** * CONSTRUCTOR RouterHandler * new RouterHandler(); **/ function RouterHandler() { this.name = "methods"; this.methods= {}; this.middlewares= []; } /* * ROUTER HANDLER, METHODS... */ RouterHandler.prototype.setMethod = function(name, fn) { if(typeof name !== 'string' || typeof fn !== 'function') return null; this.methods[name] = fn; return this; }; RouterHandler.prototype.setMiddleware = function(fn) { if(typeof fn !== 'function') return null; this.middlewares.push(fn); return this; }; /** * SERVER CONSTRUCTOR * new WsServer() **/ function WsServer(){ this.wsClients_index= 0, this.clients= new WsClients(this), this.routes= [], this.registerMethods= {}, this.methods= {}, this.middlewares= [], this.events= []; this.events.push({name:'connection', function: connection.bind(this)}); } // SETTER OF WS SOCKET EVENTS WsServer.prototype.on = function (eveName,fn) { if (typeof fn !== 'function') return null; this.events.push({ name:eveName, function:fn }); } // SETTER OF ROUTERS, FILL THE methods PROPERTY WsServer.prototype.use = function (router) { if (router.constructor == RouterHandler) { this.routes.push(router); for (var pro in router.methods) { this.registerMethods[pro] = router; this.methods[pro] = router.methods[pro]; }; for (var i = 0; i < router.middlewares.length; i++) { this.middlewares.push(router.middlewares[i]); } }else{ console.log('WS non register use to '+router.name); } } WsServer.prototype.setServer = function (server) { var ws; if (server instanceof http.Server || server instanceof https.Server) { ws = new WebSocketServer({ server:server }); }else if(isValid(server,['host','port'])){ ws = new WebSocketServer(server); }else{ return null; } for(var i in this.events){ ws.on(this.events[i].name, this.events[i].function); } /** * LINK THE websocket SERVER TO THE WsServer INSTANCE **/ ws.wsServer = this; } WsServer.prototype.start = function (server) { var ws; if (server instanceof http.Server || server instanceof https.Server) { ws = new WebSocketServer({ server:server }); }else if(typeof server == 'object'){ ws = new WebSocketServer(server); }else{ return null; } for(var i in this.events){ ws.on(this.events[i].name, this.events[i].function); } /** * LINK THE websocket SERVER TO THE WsServer INSTANCE **/ ws.wsServer = this; } /** * EVENTS CLIENT websocket * **/ function clientEventController(wsClient, eveName) { var events = this._events[eveName]; if (!events) { return 0; } for (var i = 0; i < events.length; i++) { events[i](wsClient); } } /** * CLIENT ACTIONS * **/ function json (data){ if (typeof data == 'object') { try{ this.send(JSON.stringify(data)); }catch(er){ console.log(er); } }; } // USE TO EXECUTE ANY FUNCTION ON THE CLIENT SIDE. function execute (methodName) { var wsClient = this; //this AIMS TO WS CLIEN CONNECTION var toSend = { methodName:methodName, arguments:[] }; if(arguments.length > 0){ for (var i = 1; i < arguments.length; i++) { toSend.arguments.push(arguments[i]); } } //console.log("###### EXECUTE"); //console.log(toSend) return wsClient.JPSend(toSend); } // CONNECTION HANDLER FOR NEW CLIENT CONNECTION function connection (wsClient) { var wsServer = this;// WsServer CONSTRUCTOR /* * ADD PROTOTYPE METHODS. */ wsClient.json = json; wsClient.JPSend = send; wsClient.execute = execute; /* * CLIENT EVENTS. */ wsClient.on('message', dataController); wsClient.on('close', closeController); wsClient.on('end', closeController); // STORE THE CLIENT CONNECTION. wsServer.clients.add(wsClient); } /* * EXECUTOR OF METHODS */ function trainedReturn(wsClient, taskId) { //console.log(" ->trainedReturn()"); var res = { taskId:taskId, arguments:[] } for (var i = 2; i < arguments.length; i++) { res.arguments.push(arguments[i]); } wsClient.JPSend(res); } function executeMiddlewares(wsClient, methodName, argumentsP, fn_done) { var middlewares = this.middlewares || WS.middlewares; var i = 0; var next = function () { if (i < middlewares.length) { i++; middlewares[i-1].apply(wsClient, argumentsP); }else{ argumentsP.splice(0,2); if(fn_done) fn_done(); } }; argumentsP.unshift(methodName); argumentsP.unshift(next); next(); } function methodHandler(taskId, methodName, argumentsP, defaultParams) { var wsClient = this; var wsServer = this.wsServer; // VALIDATE THE PARAMS if (typeof methodName!='string' || argumentsP.constructor != Array || typeof taskId!= 'number') { console.log('Invalid data'); var res = { taskId:taskId, error:'404' } wsClient.send(res); return 0; }; // METHOD UNDEFINED if (!wsServer.registerMethods[methodName]) { console.log('--- Unexit wsClient method: '+methodName); console.log('* Expected: '); console.log(Object.getOwnPropertyNames(wsServer.methods)); var res = { taskId:taskId, error:'404' } wsClient.JPSend(res); return 0; }; argumentsP = argumentsP || []; var _trainedReturn = trainedReturn.bind(this, wsClient, taskId); if (defaultParams) wsClient.defaultParams = defaultParams; argumentsP.unshift(_trainedReturn); /* * EXECUTE MIDDLEWARE FIRST */ var route = wsServer.registerMethods[methodName]; var method = route.methods[methodName]; if (route.middlewares.length > 0) { executeMiddlewares.call(route, wsClient, methodName, argumentsP, function () { method.apply(wsClient, argumentsP); }); return 1; } method.apply(wsClient, argumentsP); } function syncMethods(taskId) { var wsClient = this; var wsServer = this.wsServer; var res = { taskId:taskId, arguments: [Object.getOwnPropertyNames(wsServer.methods)] } wsClient.JPSend(res); } /* * CLIENT SEND DATA */ function dataController(data) { var wsClient = this; var wsServer = this.wsServer; var data = BufferStream.readFromBuffer(data); //console.log('+++ DATA REQUEST +++'); //console.log(data); //VALIDATE THE STRUCTURE OF THE RESPONCE // RECIVING THE REQUEST OF EXECUTE A FUNCTION if (data.methodName == 'syncMethods') { syncMethods.call(wsClient, data.taskId); return 0; } if (data.methodName && data.arguments && data.taskId) { methodHandler.call(wsClient, data.taskId, data.methodName, data.arguments, data.defaultParams); }else{ console.log('Invalid communication.') } } /* * CLIENT DISCONECT */ function closeController() { var wsClient = this; var wsServer = this.wsServer; //REMOVE THE CONNECTION FROM THE ARRAY STORAGE CONNECTIONS. console.log("--- wsServer CLIENT DISCONNECTED ---"); console.log(" ->BY: closeController"); console.log(" ->Identificator: "+wsClient.identificator); try{ wsServer.clients.remove(wsClient); wsServer.clients.dispatchEvent("close", wsClient); }catch(err){ console.log(err); } } /* * SEND DATA TO THE CLIENT */ function send(dataObject) { if (typeof dataObject != 'object' || typeof dataObject === null) return null; var wsClient = this; return new Promise((resolve, reject)=>{ //dataObject['taskId'] = newTask.call(_this, resolve); var dataBuffer = BufferStream.createBuffer(dataObject); wsClient.send(dataBuffer, err=>{ if (err) { console.log("## WS AREADY DISCONNECTED ##"); console.log(err); console.trace("Error WebSocketClient") reject(err); }else{ resolve('Done.'); } }); }); } return { Router: function () { return new RouterHandler(); }, start: function (server, name) { var ws, wsServer = new WsServer(); setWsServer(name, wsServer); setTimeout(function(){ wsServer.setServer(server); }, 500); return wsServer; }, getServer: getServer, client: WebSocketClient } })(); module.exports = ws;