@jayesol/jayeson.lib.delivery 2.0.6 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -96
- package/lib/CoreMessages.js +248 -1
- package/lib/DeliveryStack.js +1212 -1
- package/lib/Subscriber.js +1083 -1
- package/lib/index.js +7 -1
- package/lib/util.js +64 -1
- package/package.json +40 -41
package/lib/DeliveryStack.js
CHANGED
|
@@ -1 +1,1212 @@
|
|
|
1
|
-
var _=require("underscore"),util=require("./util"),_c=util._c,_abstract=util._abstract,EventEmitter=require("eventemitter3"),random=require("secure-random"),node_jquery=require("jquery"),node_ws=require("websocket"),W3CWebSocket=node_ws.w3cwebsocket;function isEmpty(e){return _.isEmpty(e)}function generateUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=random(1)[0]%16|0;return("x"==e?t:3&t|8).toString(16)})}var Util={_nodeId:function(e){return e.serviceId},_streamId:function(e){return e.group+"_"+e.stream},_streamIdFromNames:function(e,t){return e+"_"+t},_newPromise:function(){var s={},e=new Promise(function(e,t){s.resolve=e,s.reject=t});return e.m=s,e.resolve=function(){this.m.resolve.apply(this,arguments)},e.reject=function(){this.m.reject.apply(this,arguments)},e},_treeMap:function(){return{_map:{},_keys:[],isEmpty:function(){return isEmpty(this._keys)},size:function(){return this._keys.length},higherEntry:function(e){for(var t=-1,s=0;s<this._keys.length;s++)if(this._keys[s]==e){t=s;break}if(-1==t)return null;var i=t+1;return i==this._keys.length?null:{key:this._keys[i],value:this._map[i]}},lowerEntry:function(e){for(var t=-1,s=0;s<this._keys.length;s++)if(this._keys[s]==e){t=s;break}if(-1==t)return null;var i=t-1;return-1==i?null:{key:this._keys[i],value:this._map[i]}},firstEntry:function(){return this.getEntryByIndex(0)},lastEntry:function(){return this.getEntryByIndex(this._keys.length-1)},get:function(e){return this._map[e]},getEntryByIndex:function(e){var t=this._keys[e];return null==t?null:{key:t,value:this._map[t]}},put:function(e,t){for(var s=-1,i=0;i<this._keys.length;i++)if(this._keys[i]>e){s=i;break}-1!=s?this._keys.splice(s,0,e):this._keys.push(e),this._map[e]=t},remove:function(e){for(var t=-1,s=null,i=0;i<this._keys.length;i++)if(this._keys[i]==e){t=i;break}if(-1!=t){this._keys.splice(t,1);s=this._map[e];delete this._map[e]}return s},keys:function(){return this._keys},values:function(){for(var e=[],t=0;t<this._keys.length;t++)e.push(this._map[this._keys[t]]);return e},entrySet:function(){for(var e=[],t=0;t<this._keys.length;t++)e.push({key:this._keys[t],value:this._map[this._keys[t]]});return e}}}};function IEndPointEventSource(){this.eventDispatcher=new EventEmitter,this.listeners=[],this.listenerMethods=[]}function IEndPoint(){EndPoint.super(this)}function IMessageClass(e,t,s){this._group=e,this._id=t,this._iClass=s}function IMessageGroup(e){this._id=e,this._classes=[]}function IMessageGroupProcessor(e){this.msgGroup=e}function IPreparsingHook(e){IMessageGroupProcessor.call(this,e)}function EPEvent(e){this.endPoint=e}function IClient(e){this.config=e}function MetaInformationCode(e){this._code=e}function StreamNameCode(){StreamNameCode.super(this,0)}function MessageIdCode(){MessageIdCode.super(this,1)}function MessageWrapper(e,t,s){this.message=e,this.messageClass=t,this.metaInfo=s}function EPConnectedEvent(e){EPConnectedEvent.super(this,e)}function EPDisconnectedEvent(e){EPDisconnectedEvent.super(this,e)}function EPConnectionError(){}function GroupRegistry(){this.processors={},this.processingGroups={},this.preparsingHooks={},this.preHookGroups={}}function EndPoint(e){GroupRegistry.call(this),this.metaCodeMap={},this.streamNameCode=new StreamNameCode,this.metaCodeMap[this.streamNameCode.code()]=this.streamNameCode,this.messageIdCode=new MessageIdCode,this.metaCodeMap[this.messageIdCode.code()]=this.messageIdCode,this.ws=e,this.ws.onmessage=_.bind(this.onMessage,this),this.waiters={},this.currentTimeout=0,this.timeOutRequestsSchedule=new Util._treeMap,this.requestTimeoutChecker=null,this.isTimeoutTaskRunning=!1}function DefaultClient(e,t){return DefaultClient.super(this,e),IEndPointEventSource.call(this),GroupRegistry.call(this),this._ep=void 0,this.config={url:"",keepAliveInterval:3e4,msgClass:null,msgGroup:null},_.extend(this.config,e),this.keepAlive=!1,this._keepAliveMsgGrp=t,this.registerPreparsingHook(t,this),this}_c(IEndPointEventSource,{attachListener:function(e){this.listeners.push(e),this.listenerMethods.push(_.bind(e.onEvent,e)),this.eventDispatcher.on("EPEvent",this.listenerMethods[this.listenerMethods.length-1])},detachListener:function(e){for(var t=0;t<this.listeners.length;t++)this.listeners[t]===e&&this.eventDispatcher.off("EPEvent",this.listenerMethods[t])},clearListeners:function(){delete this.listeners,delete this.listenersMethods,this.eventDispatcher.removeAllListeners("EPEvent")},dispatch:function(e){e instanceof EPEvent&&this.eventDispatcher.emit("EPEvent",e)}}),_c(IEndPoint,{registerGroup:_abstract,deregisterGroup:_abstract,registerPreparsingHook:_abstract,deregisterPreparsingHook:_abstract},IEndPointEventSource),_c(IMessageClass,{id:function(){return this._id},group:function(){return this._group},inHandlers:function(){return[]},outHandlers:function(){return[]},instanceClass:function(){return this._iClass}}),_c(IMessageGroup,{id:function(){return this._id},classById:function(e){return this._classes[e]},allClasses:function(){return this._classes}}),_c(IMessageGroupProcessor,{group:function(){return this.msgGroup},onRegistered:void 0,onDeregistered:void 0,process:function(e){_abstract()}}),_c(IPreparsingHook,{processUnparsedMessage:function(e){_abstract()},process:function(e){_abstract()}},IMessageGroupProcessor),_c(IClient,{connect:_abstract}),_c(MetaInformationCode,{code:function(){return this._code}}),_c(StreamNameCode,{},MetaInformationCode),_c(MessageIdCode,{},MetaInformationCode),_c(MessageWrapper,{hasMetaInfo:function(){return null!=this.metaInfo},containsMetaInfo:function(e){return 0!=this.hasMetaInfo()&&null!=this.metaInfo[e]},getMetaInfo:function(e){return 0==this.hasMetaInfo()?null:this.metaInfo[e]},addMetaInfo:function(e,t){this.hasMetaInfo()||(this.metaInfo={}),this.metaInfo[e]=t}}),_c(EPConnectedEvent,{},EPEvent),_c(EPDisconnectedEvent,{},EPEvent),_c(EPConnectionError,{},EPEvent),_c(GroupRegistry,{registerGroup:function(e,t){void 0!==this.processors[e.id()]&&void 0!==this.processors[e.id()].onDeregistered&&this.processors[e.id()].onDeregistered(this),this.processors[e.id()]=t,this.processingGroups[e.id()]=e,void 0!==t.onRegistered&&t.onRegistered(this)},deregisterGroup:function(e){if(void 0!==this.processors[e.id()]){var t=this.processors[e.id()];delete this.processors[e.id()],delete this.processingGroups[e.id()],void 0!==t.onDeregistered&&t.onDeregistered(this)}},registerPreparsingHook:function(e,t){void 0!==this.preparsingHooks[e.id()]&&void 0!==this.preparsingHooks[e.id()].onDeregistered&&this.preparsingHooks[e.id()].onDeregistered(this),this.preparsingHooks[e.id()]=t,this.preHookGroups[e.id()]=e,void 0!==t.onRegistered&&t.onRegistered(this)},deregisterPreparsingHook:function(e){if(void 0!==this.preparsingHooks[e.id()]){var t=this.preparsingHooks[e.id()];delete this.preparsingHooks[e.id()],delete this.preHookGroups[e.id()],void 0!==t.onDeregistered&&t.onDeregistered(this)}},hasPreHook:function(e){return void 0!==this.preparsingHooks[e]},hasProcessor:function(e){return void 0!==this.processors[e]},copyGroupRegistry:function(e){this.processors=e.processors,this.processingGroups=e.processingGroups,this.preparsingHooks=e.preparsingHooks,this.preHookGroups=e.preHookGroups}}),_c(EndPoint,{_extract:function(e){var t,s={},i=0==(128&e[0])?0:127&(e[0]<<8|e[1]);0<i&&(t=e.subarray(2,i+2));for(var n=0;n<i;){var r=t[n];n++;var o=r>>2,a=3&r,u=t.subarray(n,a+n);n+=a;for(var h=0,c=0;c<a;c++){var l=8*(a-1-c);h|=(255&u[c])<<l}var d=t.subarray(n,h+n);n+=h,s[o]=String.fromCharCode.apply(String,d)}for(var p in e=0<i?e.subarray(i+2):e.subarray(1),s){if(s.hasOwnProperty(p))if(null==this.metaCodeMap[p])throw"Code "+p+" is not defined."}if(e.byteLength<2)throw"Corrupted Message Format!";return{meta:s,gid:e[0],cid:e[1],pl:e.subarray(2)}},onMessage:function(e){var t=this._extract(new Uint8Array(e.data));if(this.hasPreHook(t.gid)){var s=this.preparsingHooks[t.gid],i=this.preHookGroups[t.gid].classById(t.cid),n=new MessageWrapper(t.pl,i,t.meta);if(!s.processUnparsedMessage(n,e.origin)&&!n.containsMetaInfo(this.messageIdCode.code()))return}if(!this.hasProcessor(t.gid))return console.log("Received a message with id "+t.gid+" not registered!"),null;var r=this.processors[t.gid];i=this.processingGroups[t.gid].classById(t.cid),n=new MessageWrapper(t.pl,i,t.meta);if(null==i)return console.log("Message class id "+t.cid+" is not supported."),null;_.each(i.inHandlers(),function(e){e.unpack(n)});var o=n.getMetaInfo(this.messageIdCode.code());if(null!=o){var a=this.clearRequest(o);if(void 0!==a){a.waiter.resolve(n);var u=a.stopTime;if(0<u){var h=this.timeOutRequestsSchedule.get(u);if(null!=h){for(var c=-1,l=0;l<h.length;l++)if(h[l]==o){c=l;break}-1!=c&&h.splice(c,1),isEmpty(h)&&this.timeOutRequestsSchedule.remove(u)}}return}}r.process(n)},request:function(e,t){var s=this.storeRequest(e,t);try{this.send(e)}catch(e){console.error("Cannot send message ",e);var i=s.stopTime,n=s.messageId;if(this.clearRequest(n),0<i){var r=this.timeOutRequestsSchedule.get(i);if(null!=r){for(var o=-1,a=0;a<r.length;a++)if(r[a]==n){o=a;break}-1!=o&&r.splice(o,1),isEmpty(r)&&this.timeOutRequestsSchedule.remove(i)}}return null}return s},reply:function(e,t){var s=this.createReply(e,t);this.send(s)},createReply:function(e,t){var s=this.messageIdCode.code();if(!e.hasMetaInfo(s))throw"Trying to reply to a non-request message!";return t.addMetaInfo(s,e.getMetaInfo(s)),t},storeRequest:function(e,t){var s=generateUUID();console.log(s),e.addMetaInfo(this.messageIdCode.code(),s);var i=Util._newPromise(),n=0;if(0<t){n=(new Date).getTime()+t;var r=this.timeOutRequestsSchedule.get(n);null==r&&(r=[],this.timeOutRequestsSchedule.put(n,r)),r.push(s),this.waitRequestTimeOut()}var o={waiter:i,stopTime:n,messageId:s};return this.waiters[s]=o},waitRequestTimeOut:function(){var e=(new Date).getTime();if(null!=this.requestTimeoutChecker){if(!this.timeOutRequestsSchedule.isEmpty()){var t=this.timeOutRequestsSchedule.firstEntry().key;if(t<this.currentTimeout&&(this.cancelRequestTimeout(),null==this.requestTimeoutChecker)){i=t-e;this.requestTimeoutChecker=setTimeout(_.bind(this.rejectRequestsAndRepeatWaitingNextTimeout,this,t),i),console.log("re-run new task with stop time "+t),this.currentTimeout=t}}}else if(!this.timeOutRequestsSchedule.isEmpty()){var s=this.timeOutRequestsSchedule.firstEntry().key,i=s-e;this.requestTimeoutChecker=setTimeout(_.bind(this.rejectRequestsAndRepeatWaitingNextTimeout,this,s),i),console.log("Run new task with stoptime "+s),this.currentTimeout=s}},cancelRequestTimeout:function(){this.isTimeoutTaskRunning||(clearTimeout(this.requestTimeoutChecker),this.requestTimeoutChecker=null)},rejectRequestsAndRepeatWaitingNextTimeout:function(e){this.isTimeoutTaskRunning=!0,this._rejectTimeoutRequest(e);for(var t=e,s=null;null!=(s=this.timeOutRequestsSchedule.lowerEntry(t));)this._rejectTimeoutRequest(t),t=s.key;this.requestTimeoutChecker=null,this.currentTimeout=0,this.isTimeoutTaskRunning=!1,setTimeout(_.bind(this.waitRequestTimeOut,this),0)},_rejectTimeoutRequest:function(e){var t=this.timeOutRequestsSchedule.remove(e);if(null!=t)for(var s=0;s<t.length;s++){var i=t[s],n=this.clearRequest(i);null!=n&&n.waiter.reject("Request timeout")}},clearRequest:function(e){var t=this.waiters[e];return delete this.waiters[e],t},send:function(t){if(null==this.ws)throw"Invalid socket Exception. Cannot send message.";if(_.each(t.messageClass.outHandlers(),function(e){e.pack(t)}),!(t.message instanceof Array))throw"Output pipeline doesn't encod message to byte array successfully!";for(var e=this._encodeMetaInfo(t.metaInfo),s=e.length,i=new Uint8Array(t.message.length+s+2),n=0;n<e.length;n++)i[n]=e[n];i[s]=t.messageClass.group().id(),i[s+1]=t.messageClass.id();for(n=0;n<t.message.length;n++)i[s+2+n]=t.message[n];if(null!=i&&3<=i.length)return this.ws.send(i),i;throw"Endpoint sends invalid frame!!"},_encodeMetaInfo:function(e){if(isEmpty(e))return[0];var t=[];for(var s in e)if(e.hasOwnProperty(s)){for(var i=[],n=0;n<e[s].length;n++)i.push(e[s].charCodeAt(n));var r=i.length,o=new Uint8Array([(4278190080&r)>>24,(16711680&r)>>16,(65280&r)>>8,255&r]),a=[],u=!1;for(n=0;n<o.length;n++)0!=o[n]&&(u=!0),u&&a.push(o[n]);var h=a.length;if(3<h|h<0)return[0];var c=s<<2|3&h;t.push(c);for(n=0;n<a.length;n++)t.push(a[n]);for(var l=0;l<i.length;l++)t.push(i[l])}var d=t.length,p=32768|d,g=new Uint8Array([(65280&p)>>8,255&p]),f=new Uint8Array(g.length+d);f[0]=g[0],f[1]=g[1];for(l=0;l<d;l++)f[2+l]=t[l];return f}},IEndPoint,GroupRegistry),_c(DefaultClient,{connect:function(){this.websocket=null;var e="",t=this._generateConnectionParams();if("WebSocket"in window||!W3CWebSocket||(WebSocket=W3CWebSocket),"undefined"==typeof jQuery&&(jQuery=node_jquery),!WebSocket||!jQuery)throw alert("PLEASE UPGRADE YOUR BROWSER TO SUPPORT WEBSOCKET!"),"Please upgrade your browser to support WEBSOCKET!";null!=t&&(e="?"+jQuery.param(t));var s=this.config.url+e,i=new WebSocket(s);i.binaryType="arraybuffer",i.onopen=_.bind(this.onConnected,this),i.onclose=_.bind(this.onDisconnected,this),i.onerror=_.bind(this.onConnectionError,this),console.log("Created websocket: "+s),this.websocket=i},disconnect:function(){null!=this.websocket&&(this.websocket.close(),this.keepAlive&&this.disableKeepAlive(),delete this._ep)},_generateConnectionParams:function(){var e={};if(void 0!==this.config.params&&(e=_.clone(this.config.params)),void 0!==this.config.msgGroup&&""!==this.config.msgGroup&&null!==this.config.msgGroup&&(this.config.msgGroup instanceof IMessageGroup?e.MessageGroup=this.config.msgGroup.id():e.MessageGroup=this.config.msgGroup),void 0!==this.config.msgClass&&""!==this.config.msgClass&&null!==this.config.msgClass)if(this.config.msgClass instanceof IMessageClass){if(void 0!==e.MessageGroup&&this.config.msgClass.group().id()!=e.MessageGroup)throw"Cannot generate connection params! Group ID "+e.MessageClass+" doesn't match with what defined in accompanied message class!";e.MessageClass=this.config.msgClass.id(),e.MessageGroup=this.config.msgClass.group().id()}else e.MessageClass=this.config.msgClass;return _.isEmpty(e)?null:e},enableKeepAlive:function(){0==this.keepAlive&&(this.keepAliveTimeout=setInterval(_.bind(this.sendKeepAlive,this),this.config.keepAliveInterval)),this.keepAlive=!0},disableKeepAlive:function(){1==this.keepAlive&&clearInterval(this.keepAliveTimeout),this.keepAlive=!1},sendKeepAlive:function(){if(null!=this._ep){var e=new MessageWrapper("",this._keepAliveMsgGrp.classById(0));this._ep.send(e)}},send:function(e){this._ep.send(e)},onConnected:function(e){this._ep=new EndPoint(this.websocket),this._ep.copyGroupRegistry(this),this.dispatch(new EPConnectedEvent(this._ep))},onDisconnected:function(e){void 0!==this._ep&&(this.dispatch(new EPDisconnectedEvent(this._ep)),this._ep=void 0)},onConnectionError:function(e){this.dispatch(new EPConnectionError)},processUnparsedMessage:function(e,t){if(4==e.messageClass.group().id())return console.debug("Receive keep alive message from",t),!1}},IClient,IEndPointEventSource,GroupRegistry),module.exports=exports={DefaultClient:DefaultClient,IMessageClass:IMessageClass,IMessageGroup:IMessageGroup,IMessageGroupProcessor:IMessageGroupProcessor,MetaInformationCode:MetaInformationCode,StreamNameCode:StreamNameCode,MessageIdCode:MessageIdCode,IEndPoint:IEndPoint,IEndPointEventSource:IEndPointEventSource,IPreparsingHook:IPreparsingHook,IClient:IClient,EndPoint:EndPoint,EPEvent:EPEvent,EPConnectedEvent:EPConnectedEvent,EPConnectionError:EPConnectionError,EPDisconnectedEvent:EPDisconnectedEvent,MessageWrapper:MessageWrapper,Util:Util,generateUUID:generateUUID};
|
|
1
|
+
var _ = require("underscore");
|
|
2
|
+
var util = require("./util");
|
|
3
|
+
var _c = util._c;
|
|
4
|
+
var _abstract = util._abstract;
|
|
5
|
+
var EventEmitter = require("eventemitter3");
|
|
6
|
+
var random = require('secure-random');
|
|
7
|
+
|
|
8
|
+
var node_jquery = require("jquery");
|
|
9
|
+
var node_ws = require("websocket");
|
|
10
|
+
var W3CWebSocket = node_ws.w3cwebsocket;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @author Ryan
|
|
15
|
+
*
|
|
16
|
+
* This depends on underscorejs, eventemitter
|
|
17
|
+
* Kee
|
|
18
|
+
* The DeliveryStack mimick the implementation on Java
|
|
19
|
+
* to give a user an API to interact with th delivery system.
|
|
20
|
+
*
|
|
21
|
+
* Basically it expose:
|
|
22
|
+
* - Base implementation for IClient, IEndPoint, EPEvent
|
|
23
|
+
* - Base implementation for IMessageClass and IMessageGroup
|
|
24
|
+
* - Base implementation for IPreparsingHook and IMessageGroupProcessor
|
|
25
|
+
* - DefaultClient implementation which allow user to set the URL and listen
|
|
26
|
+
* to connected / disconnected event, register group processor to any new connected
|
|
27
|
+
* end point.
|
|
28
|
+
* - DefaultClient also handle keep alive by default
|
|
29
|
+
* -
|
|
30
|
+
* - EndPoint implementation which allow user to listen to connected / disconnected
|
|
31
|
+
* event.
|
|
32
|
+
*
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if object is empty
|
|
37
|
+
* @param object
|
|
38
|
+
* @returns {Boolean}
|
|
39
|
+
*/
|
|
40
|
+
function isEmpty(object) {
|
|
41
|
+
// for(var key in object){
|
|
42
|
+
// if(object.hasOwnProperty(key)){
|
|
43
|
+
// return false;
|
|
44
|
+
// }
|
|
45
|
+
// }
|
|
46
|
+
// return true;
|
|
47
|
+
return _.isEmpty(object);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate uuid string
|
|
52
|
+
*/
|
|
53
|
+
function generateUUID() {
|
|
54
|
+
|
|
55
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
56
|
+
|
|
57
|
+
var r = random(1)[0] % 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
58
|
+
|
|
59
|
+
return v.toString(16);
|
|
60
|
+
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate the stream id from the stream information returned
|
|
68
|
+
* from stream finder.
|
|
69
|
+
*
|
|
70
|
+
* Stream information is a JSON has the following format:
|
|
71
|
+
*
|
|
72
|
+
* {
|
|
73
|
+
* "group": "<groupId>",
|
|
74
|
+
* "stream": "<streamName>",
|
|
75
|
+
* "level": <level>,
|
|
76
|
+
* "serviceId": <serviceId>,
|
|
77
|
+
* "connection": {
|
|
78
|
+
* "protocol": "<protocol>",
|
|
79
|
+
* "hostname": "<hostname>",
|
|
80
|
+
* "port": <port>
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
*/
|
|
84
|
+
var Util = {
|
|
85
|
+
/**
|
|
86
|
+
* get the id of the connection. Currently identified by service id.
|
|
87
|
+
*/
|
|
88
|
+
_nodeId: function (source) {
|
|
89
|
+
return source.serviceId;
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
_streamId: function (streamInfo) {
|
|
93
|
+
return streamInfo.group + "_" + streamInfo.stream;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
_streamIdFromNames: function (group, stream) {
|
|
97
|
+
return group + "_" + stream;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// return a promise that can be resolve in a different place
|
|
101
|
+
_newPromise: function () {
|
|
102
|
+
|
|
103
|
+
var methodWrapper = {};
|
|
104
|
+
|
|
105
|
+
var p = new Promise(function (resolve, reject) {
|
|
106
|
+
methodWrapper["resolve"] = resolve;
|
|
107
|
+
methodWrapper["reject"] = reject;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
p.m = methodWrapper;
|
|
111
|
+
p.resolve = function () {
|
|
112
|
+
this.m.resolve.apply(this, arguments);
|
|
113
|
+
};
|
|
114
|
+
p.reject = function () {
|
|
115
|
+
this.m.reject.apply(this, arguments);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return p;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
_treeMap: function () {
|
|
122
|
+
return {
|
|
123
|
+
_map: {},
|
|
124
|
+
_keys: [],
|
|
125
|
+
|
|
126
|
+
isEmpty: function () {
|
|
127
|
+
return isEmpty(this._keys);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
size: function () {
|
|
131
|
+
return this._keys.length;
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
higherEntry: function (key) {
|
|
135
|
+
var index = -1;
|
|
136
|
+
for (var i = 0; i < this._keys.length; i++) {
|
|
137
|
+
if (this._keys[i] == key) {
|
|
138
|
+
index = i;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (index != -1) {
|
|
143
|
+
var higherIndex = index + 1;
|
|
144
|
+
if (higherIndex == this._keys.length) {
|
|
145
|
+
return null;
|
|
146
|
+
} else {
|
|
147
|
+
return {
|
|
148
|
+
key: this._keys[higherIndex],
|
|
149
|
+
value: this._map[higherIndex]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
lowerEntry: function (key) {
|
|
158
|
+
var index = -1;
|
|
159
|
+
for (var i = 0; i < this._keys.length; i++) {
|
|
160
|
+
if (this._keys[i] == key) {
|
|
161
|
+
index = i;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (index != -1) {
|
|
166
|
+
var lowerIndex = index - 1;
|
|
167
|
+
if (lowerIndex == -1) {
|
|
168
|
+
return null;
|
|
169
|
+
} else {
|
|
170
|
+
return {
|
|
171
|
+
key: this._keys[lowerIndex],
|
|
172
|
+
value: this._map[lowerIndex]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
firstEntry: function () {
|
|
181
|
+
return this.getEntryByIndex(0);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
lastEntry: function () {
|
|
185
|
+
return this.getEntryByIndex(this._keys.length - 1);
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
get: function (key) {
|
|
189
|
+
return this._map[key];
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
getEntryByIndex: function (index) {
|
|
193
|
+
var _key = this._keys[index];
|
|
194
|
+
|
|
195
|
+
if (_key == null) {
|
|
196
|
+
return null;
|
|
197
|
+
} else {
|
|
198
|
+
return {
|
|
199
|
+
key: _key,
|
|
200
|
+
value: this._map[_key]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
put: function (key, value) {
|
|
206
|
+
var index = -1;
|
|
207
|
+
for (var i = 0; i < this._keys.length; i++) {
|
|
208
|
+
if (this._keys[i] > key) {
|
|
209
|
+
index = i;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (index != -1) {
|
|
214
|
+
this._keys.splice(index, 0, key);
|
|
215
|
+
} else {
|
|
216
|
+
this._keys.push(key);
|
|
217
|
+
}
|
|
218
|
+
this._map[key] = value;
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
remove: function (key) {
|
|
222
|
+
var index = -1;
|
|
223
|
+
var value = null;
|
|
224
|
+
for (var i = 0; i < this._keys.length; i++) {
|
|
225
|
+
if (this._keys[i] == key) {
|
|
226
|
+
index = i;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (index != -1) {
|
|
231
|
+
this._keys.splice(index, 1);
|
|
232
|
+
var value = this._map[key];
|
|
233
|
+
delete this._map[key];
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
keys: function () {
|
|
239
|
+
return this._keys;
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
values: function () {
|
|
243
|
+
var valueSet = [];
|
|
244
|
+
for (var i = 0; i < this._keys.length; i++) {
|
|
245
|
+
valueSet.push(this._map[this._keys[i]]);
|
|
246
|
+
}
|
|
247
|
+
return valueSet;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
entrySet: function () {
|
|
251
|
+
var entries = [];
|
|
252
|
+
for (var i = 0; i < this._keys.length; i++) {
|
|
253
|
+
entries.push({
|
|
254
|
+
key: this._keys[i],
|
|
255
|
+
value: this._map[this._keys[i]],
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return entries;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
/************END UTILITY ********************************/
|
|
267
|
+
|
|
268
|
+
/************* INTERFACE DEFINITION *********************/
|
|
269
|
+
|
|
270
|
+
function IEndPointEventSource() {
|
|
271
|
+
// initialize the dispatcher
|
|
272
|
+
this.eventDispatcher = new EventEmitter();
|
|
273
|
+
|
|
274
|
+
// event submitter store method but not the object so
|
|
275
|
+
// so we can't call the listener method with the object it belongs to.
|
|
276
|
+
// Hence, needs to store the _.bind instead of the original method.
|
|
277
|
+
this.listeners = [];
|
|
278
|
+
this.listenerMethods = [];
|
|
279
|
+
|
|
280
|
+
}
|
|
281
|
+
_c(IEndPointEventSource, {
|
|
282
|
+
|
|
283
|
+
attachListener: function (listener) {
|
|
284
|
+
this.listeners.push(listener);
|
|
285
|
+
this.listenerMethods.push(_.bind(listener.onEvent, listener));
|
|
286
|
+
this.eventDispatcher.on("EPEvent", this.listenerMethods[this.listenerMethods.length - 1]);
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
detachListener: function (listener) {
|
|
290
|
+
for (var i = 0; i < this.listeners.length; i++)
|
|
291
|
+
if (this.listeners[i] === listener) {
|
|
292
|
+
this.eventDispatcher.off("EPEvent", this.listenerMethods[i]);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
clearListeners: function () {
|
|
297
|
+
delete this.listeners;
|
|
298
|
+
delete this.listenersMethods;
|
|
299
|
+
this.eventDispatcher.removeAllListeners("EPEvent");
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
dispatch: function (event) {
|
|
303
|
+
if (event instanceof EPEvent) {
|
|
304
|
+
this.eventDispatcher.emit("EPEvent", event);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
function IEndPoint() {
|
|
310
|
+
EndPoint.super(this); // Initialize properties related to IEndPointEventSource
|
|
311
|
+
}
|
|
312
|
+
_c(IEndPoint, {
|
|
313
|
+
registerGroup: _abstract,
|
|
314
|
+
deregisterGroup: _abstract,
|
|
315
|
+
registerPreparsingHook: _abstract,
|
|
316
|
+
deregisterPreparsingHook: _abstract
|
|
317
|
+
}, IEndPointEventSource); // extends EndPoint from IEndPointEventSource
|
|
318
|
+
|
|
319
|
+
function IMessageClass(msgGroup, id, instanceClass) {
|
|
320
|
+
this._group = msgGroup;
|
|
321
|
+
this._id = id;
|
|
322
|
+
this._iClass = instanceClass;
|
|
323
|
+
}
|
|
324
|
+
_c(IMessageClass, {
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Return the id of the message class which should be unique within the group
|
|
328
|
+
* containing it.
|
|
329
|
+
*/
|
|
330
|
+
id: function () { return this._id; },
|
|
331
|
+
/**
|
|
332
|
+
* Return the message group that this class belongs to
|
|
333
|
+
*/
|
|
334
|
+
group: function () { return this._group; },
|
|
335
|
+
/**
|
|
336
|
+
* Define the list of in handlers for the message class.
|
|
337
|
+
* Each handler should have a handling method that accept a message wrapper.
|
|
338
|
+
* It transforms the original message wrapper and pass to the next handler
|
|
339
|
+
* Passing through all handler should provide the final message in the form
|
|
340
|
+
* the processor can process.
|
|
341
|
+
*/
|
|
342
|
+
inHandlers: function () { return []; },
|
|
343
|
+
/**
|
|
344
|
+
* Define the list of out handlers for the message class.
|
|
345
|
+
* Each handler should have a handling method that accept a message wrapper.
|
|
346
|
+
* It transforms the original message wrapper and pass to the next handler
|
|
347
|
+
* Passing through all out handlers should provide the final message in the form
|
|
348
|
+
* can be written to the web socket.
|
|
349
|
+
*/
|
|
350
|
+
outHandlers: function () { return []; }, // define the out handlers for the message class
|
|
351
|
+
/**
|
|
352
|
+
* The instance class of the message content.
|
|
353
|
+
*/
|
|
354
|
+
instanceClass: function () { return this._iClass; }
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
function IMessageGroup(id) {
|
|
358
|
+
this._id = id; // id of the message group
|
|
359
|
+
this._classes = []; // store all the message classes
|
|
360
|
+
}
|
|
361
|
+
_c(IMessageGroup, {
|
|
362
|
+
id: function () { return this._id; },
|
|
363
|
+
classById: function (idByte) { return this._classes[idByte]; }, // return the message class from its id
|
|
364
|
+
allClasses: function () { return this._classes; } // return all message classes
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
function IMessageGroupProcessor(msgGroup) {
|
|
368
|
+
this.msgGroup = msgGroup;
|
|
369
|
+
}
|
|
370
|
+
_c(IMessageGroupProcessor, {
|
|
371
|
+
group: function () { return this.msgGroup; }, // return the message group that the processor is in charged of
|
|
372
|
+
onRegistered: undefined, // triggered when the processor is registered on an EndPoint
|
|
373
|
+
onDeregistered: undefined, // triggered when the processor is deregistered from an EndPoint
|
|
374
|
+
process: function (messageWrapper) { _abstract(); } // called whenever a message is received for the group
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
function IPreparsingHook(msgGroup) {
|
|
378
|
+
IMessageGroupProcessor.call(this, msgGroup);
|
|
379
|
+
}
|
|
380
|
+
_c(IPreparsingHook, {
|
|
381
|
+
/**
|
|
382
|
+
* called whenever an unparsed message is received for the group
|
|
383
|
+
* This method return true or false.
|
|
384
|
+
* True if the message should continue through the pipeline for parsing and handling
|
|
385
|
+
* by the corresponding group processor
|
|
386
|
+
*
|
|
387
|
+
* False if otherwise.
|
|
388
|
+
*/
|
|
389
|
+
processUnparsedMessage: function (messageWrapper) { _abstract(); },
|
|
390
|
+
|
|
391
|
+
process: function (messageWrapper) { _abstract(); }// not supposed to be implemented
|
|
392
|
+
|
|
393
|
+
}, IMessageGroupProcessor);
|
|
394
|
+
|
|
395
|
+
function EPEvent(endPoint) {
|
|
396
|
+
this.endPoint = endPoint;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function IClient(configuration) {
|
|
400
|
+
this.config = configuration;
|
|
401
|
+
}
|
|
402
|
+
_c(IClient, {
|
|
403
|
+
connect: _abstract
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
function MetaInformationCode(code) {
|
|
407
|
+
this._code = code;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
_c(MetaInformationCode, {
|
|
411
|
+
code: function () {
|
|
412
|
+
return this._code;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
function StreamNameCode() {
|
|
417
|
+
StreamNameCode.super(this, 0);
|
|
418
|
+
}
|
|
419
|
+
_c(StreamNameCode, {}, MetaInformationCode);
|
|
420
|
+
|
|
421
|
+
//StreamNameCode = new StreamNameCode();
|
|
422
|
+
|
|
423
|
+
function MessageIdCode() {
|
|
424
|
+
MessageIdCode.super(this, 1);
|
|
425
|
+
}
|
|
426
|
+
_c(MessageIdCode, {}, MetaInformationCode);
|
|
427
|
+
|
|
428
|
+
//MessageIdCode = new MessageIdCode();
|
|
429
|
+
|
|
430
|
+
function MessageWrapper(message, messageClass, metaInfo) {
|
|
431
|
+
this.message = message;
|
|
432
|
+
this.messageClass = messageClass;
|
|
433
|
+
this.metaInfo = metaInfo;
|
|
434
|
+
}
|
|
435
|
+
_c(MessageWrapper, {
|
|
436
|
+
hasMetaInfo: function () {
|
|
437
|
+
return this.metaInfo != null;
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
containsMetaInfo: function (metaCode) {
|
|
441
|
+
if (this.hasMetaInfo() == false) return false;
|
|
442
|
+
return this.metaInfo[metaCode] != null;
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
getMetaInfo: function (metaCode) {
|
|
446
|
+
if (this.hasMetaInfo() == false) return null;
|
|
447
|
+
var content = this.metaInfo[metaCode];
|
|
448
|
+
return content;
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
addMetaInfo: function (metaCode, content) {
|
|
452
|
+
if (!this.hasMetaInfo()) this.metaInfo = {};
|
|
453
|
+
this.metaInfo[metaCode] = content;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
/************END INTERFACE DEFINITION *******************/
|
|
458
|
+
|
|
459
|
+
/************* STANDARD EVENTS **************************/
|
|
460
|
+
function EPConnectedEvent(endPoint) {
|
|
461
|
+
EPConnectedEvent.super(this, endPoint);
|
|
462
|
+
}
|
|
463
|
+
_c(EPConnectedEvent, {}, EPEvent);
|
|
464
|
+
|
|
465
|
+
function EPDisconnectedEvent(endPoint) {
|
|
466
|
+
EPDisconnectedEvent.super(this, endPoint);
|
|
467
|
+
}
|
|
468
|
+
_c(EPDisconnectedEvent, {}, EPEvent);
|
|
469
|
+
|
|
470
|
+
function EPConnectionError() { };
|
|
471
|
+
_c(EPConnectionError, {}, EPEvent);
|
|
472
|
+
/************ END STANDARD EVENT *************************/
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
/*********** IMPLEMENTATION *****************************/
|
|
476
|
+
|
|
477
|
+
function GroupRegistry() {
|
|
478
|
+
this.processors = {}; // a map from registered group to the corresponding processor
|
|
479
|
+
this.processingGroups = {};
|
|
480
|
+
this.preparsingHooks = {}; // map from registered group to the corresponding hook
|
|
481
|
+
this.preHookGroups = {};
|
|
482
|
+
}
|
|
483
|
+
_c(GroupRegistry, {
|
|
484
|
+
registerGroup: function (msgGroup, processor) {
|
|
485
|
+
if (this.processors[msgGroup.id()] !== undefined
|
|
486
|
+
&& this.processors[msgGroup.id()].onDeregistered !== undefined) {
|
|
487
|
+
this.processors[msgGroup.id()].onDeregistered(this);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
this.processors[msgGroup.id()] = processor;
|
|
491
|
+
this.processingGroups[msgGroup.id()] = msgGroup;
|
|
492
|
+
|
|
493
|
+
if (processor.onRegistered !== undefined) processor.onRegistered(this);
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
deregisterGroup: function (msgGroup) {
|
|
497
|
+
if (this.processors[msgGroup.id()] !== undefined) {
|
|
498
|
+
var p = this.processors[msgGroup.id()];
|
|
499
|
+
|
|
500
|
+
delete this.processors[msgGroup.id()];
|
|
501
|
+
delete this.processingGroups[msgGroup.id()];
|
|
502
|
+
|
|
503
|
+
if (p.onDeregistered !== undefined) p.onDeregistered(this);
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
registerPreparsingHook: function (msgGroup, hook) {
|
|
508
|
+
if (this.preparsingHooks[msgGroup.id()] !== undefined
|
|
509
|
+
&& this.preparsingHooks[msgGroup.id()].onDeregistered !== undefined) {
|
|
510
|
+
this.preparsingHooks[msgGroup.id()].onDeregistered(this);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this.preparsingHooks[msgGroup.id()] = hook;
|
|
514
|
+
this.preHookGroups[msgGroup.id()] = msgGroup;
|
|
515
|
+
|
|
516
|
+
if (hook.onRegistered !== undefined) hook.onRegistered(this);
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
deregisterPreparsingHook: function (msgGroup) {
|
|
520
|
+
if (this.preparsingHooks[msgGroup.id()] !== undefined) {
|
|
521
|
+
var p = this.preparsingHooks[msgGroup.id()];
|
|
522
|
+
|
|
523
|
+
delete this.preparsingHooks[msgGroup.id()];
|
|
524
|
+
delete this.preHookGroups[msgGroup.id()];
|
|
525
|
+
|
|
526
|
+
if (p.onDeregistered !== undefined) p.onDeregistered(this);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
hasPreHook: function (groupId) {
|
|
531
|
+
return this.preparsingHooks[groupId] !== undefined;
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
hasProcessor: function (groupId) {
|
|
535
|
+
return this.processors[groupId] !== undefined;
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Copy the registration function from other
|
|
540
|
+
*/
|
|
541
|
+
copyGroupRegistry: function (other) {
|
|
542
|
+
this.processors = other.processors;
|
|
543
|
+
this.processingGroups = other.processingGroups;
|
|
544
|
+
this.preparsingHooks = other.preparsingHooks; // map from registered group to the corresponding hook
|
|
545
|
+
this.preHookGroups = other.preHookGroups;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
function EndPoint(websocketClient) {
|
|
550
|
+
|
|
551
|
+
GroupRegistry.call(this); // initialize parameters related to GroupRegistry
|
|
552
|
+
this.metaCodeMap = {};
|
|
553
|
+
// config some default codes.
|
|
554
|
+
this.streamNameCode = new StreamNameCode();
|
|
555
|
+
this.metaCodeMap[this.streamNameCode.code()] = this.streamNameCode;
|
|
556
|
+
this.messageIdCode = new MessageIdCode();
|
|
557
|
+
this.metaCodeMap[this.messageIdCode.code()] = this.messageIdCode;
|
|
558
|
+
this.ws = websocketClient;
|
|
559
|
+
this.ws.onmessage = _.bind(this.onMessage, this); // bind the end point onMessage to the websocket client on message
|
|
560
|
+
this.waiters = {}; // promise to get the reply.
|
|
561
|
+
this.currentTimeout = 0; // timeout of current task checking timeout requests.
|
|
562
|
+
this.timeOutRequestsSchedule = new Util._treeMap(); // contains requests grouped by their timeout.
|
|
563
|
+
this.requestTimeoutChecker = null; // task for firing timeout requests.
|
|
564
|
+
this.isTimeoutTaskRunning = false;
|
|
565
|
+
}
|
|
566
|
+
_c(EndPoint, {
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Extract class, group, meta header
|
|
570
|
+
* and payload
|
|
571
|
+
*/
|
|
572
|
+
_extract: function (decodedMsg) {
|
|
573
|
+
var metaHeader = {};
|
|
574
|
+
var metaHeaderBytes;
|
|
575
|
+
var metaLength = ((decodedMsg[0] & 128) == 0) ? 0 : ((decodedMsg[0] << 8) | decodedMsg[1]) & 127;
|
|
576
|
+
// Get encrypted MetaHeader Content
|
|
577
|
+
if (metaLength > 0) metaHeaderBytes = decodedMsg.subarray(2, metaLength + 2);
|
|
578
|
+
var i = 0;
|
|
579
|
+
while (i < metaLength) {
|
|
580
|
+
// read FC-FLF byte.
|
|
581
|
+
// first byte in block [FC-FLF]-[FL]-[FV]
|
|
582
|
+
var firstByteInBlock = metaHeaderBytes[i];
|
|
583
|
+
i++;
|
|
584
|
+
var codeByte = firstByteInBlock >> 2;
|
|
585
|
+
var nBytesOfFLF = (firstByteInBlock & 3);
|
|
586
|
+
|
|
587
|
+
// read FL
|
|
588
|
+
var fieldLength = metaHeaderBytes.subarray(i, nBytesOfFLF + i);
|
|
589
|
+
i += nBytesOfFLF;
|
|
590
|
+
var contentLen = 0;
|
|
591
|
+
for (var index = 0; index < nBytesOfFLF; index++) {
|
|
592
|
+
var leftShift = 8 * (nBytesOfFLF - 1 - index);
|
|
593
|
+
contentLen |= ((fieldLength[index] & 0xff) << leftShift);
|
|
594
|
+
}
|
|
595
|
+
var content = metaHeaderBytes.subarray(i, contentLen + i);
|
|
596
|
+
i += contentLen;
|
|
597
|
+
metaHeader[codeByte] = String.fromCharCode.apply(String, content);
|
|
598
|
+
}
|
|
599
|
+
// remove the meta information
|
|
600
|
+
if (metaLength > 0) {
|
|
601
|
+
decodedMsg = decodedMsg.subarray(metaLength + 2);
|
|
602
|
+
} else {
|
|
603
|
+
decodedMsg = decodedMsg.subarray(1);
|
|
604
|
+
}
|
|
605
|
+
for (var headerKey in metaHeader) {
|
|
606
|
+
if (metaHeader.hasOwnProperty(headerKey)) {
|
|
607
|
+
var metaCode = this.metaCodeMap[headerKey];
|
|
608
|
+
if (metaCode == null) throw "Code " + headerKey + " is not defined.";
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (decodedMsg.byteLength < 2) throw "Corrupted Message Format!";
|
|
612
|
+
|
|
613
|
+
var groupId = decodedMsg[0]; // group id
|
|
614
|
+
var classId = decodedMsg[1]; // class id.
|
|
615
|
+
|
|
616
|
+
var payload = decodedMsg.subarray(2);
|
|
617
|
+
|
|
618
|
+
return { meta: metaHeader, gid: groupId, cid: classId, pl: payload };
|
|
619
|
+
},
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* In contrast to the java implementation, we only have one transport
|
|
623
|
+
* which is websocket so we will merge the router into the endpoint as well.
|
|
624
|
+
*
|
|
625
|
+
* This method will use the pipeline (defined by message group and class) and
|
|
626
|
+
* turn the message into a message wrapper
|
|
627
|
+
*/
|
|
628
|
+
onMessage: function (message) {
|
|
629
|
+
|
|
630
|
+
//try{
|
|
631
|
+
// extract information
|
|
632
|
+
var ext = this._extract(new Uint8Array(message.data));
|
|
633
|
+
|
|
634
|
+
if (this.hasPreHook(ext.gid)) {
|
|
635
|
+
|
|
636
|
+
var hook = this.preparsingHooks[ext.gid];
|
|
637
|
+
var msgGroup = this.preHookGroups[ext.gid];
|
|
638
|
+
var msgClass = msgGroup.classById(ext.cid);
|
|
639
|
+
|
|
640
|
+
var wrapper = new MessageWrapper(ext.pl, msgClass, ext.meta);
|
|
641
|
+
if (!hook.processUnparsedMessage(wrapper, message.origin)) {
|
|
642
|
+
|
|
643
|
+
// check if it is messageId.
|
|
644
|
+
if (!wrapper.containsMetaInfo(this.messageIdCode.code())) {
|
|
645
|
+
return; // if processUnparsedMessage return false, it means there isn't a need to proceed
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (this.hasProcessor(ext.gid)) {
|
|
653
|
+
|
|
654
|
+
var processor = this.processors[ext.gid];
|
|
655
|
+
var msgGroup = this.processingGroups[ext.gid];
|
|
656
|
+
var msgClass = msgGroup.classById(ext.cid);
|
|
657
|
+
|
|
658
|
+
var wrapper = new MessageWrapper(ext.pl, msgClass, ext.meta);
|
|
659
|
+
|
|
660
|
+
if (msgClass == null) {
|
|
661
|
+
console.log("Message class id " + ext.cid + " is not supported.");
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
_.each(msgClass.inHandlers(), function (handler) {
|
|
666
|
+
handler.unpack(wrapper);
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
// check if it is messageId.
|
|
670
|
+
var messageId = wrapper.getMetaInfo(this.messageIdCode.code());
|
|
671
|
+
if (messageId != null) {
|
|
672
|
+
var responseFuture = this.clearRequest(messageId);
|
|
673
|
+
if (responseFuture !== undefined) { // resolve waiter if available.
|
|
674
|
+
responseFuture.waiter.resolve(wrapper);
|
|
675
|
+
var stopTime = responseFuture.stopTime;
|
|
676
|
+
// remove the requestId in time line.
|
|
677
|
+
if (stopTime > 0) {
|
|
678
|
+
var requestIds = this.timeOutRequestsSchedule.get(stopTime);
|
|
679
|
+
if (requestIds != null) {
|
|
680
|
+
var deletedIndex = -1;
|
|
681
|
+
for (var i = 0; i < requestIds.length; i++) {
|
|
682
|
+
if (requestIds[i] == messageId) {
|
|
683
|
+
deletedIndex = i;
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (deletedIndex != -1) {
|
|
688
|
+
requestIds.splice(deletedIndex, 1);
|
|
689
|
+
}
|
|
690
|
+
if (isEmpty(requestIds)) {
|
|
691
|
+
this.timeOutRequestsSchedule.remove(stopTime);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
processor.process(wrapper);
|
|
699
|
+
return;
|
|
700
|
+
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// if we reach here, it's equals to the groupId is not registered!!
|
|
704
|
+
console.log("Received a message with id " + ext.gid + " not registered!");
|
|
705
|
+
return null;
|
|
706
|
+
|
|
707
|
+
//}
|
|
708
|
+
//catch(e){
|
|
709
|
+
// console.log("Exception while decoding message frame!");
|
|
710
|
+
// throw e;
|
|
711
|
+
//}
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Send a request to other endpoint and wait for an response from that endpoint.
|
|
716
|
+
* Zero timeOut is waiting forever.
|
|
717
|
+
*/
|
|
718
|
+
request: function (wrapper, timeout) {
|
|
719
|
+
var responseFuture = this.storeRequest(wrapper, timeout);
|
|
720
|
+
try {
|
|
721
|
+
this.send(wrapper);
|
|
722
|
+
} catch (e) {
|
|
723
|
+
// Remove the responseFuture if cannot send.
|
|
724
|
+
console.error("Cannot send message ", e);
|
|
725
|
+
var stopTime = responseFuture.stopTime;
|
|
726
|
+
var messageId = responseFuture.messageId;
|
|
727
|
+
this.clearRequest(messageId);
|
|
728
|
+
if (stopTime > 0) {
|
|
729
|
+
var requestIds = this.timeOutRequestsSchedule.get(stopTime);
|
|
730
|
+
if (requestIds != null) {
|
|
731
|
+
var deletedIndex = -1;
|
|
732
|
+
for (var i = 0; i < requestIds.length; i++) {
|
|
733
|
+
if (requestIds[i] == messageId) {
|
|
734
|
+
deletedIndex = i;
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (deletedIndex != -1) {
|
|
739
|
+
requestIds.splice(deletedIndex, 1);
|
|
740
|
+
}
|
|
741
|
+
if (isEmpty(requestIds)) {
|
|
742
|
+
this.timeOutRequestsSchedule.remove(stopTime);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
return responseFuture;
|
|
749
|
+
},
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Send a response to endpoint making a request.
|
|
753
|
+
*/
|
|
754
|
+
reply: function (original, response) {
|
|
755
|
+
var wrapper = this.createReply(original, response);
|
|
756
|
+
this.send(wrapper);
|
|
757
|
+
},
|
|
758
|
+
|
|
759
|
+
createReply: function (original, response) {
|
|
760
|
+
var metaMIDCode = this.messageIdCode.code();
|
|
761
|
+
if (original.hasMetaInfo(metaMIDCode)) {
|
|
762
|
+
response.addMetaInfo(metaMIDCode, original.getMetaInfo(metaMIDCode));
|
|
763
|
+
} else throw "Trying to reply to a non-request message!";
|
|
764
|
+
|
|
765
|
+
return response;
|
|
766
|
+
},
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* store the message to wait for the corresponding reply
|
|
770
|
+
* will fire an Request timeout exception after a given delay.
|
|
771
|
+
* will be called before encode message handler finishes.
|
|
772
|
+
* leverage the executor of channel context instead of the GlobalEventExecutor.
|
|
773
|
+
*/
|
|
774
|
+
storeRequest: function (wrapper, timeout) {
|
|
775
|
+
var uuid = generateUUID();
|
|
776
|
+
console.log(uuid);
|
|
777
|
+
wrapper.addMetaInfo(this.messageIdCode.code(), uuid);
|
|
778
|
+
var waiter = Util._newPromise();
|
|
779
|
+
|
|
780
|
+
// Create a promise to get the reply.
|
|
781
|
+
// The promise can be resolved in case the endpoint get the reply on time.
|
|
782
|
+
// Fire requestT timeout task after a period.
|
|
783
|
+
var stopTime = 0;
|
|
784
|
+
if (timeout > 0) {
|
|
785
|
+
var stopTime = new Date().getTime() + timeout;
|
|
786
|
+
var requestIds = this.timeOutRequestsSchedule.get(stopTime);
|
|
787
|
+
if (requestIds == null) {
|
|
788
|
+
requestIds = [];
|
|
789
|
+
this.timeOutRequestsSchedule.put(stopTime, requestIds);
|
|
790
|
+
}
|
|
791
|
+
requestIds.push(uuid);
|
|
792
|
+
this.waitRequestTimeOut();
|
|
793
|
+
}
|
|
794
|
+
var responseFuture = { waiter: waiter, stopTime: stopTime, messageId: uuid };
|
|
795
|
+
this.waiters[uuid] = responseFuture;
|
|
796
|
+
return responseFuture;
|
|
797
|
+
},
|
|
798
|
+
|
|
799
|
+
waitRequestTimeOut: function () {
|
|
800
|
+
var currentTime = new Date().getTime();
|
|
801
|
+
// Handle firing timeout request task when it hasn't fired and there isn't a cancelled task.
|
|
802
|
+
if (this.requestTimeoutChecker == null) {
|
|
803
|
+
if (!this.timeOutRequestsSchedule.isEmpty()) {
|
|
804
|
+
var firstEntry = this.timeOutRequestsSchedule.firstEntry();
|
|
805
|
+
var currentRequestTimeOut = firstEntry.key;
|
|
806
|
+
var delay = currentRequestTimeOut - currentTime;
|
|
807
|
+
// submit a task to fire timeout request exception.
|
|
808
|
+
this.requestTimeoutChecker = setTimeout(_.bind(this.rejectRequestsAndRepeatWaitingNextTimeout, this, currentRequestTimeOut), delay);
|
|
809
|
+
console.log("Run new task with stoptime " + currentRequestTimeOut);
|
|
810
|
+
this.currentTimeout = currentRequestTimeOut;
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Handle in case it fired. Cancel it if there is an urgent one.
|
|
816
|
+
if (!this.timeOutRequestsSchedule.isEmpty()) {
|
|
817
|
+
var firstEntry = this.timeOutRequestsSchedule.firstEntry();
|
|
818
|
+
var firstRequestTimeout = firstEntry.key;
|
|
819
|
+
// if there is an urgent request, cancel the current timeout task.
|
|
820
|
+
// otherwise, do nothing.
|
|
821
|
+
if (firstRequestTimeout < this.currentTimeout) {
|
|
822
|
+
this.cancelRequestTimeout();
|
|
823
|
+
// If cancel the the current task successfully.
|
|
824
|
+
if (this.requestTimeoutChecker == null) {
|
|
825
|
+
var delay = firstRequestTimeout - currentTime;
|
|
826
|
+
// submit a task to fire timeout request exception.
|
|
827
|
+
this.requestTimeoutChecker = setTimeout(_.bind(this.rejectRequestsAndRepeatWaitingNextTimeout, this, firstRequestTimeout), delay);
|
|
828
|
+
console.log("re-run new task with stop time " + firstRequestTimeout);
|
|
829
|
+
this.currentTimeout = firstRequestTimeout;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
cancelRequestTimeout: function () {
|
|
836
|
+
if (!this.isTimeoutTaskRunning) {
|
|
837
|
+
clearTimeout(this.requestTimeoutChecker);
|
|
838
|
+
this.requestTimeoutChecker = null;
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
|
|
842
|
+
rejectRequestsAndRepeatWaitingNextTimeout: function (stopTime) {
|
|
843
|
+
this.isTimeoutTaskRunning = true;
|
|
844
|
+
// Stop all requests
|
|
845
|
+
this._rejectTimeoutRequest(stopTime);
|
|
846
|
+
// Stop all requests have expired too.
|
|
847
|
+
var currentStopTime = stopTime;
|
|
848
|
+
var lowerEntry = null;
|
|
849
|
+
while ((lowerEntry = this.timeOutRequestsSchedule.lowerEntry(currentStopTime)) != null) {
|
|
850
|
+
this._rejectTimeoutRequest(currentStopTime);
|
|
851
|
+
currentStopTime = lowerEntry.key;
|
|
852
|
+
}
|
|
853
|
+
this.requestTimeoutChecker = null;
|
|
854
|
+
this.currentTimeout = 0;
|
|
855
|
+
this.isTimeoutTaskRunning = false;
|
|
856
|
+
setTimeout(_.bind(this.waitRequestTimeOut, this), 0);
|
|
857
|
+
},
|
|
858
|
+
|
|
859
|
+
_rejectTimeoutRequest: function (stopTime) {
|
|
860
|
+
var requestIds = this.timeOutRequestsSchedule.remove(stopTime);
|
|
861
|
+
if (requestIds != null) {
|
|
862
|
+
for (var i = 0; i < requestIds.length; i++) {
|
|
863
|
+
var requestId = requestIds[i];
|
|
864
|
+
var responseFuture = this.clearRequest(requestId);
|
|
865
|
+
if (responseFuture != null) {
|
|
866
|
+
responseFuture.waiter.reject('Request timeout');
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
|
|
872
|
+
clearRequest: function (requestId) {
|
|
873
|
+
var waiter = this.waiters[requestId];
|
|
874
|
+
delete this.waiters[requestId];
|
|
875
|
+
return waiter;
|
|
876
|
+
},
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Method to send a message wrapped inside a message wrapper.
|
|
880
|
+
* The message wrapper should contains enough information about the message the message class
|
|
881
|
+
* correspond to the message content
|
|
882
|
+
*
|
|
883
|
+
* Message will be sent asynchronously through setTimeout(0). Once the message is sent
|
|
884
|
+
* successfully, the callback will be invoked.
|
|
885
|
+
*/
|
|
886
|
+
send: function (wrapper) {
|
|
887
|
+
if (this.ws != null) {
|
|
888
|
+
|
|
889
|
+
_.each(wrapper.messageClass.outHandlers(), function (handler) {
|
|
890
|
+
handler.pack(wrapper);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
if (!(wrapper.message instanceof Array)) throw "Output pipeline doesn't encod message to byte array successfully!";
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
var encodedMetaInfo = this._encodeMetaInfo(wrapper.metaInfo);
|
|
897
|
+
var metaLength = encodedMetaInfo.length;
|
|
898
|
+
var encodedMsg = new Uint8Array(wrapper.message.length + metaLength + 2);
|
|
899
|
+
|
|
900
|
+
for (var i = 0; i < encodedMetaInfo.length; i++) encodedMsg[i] = encodedMetaInfo[i];
|
|
901
|
+
|
|
902
|
+
encodedMsg[metaLength] = wrapper.messageClass.group().id();
|
|
903
|
+
encodedMsg[metaLength + 1] = wrapper.messageClass.id();
|
|
904
|
+
|
|
905
|
+
for (var i = 0; i < wrapper.message.length; i++) encodedMsg[metaLength + 2 + i] = wrapper.message[i];
|
|
906
|
+
|
|
907
|
+
if (encodedMsg != null && encodedMsg.length >= 3) {
|
|
908
|
+
this.ws.send(encodedMsg);
|
|
909
|
+
return encodedMsg;
|
|
910
|
+
} else {
|
|
911
|
+
throw "Endpoint sends invalid frame!!";
|
|
912
|
+
}
|
|
913
|
+
} else {
|
|
914
|
+
throw "Invalid socket Exception. Cannot send message.";
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
|
|
918
|
+
_encodeMetaInfo: function (metaHeaders) {
|
|
919
|
+
if (isEmpty(metaHeaders))
|
|
920
|
+
return [0];
|
|
921
|
+
var encodedMetaInfo = []; // contains mamy
|
|
922
|
+
// blocks [FC FLF]
|
|
923
|
+
// [FL] [FV]
|
|
924
|
+
for (var metaCode in metaHeaders) {
|
|
925
|
+
if (metaHeaders.hasOwnProperty(metaCode)) {
|
|
926
|
+
var encodedFV = [];
|
|
927
|
+
for (var i = 0; i < metaHeaders[metaCode].length; i++) {
|
|
928
|
+
encodedFV.push(metaHeaders[metaCode].charCodeAt(i));
|
|
929
|
+
}
|
|
930
|
+
var nBytesFV = encodedFV.length;// number of bytes that FV will
|
|
931
|
+
// occupy.
|
|
932
|
+
|
|
933
|
+
// convert nBytesFV to byte[] and left-trim it.
|
|
934
|
+
var convertedContentLen = new Uint8Array([
|
|
935
|
+
(nBytesFV & 0xff000000) >> 24,
|
|
936
|
+
(nBytesFV & 0x00ff0000) >> 16,
|
|
937
|
+
(nBytesFV & 0x0000ff00) >> 8,
|
|
938
|
+
(nBytesFV & 0x000000ff)
|
|
939
|
+
]);
|
|
940
|
+
|
|
941
|
+
var encodedFL = []; // bytes that FL after
|
|
942
|
+
// being left-trimmed.
|
|
943
|
+
var metNotZero = false;
|
|
944
|
+
for (var i = 0; i < convertedContentLen.length; i++) {
|
|
945
|
+
if (convertedContentLen[i] != 0) {
|
|
946
|
+
metNotZero = true;
|
|
947
|
+
}
|
|
948
|
+
if (metNotZero) {
|
|
949
|
+
encodedFL.push(convertedContentLen[i]);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
var encodedFLF = encodedFL.length; // FLF.
|
|
953
|
+
// nBitsFLF in [0,3]
|
|
954
|
+
if (encodedFLF > 3 | encodedFLF < 0)
|
|
955
|
+
return [0];
|
|
956
|
+
// first byte of meta content block has the format xxxxxxyy. Where:
|
|
957
|
+
// xxxxxx : FC
|
|
958
|
+
// yy: FLF
|
|
959
|
+
var firstByte = (metaCode << 2) | (3 & encodedFLF);
|
|
960
|
+
encodedMetaInfo.push(firstByte);// insert FC-FLF
|
|
961
|
+
for (var i = 0; i < encodedFL.length; i++) {
|
|
962
|
+
encodedMetaInfo.push(encodedFL[i]);// insert FL
|
|
963
|
+
}
|
|
964
|
+
// insert FV
|
|
965
|
+
for (var index = 0; index < encodedFV.length; index++) {
|
|
966
|
+
encodedMetaInfo.push(encodedFV[index]);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
var contentSize = encodedMetaInfo.length;
|
|
974
|
+
var metaLength = 0x8000 | contentSize;
|
|
975
|
+
var encodedMetaLength = new Uint8Array([
|
|
976
|
+
(metaLength & 0x0000ff00) >> 8,
|
|
977
|
+
(metaLength & 0x000000ff)
|
|
978
|
+
]);
|
|
979
|
+
var result = new Uint8Array(encodedMetaLength.length + contentSize);
|
|
980
|
+
result[0] = encodedMetaLength[0];
|
|
981
|
+
result[1] = encodedMetaLength[1];
|
|
982
|
+
for (var index = 0; index < contentSize; index++) {
|
|
983
|
+
result[2 + index] = encodedMetaInfo[index];
|
|
984
|
+
}
|
|
985
|
+
return result;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
}, IEndPoint, GroupRegistry);
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* The client on javascript is different from java counter part. It will act as the
|
|
992
|
+
* monitor that monitoring the connection events. On java implementation, there is
|
|
993
|
+
* a EndPointMonitor.
|
|
994
|
+
*
|
|
995
|
+
* The configuration is an object define as following:
|
|
996
|
+
* {
|
|
997
|
+
* url : url of the target server. The URL should be in a full form
|
|
998
|
+
* params : { field : value, ... }
|
|
999
|
+
* msgClass : the message class we want in the connection request
|
|
1000
|
+
* msgGroup : the message group we want in the connection request
|
|
1001
|
+
* }
|
|
1002
|
+
*
|
|
1003
|
+
* Note that: if we specified msgClass as the actual object (not only the id) the group will not needed
|
|
1004
|
+
* as it can be extracted from message class.
|
|
1005
|
+
*/
|
|
1006
|
+
function DefaultClient(configuration, keepAliveMsgGrp) {
|
|
1007
|
+
DefaultClient.super(this, configuration); // call IClient
|
|
1008
|
+
IEndPointEventSource.call(this); // initialize stuff for event source mixin
|
|
1009
|
+
GroupRegistry.call(this);
|
|
1010
|
+
|
|
1011
|
+
this._ep = undefined; // initialize the endpoint
|
|
1012
|
+
|
|
1013
|
+
this.config = {
|
|
1014
|
+
url: "",
|
|
1015
|
+
keepAliveInterval: 30000, // by default send keep alive every 30s
|
|
1016
|
+
msgClass: null,
|
|
1017
|
+
msgGroup: null
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
_.extend(this.config, configuration); // copy configuration into the config to override default value
|
|
1021
|
+
|
|
1022
|
+
this.keepAlive = false;
|
|
1023
|
+
|
|
1024
|
+
this._keepAliveMsgGrp = keepAliveMsgGrp;
|
|
1025
|
+
|
|
1026
|
+
this.registerPreparsingHook(keepAliveMsgGrp, this);
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
return this;
|
|
1030
|
+
}
|
|
1031
|
+
_c(DefaultClient, {
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Configuration object has following format
|
|
1035
|
+
* {
|
|
1036
|
+
* url :
|
|
1037
|
+
* params : { field : value, ... }
|
|
1038
|
+
* msgClass :
|
|
1039
|
+
* msgGroup :
|
|
1040
|
+
* }
|
|
1041
|
+
*/
|
|
1042
|
+
connect: function () { // connect the client
|
|
1043
|
+
this.websocket = null;
|
|
1044
|
+
|
|
1045
|
+
var queryString = "";
|
|
1046
|
+
var params = this._generateConnectionParams();
|
|
1047
|
+
|
|
1048
|
+
//not in browser env but using node js to run
|
|
1049
|
+
if (!("WebSocket" in window)
|
|
1050
|
+
&& W3CWebSocket) {
|
|
1051
|
+
WebSocket = W3CWebSocket;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
if (typeof jQuery == "undefined") {
|
|
1055
|
+
jQuery = node_jquery;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (WebSocket && jQuery) {
|
|
1059
|
+
// serialize the object into query string
|
|
1060
|
+
|
|
1061
|
+
/*
|
|
1062
|
+
* TODO If you don't want to use jQuery you can use below logic to generate same string
|
|
1063
|
+
*
|
|
1064
|
+
if (params != null) queryString = "?" + Object.keys(params).map(function(k) {
|
|
1065
|
+
return encodeURIComponent(k) + '=' + encodeURIComponent(params[k])
|
|
1066
|
+
}).join('&')
|
|
1067
|
+
*
|
|
1068
|
+
*/
|
|
1069
|
+
if (params != null) queryString = "?" + jQuery.param(params);
|
|
1070
|
+
|
|
1071
|
+
var ws_url = this.config.url + queryString;
|
|
1072
|
+
var websocket = new WebSocket(ws_url);
|
|
1073
|
+
websocket.binaryType = "arraybuffer";
|
|
1074
|
+
|
|
1075
|
+
// on message will be attached to the endpoint directly
|
|
1076
|
+
// after it is created
|
|
1077
|
+
websocket.onopen = _.bind(this.onConnected, this);
|
|
1078
|
+
websocket.onclose = _.bind(this.onDisconnected, this);
|
|
1079
|
+
websocket.onerror = _.bind(this.onConnectionError, this);
|
|
1080
|
+
|
|
1081
|
+
console.log("Created websocket: " + ws_url);
|
|
1082
|
+
|
|
1083
|
+
this.websocket = websocket;
|
|
1084
|
+
|
|
1085
|
+
} else {
|
|
1086
|
+
alert("PLEASE UPGRADE YOUR BROWSER TO SUPPORT WEBSOCKET!");
|
|
1087
|
+
throw "Please upgrade your browser to support WEBSOCKET!";
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
|
|
1091
|
+
disconnect: function () {
|
|
1092
|
+
if (this.websocket != null) {
|
|
1093
|
+
this.websocket.close();
|
|
1094
|
+
if (this.keepAlive) this.disableKeepAlive();
|
|
1095
|
+
delete this._ep;
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Generate the object containing data to be put into the get request
|
|
1101
|
+
*/
|
|
1102
|
+
_generateConnectionParams: function () {
|
|
1103
|
+
var params = {};
|
|
1104
|
+
|
|
1105
|
+
if (this.config.params !== undefined) params = _.clone(this.config.params);
|
|
1106
|
+
|
|
1107
|
+
if (this.config.msgGroup !== undefined && this.config.msgGroup !== "" && this.config.msgGroup !== null) {
|
|
1108
|
+
|
|
1109
|
+
if (this.config.msgGroup instanceof IMessageGroup) params["MessageGroup"] = this.config.msgGroup.id();
|
|
1110
|
+
else params["MessageGroup"] = this.config.msgGroup;
|
|
1111
|
+
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (this.config.msgClass !== undefined && this.config.msgClass !== "" && this.config.msgClass !== null) {
|
|
1115
|
+
|
|
1116
|
+
if (this.config.msgClass instanceof IMessageClass) {
|
|
1117
|
+
|
|
1118
|
+
if (params["MessageGroup"] !== undefined && this.config.msgClass.group().id() != params["MessageGroup"])
|
|
1119
|
+
throw "Cannot generate connection params! Group ID " + params["MessageClass"] + " doesn't match with what defined in accompanied message class!";
|
|
1120
|
+
|
|
1121
|
+
params["MessageClass"] = this.config.msgClass.id();
|
|
1122
|
+
params["MessageGroup"] = this.config.msgClass.group().id();
|
|
1123
|
+
|
|
1124
|
+
} else params["MessageClass"] = this.config.msgClass;
|
|
1125
|
+
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (_.isEmpty(params)) return null;
|
|
1129
|
+
else return params;
|
|
1130
|
+
},
|
|
1131
|
+
|
|
1132
|
+
enableKeepAlive: function () {
|
|
1133
|
+
|
|
1134
|
+
if (this.keepAlive == false) {
|
|
1135
|
+
this.keepAliveTimeout = setInterval(_.bind(this.sendKeepAlive, this), this.config.keepAliveInterval);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
this.keepAlive = true;
|
|
1139
|
+
},
|
|
1140
|
+
|
|
1141
|
+
disableKeepAlive: function () {
|
|
1142
|
+
|
|
1143
|
+
if (this.keepAlive == true) {
|
|
1144
|
+
clearInterval(this.keepAliveTimeout);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
this.keepAlive = false;
|
|
1148
|
+
},
|
|
1149
|
+
|
|
1150
|
+
sendKeepAlive: function () {
|
|
1151
|
+
|
|
1152
|
+
if (this._ep == undefined) return;
|
|
1153
|
+
|
|
1154
|
+
var wrapper = new MessageWrapper("", this._keepAliveMsgGrp.classById(0)); // send an empty string
|
|
1155
|
+
this._ep.send(wrapper);
|
|
1156
|
+
},
|
|
1157
|
+
|
|
1158
|
+
send: function (wrapper) {
|
|
1159
|
+
this._ep.send(wrapper);
|
|
1160
|
+
},
|
|
1161
|
+
|
|
1162
|
+
onConnected: function (event) {
|
|
1163
|
+
this._ep = new EndPoint(this.websocket);
|
|
1164
|
+
this._ep.copyGroupRegistry(this);
|
|
1165
|
+
|
|
1166
|
+
this.dispatch(new EPConnectedEvent(this._ep));
|
|
1167
|
+
|
|
1168
|
+
},
|
|
1169
|
+
|
|
1170
|
+
onDisconnected: function (event) {
|
|
1171
|
+
if (this._ep !== undefined) {
|
|
1172
|
+
this.dispatch(new EPDisconnectedEvent(this._ep));
|
|
1173
|
+
this._ep = undefined;
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
|
|
1177
|
+
onConnectionError: function (event) {
|
|
1178
|
+
this.dispatch(new EPConnectionError());
|
|
1179
|
+
},
|
|
1180
|
+
|
|
1181
|
+
processUnparsedMessage: function (mw, origin) {
|
|
1182
|
+
|
|
1183
|
+
if (mw.messageClass.group().id() == 4) {
|
|
1184
|
+
console.debug("Receive keep alive message from", origin)
|
|
1185
|
+
return false; // no need to continue processing this message
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
}, IClient, IEndPointEventSource, GroupRegistry);
|
|
1191
|
+
|
|
1192
|
+
module.exports = exports = {
|
|
1193
|
+
DefaultClient: DefaultClient,
|
|
1194
|
+
IMessageClass: IMessageClass,
|
|
1195
|
+
IMessageGroup: IMessageGroup,
|
|
1196
|
+
IMessageGroupProcessor: IMessageGroupProcessor,
|
|
1197
|
+
MetaInformationCode: MetaInformationCode,
|
|
1198
|
+
StreamNameCode: StreamNameCode,
|
|
1199
|
+
MessageIdCode: MessageIdCode,
|
|
1200
|
+
IEndPoint: IEndPoint,
|
|
1201
|
+
IEndPointEventSource: IEndPointEventSource,
|
|
1202
|
+
IPreparsingHook: IPreparsingHook,
|
|
1203
|
+
IClient: IClient,
|
|
1204
|
+
EndPoint: EndPoint,
|
|
1205
|
+
EPEvent: EPEvent,
|
|
1206
|
+
EPConnectedEvent: EPConnectedEvent,
|
|
1207
|
+
EPConnectionError: EPConnectionError,
|
|
1208
|
+
EPDisconnectedEvent: EPDisconnectedEvent,
|
|
1209
|
+
MessageWrapper: MessageWrapper,
|
|
1210
|
+
Util: Util,
|
|
1211
|
+
generateUUID: generateUUID
|
|
1212
|
+
}
|