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