@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.
@@ -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
+ }