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
JavaScript
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;