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.

517 lines (483 loc) 13.7 kB
var BufferStream = require('./BufferStream'); var WebSocket = require('ws'); /* * MODULE EVENTS HANDLER * OBJECT -> jEvents {} * METHODS -> addEvent(HTMLElemet, eventName, function); */ function isValid(obj, props) { if (obj === null || typeof obj != 'object') return false; var exist=0; props.map(prop=>{ if(obj[prop]!=undefined) exist++; }); if (exist < props.length) return false; return true; } function msToSeg(ms) { return (ms/1000); } var wsClient = (function () { function generateInterval (k) { var maxInterval = (Math.pow(2, k) - 1) * 1000; if (maxInterval > 30*1000) { maxInterval = 30*1000; // If the generated interval is more than 30 seconds, truncate it down to 30 seconds. } // generate the interval to a random number between 0 and the maxInterval determined from above return Math.random() * maxInterval; } function init () { // ADD THE EVENTS this.ws.onopen = openController.bind(this); this.ws.onmessage = dataController.bind(this); this.ws.onclose = closeController.bind(this); this.ws.onerror = errorController.bind(this); return this; } function callMethod (name) { var _this = this; var toSend = { methodName:name, arguments:[] }; if(arguments.length > 0){ for (var i = 1; i < arguments.length; i++) { toSend.arguments.push(arguments[i]); } } return send.call(this, toSend); } function syncMethods () { return send.call(this,{ methodName:'syncMethods' }).then(methodsR => { for (var i in methodsR) { var methodName = methodsR[i]; // DEFINING THE REFLEC OF METHOD this[methodName] = callMethod.bind(this, methodName); } return this; }); } function extracData (data) { if (typeof data == 'string') { try{ var data = JSON.parse(data); return data; }catch(err){ console.log(err); return null; } }; } /* * SEND DATA TO THE SERVER */ function send (dataObject) { if (typeof dataObject != 'object') return Promise.reject('Invalid data to send. Err: is not object.'); return new Promise((resolve, reject)=>{ dataObject['taskId'] = registerPromise({resolve,reject}); var dataBuffer = BufferStream.createBuffer(dataObject); this.ws.send(dataBuffer, err=>{ if (err) { console.log("## WS AREADY DISCONNECTED ##"); console.log(err); console.trace("Error WebSocketClient") reject(err); } }) }).catch(err=>{ removePromise(dataObject.taskId); return Promise.reject(err); }) } function dataController (data) { var _this = this; if (!data) { return 0; } if ( data.type == 'message') { data = data.data; } data = BufferStream.readFromBuffer(data); //IT'S IS THE SERVER CALLING FOR EXECUTE A FUNCTION ON THE CLIENT if (data.methodName && this[data.methodName]) { var work = this[data.methodName]; var params = data.arguments || []; if (Array.isArray(work)) { work.forEach(function(_fn){ _fn.apply(this, params); }); }else if(typeof work == 'function'){ work.apply(this, params); }else{ console.log("Invalid registed method."); } } //IT´S THE SERVER SENING THE RESULT OF AN EXECUTED FUNCTION else if (data.taskId) { responseHandler.call(this,data); }else{ //console.log('Non register Function or task.'); } } function connect (_callbackfn){ console.log("*** WS CONNECT ***"); var state = this.status.state; //var this... AIM TO THE OBJECT WS. if (state == "close" || state == "error") { this.status.attempts++; try{ this.ws = new WebSocket(this.url); this.status.state = "connecting"; // STARTING THE INIT METHOD. init.call(this); console.log(' > Connecting.'); return this; }catch(err){ console.log(err); return null; } }else{ console.log(' > Already Connected.'); return this; } } function reconnect (_callbackfn){ console.log('-- Reconnecting --'); return connect.call(this, _callbackfn);// CREATE A NEW CONNECTION } /** * TASKS RESPONSE HANDLER * **/ var _CACHE_PROMISES= {}, _CACHE_PROMISES_IDS=[]; function responseHandler(response) { if (!response) { return 0; } if (typeof response != 'object') { return 0; } var id= response.taskId; var ret = getRegisterPromise(id); if (!ret) { return 0; } var argumentsR = response.arguments || []; /** * EXECUTE ANY RESPONSE MIDDLEWARE **/ var onResponseMiddleware = this._events.onResponseMiddleware; if(Array.isArray(onResponseMiddleware) && onResponseMiddleware.length > 0) for (var i = 0; i < onResponseMiddleware.length; i++) { var middleRs = onResponseMiddleware[i].apply(argumentsR); if (!middleRs) { return null; } } if (argumentsR[0]===null) { ret.reject(argumentsR[1]); }else{ ret.resolve(argumentsR[0]); } } function guid() { return Math.floor((Math.random() * 1024) + 256); } function registerPromise(promise) { promise.createdAt = new Date(); var id = guid(); promise.id = id; if (_CACHE_PROMISES[id]) return registerPromise(promise); else{ _CACHE_PROMISES_IDS.push(id); _CACHE_PROMISES[id] = promise; return id; } } function getRegisterPromise(id) { var promise = _CACHE_PROMISES[id]; if (!promise) { return null; } removePromise(promise); return promise; } function removePromise(promise) { var id = promise.id; var index = _CACHE_PROMISES_IDS.indexOf(id); _CACHE_PROMISES_IDS.splice(index, 1); delete _CACHE_PROMISES[id]; } function collectorPromises() { _CACHE_PROMISES_IDS.map((id, index)=>{ var promise = _CACHE_PROMISES[id]; if (!promise) return 0; var restTime = msToSeg(Date.now() - promise.createdAt); if (restTime > 15) { console.log("## REMOVE PROMISE ##"); promise.reject("Response timeout."); _CACHE_PROMISES_IDS.splice(index, 1); delete _CACHE_PROMISES[id]; } }) } setInterval(collectorPromises, 15000); /* * EVENTS CONTROLLERS */ function onReadyController () { var _this = this; this.status.state = "ready"; if(this.parent && this.parent._events.onReady) this.parent._events.onReady.map(function (fn) { fn.call(_this); }); if(this._events.onReady) this._events.onReady.map(function (fn) { fn.call(_this); }); } function openController () { var _this = this; console.log('--- WebSocket Opend ---'); syncMethods.call(this).then(onReadyController.bind(this)); // UPDATE THE STATE this.status.state = "open"; this.status.attempts = 0; if(this.parent && this.parent._events.onOpen) this.parent._events.onOpen.map(function (fn) { fn.call(_this); }); if(this._events.onOpen) this._events.onOpen.map(function (fn) { fn.call(_this); }); } function onStateChange (state) { var _this = this; if(this.parent && this.parent._events.onStateChange) this.parent._events.onStateChange.map(function (fn) { fn.call(_this, state); }); if(this._events.onStateChange) this._events.onStateChange.map(function (fn) { fn.call(_this, state); }); } function errorController (err) { var _this = this; // UPDATE THE STATE this.status.state = "error"; if(this.parent && this.parent._events.onError) this.parent._events.onError.map(function (fn) { fn.call(_this); }); if(this._events.onError) this._events.onError.map(function (fn) { fn.call(_this); }); } function closeController (message) { var _this = this; var st=undefined; this.status.state = "close"; if(this.parent && this.parent._events.onClose) this.parent._events.onClose.map(function (fn) { fn.call(_this); }); if(this._events.onClose) this._events.onClose.map(function (fn) { fn.call(_this); }); if(st === undefined){ var time = generateInterval(this.status.attempts); // UPDATE THE STATE setTimeout(function () { if( reconnect.call(_this) ){ }else{ } },time); } } /* * DEFINIG GETTERS & SETTER */ function getState () { return this.status._state; } function setState (val) { this.status._state=val; onStateChange.call(this, val); return this.status._state; } function setParam(name, value){ this.defaultParams[name]=value; } function setMethod (methodName, fn) { if (!this[methodName] || !Array.isArray(this[methodName])) this[methodName] = []; this[methodName].push(fn); } function isReady () { if (this.status.state == "ready") return true; else return false; } function removeMethod(methodName, workFn) { if (!this[methodName]) { return null; } if ( Array.isArray(this[methodName]) ) { var worksFn = this[methodName]; var indexWork = worksFn.indexOf(workFn); worksFn.splice(indexWork, 1); } return worksFn; } /* * FOR EVENTS */ function addEventListener(name, fn) { switch(name){ case 'open': if (!this._events.onOpen) this._events.onOpen = []; this._events.onOpen.push(fn); break; case 'connect': if (!this._events.onOpen) this._events.onOpen = []; this._events.onOpen.push(fn); break; case 'ready': if (!this._events.onReady) this._events.onReady = []; this._events.onReady.push(fn); break; case 'close': if (!this._events.onClose) this._events.onClose = []; this._events.onClose.push(fn); break; case 'error': if (!this._events.onError) this._events.onError = []; this._events.onError.push(fn); break; case 'data': break; case 'change': if (!this._events.onStateChange) this._events.onStateChange = []; this._events.onStateChange.push(fn); break; case 'response': if (!this._events.onResponseMiddleware) this._events.onResponseMiddleware = []; this._events.onResponseMiddleware.push(fn); break; default: if (!this._events[name]) this._events[name] = []; this._events[name].push(fn); } return this; } function setOnOpen (fn) { return addEventListener.call(this, 'connect', fn); } function setOnReady (fn) { return addEventListener.call(this, 'ready', fn); } function setOnClose (fn) { return addEventListener.call(this, 'close', fn); } function setOnError (fn) { return addEventListener.call(this, 'error', fn); } function setOnStateChange (fn) { return addEventListener.call(this, 'change', fn); } function open (options, _callbackfn) { // var this... AIMS TO THE WS GENERATOR CLASS // CREATE THE WS OBJECT var url; if(typeof options == "string") { url= options; }else{ url= 'ws://localhost' } var protocol = !options.protocol ? typeof location==='undefined' ? 'ws' : location.protocol : options.protocol switch(protocol){ case 'https:': protocol = "wss"; default: protocol += '://' } var host = !options.host ? typeof location==='undefined' ? 'ws' : location.host : options.host var ws = { parent : this, url : url || protocol+host, defaultParams:{}, ws : ws, registerTasks : {}, _events:{}, syncMethods : syncMethods, setParam : setParam, setMethod : setMethod, removeMethod: removeMethod, status: { _state: "close", attempts:0 } }; // ADDING GETTERS & SETTERS ws.status.__defineGetter__('state',getState.bind(ws)); ws.status.__defineSetter__('state',setState.bind(ws)); ws.status.__defineSetter__('isReady',isReady.bind(ws)); ws.__defineSetter__('onOpen' , setOnOpen); ws.__defineSetter__('onReady' , setOnReady); ws.__defineSetter__('onCose' , setOnClose); ws.__defineSetter__('onError' , setOnError); ws.__defineSetter__('onStateChange' , setOnStateChange); ws.on = addEventListener; ws.reconnect = reconnect; ws.isReady = isReady; if (_callbackfn && typeof _callbackfn == 'function') ws.onOpen(_callbackfn); connect.call(ws);// ESTABLISH THE CONNECTION // ADD/REGISTER THE NEW WS TO THE ARRAY. this.ws.push(ws); return ws; } /* * PROTOTYPE THE METHODS */ return { open: open, connect: open, _events: {}, ws: [], onOpen: setOnOpen, onReady: setOnReady, onCose: setOnClose, onError: setOnError, onStateChange: setOnStateChange, readFromBuffer: BufferStream.readFromBuffer, createBuffer: BufferStream.createBuffer, } })(); module.exports = wsClient;