├── LICENSE
├── README.md
├── nano-websocket-client.js
└── protocol.js
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 nano Authors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nano-websocket-client
2 | nano javascript WebSocket client SDK
3 |
4 | ## API
5 |
6 | ### Connect to server
7 |
8 | ```javascript
9 | nano.init(params, callback);
10 | ```
11 |
12 | Examples
13 |
14 | ```javascript
15 | nano.init({
16 | host: host,
17 | port: port,
18 | user: {},
19 | handshakeCallback : function(){}
20 | }, function() {
21 | console.log('success');
22 | });
23 | ```
24 |
25 | ### Send request to server with callback
26 |
27 | ```javascript
28 | nano.request(route, msg, callback);
29 | ```
30 |
31 | Examples
32 |
33 | ```javascript
34 | nano.request(route, {
35 | rid: rid
36 | }, function(data) {
37 | console.log(dta);
38 | });
39 | ```
40 |
41 | ### Send request to server without callback
42 |
43 | ```javascript
44 | nano.notify(route, params);
45 | ```
46 |
47 | ### Receive message from server
48 |
49 | ```javascript
50 | nano.on(route, callback);
51 | ```
52 |
53 | Examples
54 |
55 | ```javascript
56 | nano.on('onChat', function(data) {
57 | addMessage(data.from, data.target, data.msg);
58 | $("#chatHistory").show();
59 | });
60 | ```
61 |
62 | ### Disconnect from server
63 |
64 | ```javascript
65 | nano.disconnect();
66 | ```
67 |
68 | ## Usage
69 |
70 | ```html
71 |
72 |
73 |
74 | nano WebSocket Test Page
75 |
76 |
77 |
78 |
79 |
98 |
99 |
100 | ```
101 |
102 |
103 |
104 | ## License
105 |
106 | [MIT License](./LICENSE)
--------------------------------------------------------------------------------
/nano-websocket-client.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | function Emitter(obj) {
3 | if (obj) return mixin(obj);
4 | }
5 | /**
6 | * Mixin the emitter properties.
7 | *
8 | * @param {Object} obj
9 | * @return {Object}
10 | * @api private
11 | */
12 |
13 | function mixin(obj) {
14 | for (var key in Emitter.prototype) {
15 | obj[key] = Emitter.prototype[key];
16 | }
17 | return obj;
18 | }
19 |
20 | /**
21 | * Listen on the given `event` with `fn`.
22 | *
23 | * @param {String} event
24 | * @param {Function} fn
25 | * @return {Emitter}
26 | * @api public
27 | */
28 |
29 | Emitter.prototype.on =
30 | Emitter.prototype.addEventListener = function(event, fn){
31 | this._callbacks = this._callbacks || {};
32 | (this._callbacks[event] = this._callbacks[event] || [])
33 | .push(fn);
34 | return this;
35 | };
36 |
37 | /**
38 | * Adds an `event` listener that will be invoked a single
39 | * time then automatically removed.
40 | *
41 | * @param {String} event
42 | * @param {Function} fn
43 | * @return {Emitter}
44 | * @api public
45 | */
46 |
47 | Emitter.prototype.once = function(event, fn){
48 | var self = this;
49 | this._callbacks = this._callbacks || {};
50 |
51 | function on() {
52 | self.off(event, on);
53 | fn.apply(this, arguments);
54 | }
55 |
56 | on.fn = fn;
57 | this.on(event, on);
58 | return this;
59 | };
60 |
61 | /**
62 | * Remove the given callback for `event` or all
63 | * registered callbacks.
64 | *
65 | * @param {String} event
66 | * @param {Function} fn
67 | * @return {Emitter}
68 | * @api public
69 | */
70 |
71 | Emitter.prototype.off =
72 | Emitter.prototype.removeListener =
73 | Emitter.prototype.removeAllListeners =
74 | Emitter.prototype.removeEventListener = function(event, fn){
75 | this._callbacks = this._callbacks || {};
76 |
77 | // all
78 | if (0 == arguments.length) {
79 | this._callbacks = {};
80 | return this;
81 | }
82 |
83 | // specific event
84 | var callbacks = this._callbacks[event];
85 | if (!callbacks) return this;
86 |
87 | // remove all handlers
88 | if (1 == arguments.length) {
89 | delete this._callbacks[event];
90 | return this;
91 | }
92 |
93 | // remove specific handler
94 | var cb;
95 | for (var i = 0; i < callbacks.length; i++) {
96 | cb = callbacks[i];
97 | if (cb === fn || cb.fn === fn) {
98 | callbacks.splice(i, 1);
99 | break;
100 | }
101 | }
102 | return this;
103 | };
104 |
105 | /**
106 | * Emit `event` with the given args.
107 | *
108 | * @param {String} event
109 | * @param {Mixed} ...
110 | * @return {Emitter}
111 | */
112 |
113 | Emitter.prototype.emit = function(event){
114 | this._callbacks = this._callbacks || {};
115 | var args = [].slice.call(arguments, 1)
116 | , callbacks = this._callbacks[event];
117 |
118 | if (callbacks) {
119 | callbacks = callbacks.slice(0);
120 | for (var i = 0, len = callbacks.length; i < len; ++i) {
121 | callbacks[i].apply(this, args);
122 | }
123 | }
124 |
125 | return this;
126 | };
127 |
128 | /**
129 | * Return array of callbacks for `event`.
130 | *
131 | * @param {String} event
132 | * @return {Array}
133 | * @api public
134 | */
135 |
136 | Emitter.prototype.listeners = function(event){
137 | this._callbacks = this._callbacks || {};
138 | return this._callbacks[event] || [];
139 | };
140 |
141 | /**
142 | * Check if this emitter has `event` handlers.
143 | *
144 | * @param {String} event
145 | * @return {Boolean}
146 | * @api public
147 | */
148 |
149 | Emitter.prototype.hasListeners = function(event){
150 | return !! this.listeners(event).length;
151 | };
152 | var JS_WS_CLIENT_TYPE = 'js-websocket';
153 | var JS_WS_CLIENT_VERSION = '0.0.1';
154 |
155 | var Protocol = window.Protocol;
156 | var decodeIO_encoder = null;
157 | var decodeIO_decoder = null;
158 | var Package = Protocol.Package;
159 | var Message = Protocol.Message;
160 | var EventEmitter = Emitter;
161 | var rsa = window.rsa;
162 |
163 | if(typeof(window) != "undefined" && typeof(sys) != 'undefined' && sys.localStorage) {
164 | window.localStorage = sys.localStorage;
165 | }
166 |
167 | var RES_OK = 200;
168 | var RES_FAIL = 500;
169 | var RES_OLD_CLIENT = 501;
170 |
171 | if (typeof Object.create !== 'function') {
172 | Object.create = function (o) {
173 | function F() {}
174 | F.prototype = o;
175 | return new F();
176 | };
177 | }
178 |
179 | var root = window;
180 | var nano = Object.create(EventEmitter.prototype); // object extend from object
181 | root.nano = nano;
182 | var socket = null;
183 | var reqId = 0;
184 | var callbacks = {};
185 | var handlers = {};
186 | //Map from request id to route
187 | var routeMap = {};
188 | var dict = {}; // route string to code
189 | var abbrs = {}; // code to route string
190 |
191 | var heartbeatInterval = 0;
192 | var heartbeatTimeout = 0;
193 | var nextHeartbeatTimeout = 0;
194 | var gapThreshold = 100; // heartbeat gap threashold
195 | var heartbeatId = null;
196 | var heartbeatTimeoutId = null;
197 | var handshakeCallback = null;
198 |
199 | var decode = null;
200 | var encode = null;
201 |
202 | var reconnect = false;
203 | var reconncetTimer = null;
204 | var reconnectUrl = null;
205 | var reconnectAttempts = 0;
206 | var reconnectionDelay = 5000;
207 | var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
208 |
209 | var useCrypto;
210 |
211 | var handshakeBuffer = {
212 | 'sys': {
213 | type: JS_WS_CLIENT_TYPE,
214 | version: JS_WS_CLIENT_VERSION,
215 | rsa: {}
216 | },
217 | 'user': {
218 | }
219 | };
220 |
221 | var initCallback = null;
222 |
223 | nano.init = function(params, cb) {
224 | initCallback = cb;
225 | var host = params.host;
226 | var port = params.port;
227 | var path = params.path;
228 |
229 | encode = params.encode || defaultEncode;
230 | decode = params.decode || defaultDecode;
231 |
232 | var url = 'ws://' + host;
233 | if(port) {
234 | url += ':' + port;
235 | }
236 |
237 | if(path) {
238 | url += path;
239 | }
240 |
241 | handshakeBuffer.user = params.user;
242 | if(params.encrypt) {
243 | useCrypto = true;
244 | rsa.generate(1024, "10001");
245 | var data = {
246 | rsa_n: rsa.n.toString(16),
247 | rsa_e: rsa.e
248 | };
249 | handshakeBuffer.sys.rsa = data;
250 | }
251 | handshakeCallback = params.handshakeCallback;
252 | connect(params, url, cb);
253 | };
254 |
255 | var defaultDecode = nano.decode = function(data) {
256 | var msg = Message.decode(data);
257 |
258 | if(msg.id > 0){
259 | msg.route = routeMap[msg.id];
260 | delete routeMap[msg.id];
261 | if(!msg.route){
262 | return;
263 | }
264 | }
265 |
266 | msg.body = deCompose(msg);
267 | return msg;
268 | };
269 |
270 | var defaultEncode = nano.encode = function(reqId, route, msg) {
271 | var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY;
272 |
273 | if(decodeIO_encoder && decodeIO_encoder.lookup(route)) {
274 | var Builder = decodeIO_encoder.build(route);
275 | msg = new Builder(msg).encodeNB();
276 | } else {
277 | msg = Protocol.strencode(JSON.stringify(msg));
278 | }
279 |
280 | var compressRoute = 0;
281 | if(dict && dict[route]) {
282 | route = dict[route];
283 | compressRoute = 1;
284 | }
285 |
286 | return Message.encode(reqId, type, compressRoute, route, msg);
287 | };
288 |
289 | var connect = function(params, url, cb) {
290 | console.log('connect to ' + url);
291 |
292 | var params = params || {};
293 | var maxReconnectAttempts = params.maxReconnectAttempts || DEFAULT_MAX_RECONNECT_ATTEMPTS;
294 | reconnectUrl = url;
295 |
296 | var onopen = function(event) {
297 | if(!!reconnect) {
298 | nano.emit('reconnect');
299 | }
300 | reset();
301 | var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer)));
302 | send(obj);
303 | };
304 | var onmessage = function(event) {
305 | processPackage(Package.decode(event.data), cb);
306 | // new package arrived, update the heartbeat timeout
307 | if(heartbeatTimeout) {
308 | nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
309 | }
310 | };
311 | var onerror = function(event) {
312 | nano.emit('io-error', event);
313 | console.error('socket error: ', event);
314 | };
315 | var onclose = function(event) {
316 | nano.emit('close',event);
317 | nano.emit('disconnect', event);
318 | console.log('socket close: ', event);
319 | if(!!params.reconnect && reconnectAttempts < maxReconnectAttempts) {
320 | reconnect = true;
321 | reconnectAttempts++;
322 | reconncetTimer = setTimeout(function() {
323 | connect(params, reconnectUrl, cb);
324 | }, reconnectionDelay);
325 | reconnectionDelay *= 2;
326 | }
327 | };
328 | socket = new WebSocket(url);
329 | socket.binaryType = 'arraybuffer';
330 | socket.onopen = onopen;
331 | socket.onmessage = onmessage;
332 | socket.onerror = onerror;
333 | socket.onclose = onclose;
334 | };
335 |
336 | nano.disconnect = function() {
337 | if(socket) {
338 | if(socket.disconnect) socket.disconnect();
339 | if(socket.close) socket.close();
340 | console.log('disconnect');
341 | socket = null;
342 | }
343 |
344 | if(heartbeatId) {
345 | clearTimeout(heartbeatId);
346 | heartbeatId = null;
347 | }
348 | if(heartbeatTimeoutId) {
349 | clearTimeout(heartbeatTimeoutId);
350 | heartbeatTimeoutId = null;
351 | }
352 | };
353 |
354 | var reset = function() {
355 | reconnect = false;
356 | reconnectionDelay = 1000 * 5;
357 | reconnectAttempts = 0;
358 | clearTimeout(reconncetTimer);
359 | };
360 |
361 | nano.request = function(route, msg, cb) {
362 | if(arguments.length === 2 && typeof msg === 'function') {
363 | cb = msg;
364 | msg = {};
365 | } else {
366 | msg = msg || {};
367 | }
368 | route = route || msg.route;
369 | if(!route) {
370 | return;
371 | }
372 |
373 | reqId++;
374 | sendMessage(reqId, route, msg);
375 |
376 | callbacks[reqId] = cb;
377 | routeMap[reqId] = route;
378 | };
379 |
380 | nano.notify = function(route, msg) {
381 | msg = msg || {};
382 | sendMessage(0, route, msg);
383 | };
384 |
385 | var sendMessage = function(reqId, route, msg) {
386 | if(useCrypto) {
387 | msg = JSON.stringify(msg);
388 | var sig = rsa.signString(msg, "sha256");
389 | msg = JSON.parse(msg);
390 | msg['__crypto__'] = sig;
391 | }
392 |
393 | if(encode) {
394 | msg = encode(reqId, route, msg);
395 | }
396 |
397 | var packet = Package.encode(Package.TYPE_DATA, msg);
398 | send(packet);
399 | };
400 |
401 | var send = function(packet) {
402 | socket.send(packet.buffer);
403 | };
404 |
405 | var handler = {};
406 |
407 | var heartbeat = function(data) {
408 | if(!heartbeatInterval) {
409 | // no heartbeat
410 | return;
411 | }
412 |
413 | var obj = Package.encode(Package.TYPE_HEARTBEAT);
414 | if(heartbeatTimeoutId) {
415 | clearTimeout(heartbeatTimeoutId);
416 | heartbeatTimeoutId = null;
417 | }
418 |
419 | if(heartbeatId) {
420 | // already in a heartbeat interval
421 | return;
422 | }
423 | heartbeatId = setTimeout(function() {
424 | heartbeatId = null;
425 | send(obj);
426 |
427 | nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
428 | heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout);
429 | }, heartbeatInterval);
430 | };
431 |
432 | var heartbeatTimeoutCb = function() {
433 | var gap = nextHeartbeatTimeout - Date.now();
434 | if(gap > gapThreshold) {
435 | heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap);
436 | } else {
437 | console.error('server heartbeat timeout');
438 | nano.emit('heartbeat timeout');
439 | nano.disconnect();
440 | }
441 | };
442 |
443 | var handshake = function(data) {
444 | data = JSON.parse(Protocol.strdecode(data));
445 | if(data.code === RES_OLD_CLIENT) {
446 | nano.emit('error', 'client version not fullfill');
447 | return;
448 | }
449 |
450 | if(data.code !== RES_OK) {
451 | nano.emit('error', 'handshake fail');
452 | return;
453 | }
454 |
455 | handshakeInit(data);
456 |
457 | var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
458 | send(obj);
459 | if(initCallback) {
460 | initCallback(socket);
461 | }
462 | };
463 |
464 | var onData = function(data) {
465 | var msg = data;
466 | if(decode) {
467 | msg = decode(msg);
468 | }
469 | processMessage(nano, msg);
470 | };
471 |
472 | var onKick = function(data) {
473 | data = JSON.parse(Protocol.strdecode(data));
474 | nano.emit('onKick', data);
475 | };
476 |
477 | handlers[Package.TYPE_HANDSHAKE] = handshake;
478 | handlers[Package.TYPE_HEARTBEAT] = heartbeat;
479 | handlers[Package.TYPE_DATA] = onData;
480 | handlers[Package.TYPE_KICK] = onKick;
481 |
482 | var processPackage = function(msgs) {
483 | if(Array.isArray(msgs)) {
484 | for(var i=0; i>6), 0x80|(charCode & 0x3f)];
46 | }else{
47 | codes = [0xe0|(charCode>>12), 0x80|((charCode & 0xfc0)>>6), 0x80|(charCode & 0x3f)];
48 | }
49 | for(var j = 0; j < codes.length; j++){
50 | byteArray[offset] = codes[j];
51 | ++offset;
52 | }
53 | }
54 | var _buffer = new ByteArray(offset);
55 | copyArray(_buffer, 0, byteArray, 0, offset);
56 | return _buffer;
57 | };
58 |
59 | /**
60 | * client decode
61 | * msg String data
62 | * return Message Object
63 | */
64 | Protocol.strdecode = function(buffer) {
65 | var bytes = new ByteArray(buffer);
66 | var array = [];
67 | var offset = 0;
68 | var charCode = 0;
69 | var end = bytes.length;
70 | while(offset < end){
71 | if(bytes[offset] < 128){
72 | charCode = bytes[offset];
73 | offset += 1;
74 | }else if(bytes[offset] < 224){
75 | charCode = ((bytes[offset] & 0x3f)<<6) + (bytes[offset+1] & 0x3f);
76 | offset += 2;
77 | }else{
78 | charCode = ((bytes[offset] & 0x0f)<<12) + ((bytes[offset+1] & 0x3f)<<6) + (bytes[offset+2] & 0x3f);
79 | offset += 3;
80 | }
81 | array.push(charCode);
82 | }
83 | return String.fromCharCode.apply(null, array);
84 | };
85 |
86 | /**
87 | * Package protocol encode.
88 | *
89 | * Pomelo package format:
90 | * +------+-------------+------------------+
91 | * | type | body length | body |
92 | * +------+-------------+------------------+
93 | *
94 | * Head: 4bytes
95 | * 0: package type,
96 | * 1 - handshake,
97 | * 2 - handshake ack,
98 | * 3 - heartbeat,
99 | * 4 - data
100 | * 5 - kick
101 | * 1 - 3: big-endian body length
102 | * Body: body length bytes
103 | *
104 | * @param {Number} type package type
105 | * @param {ByteArray} body body content in bytes
106 | * @return {ByteArray} new byte array that contains encode result
107 | */
108 | Package.encode = function(type, body){
109 | var length = body ? body.length : 0;
110 | var buffer = new ByteArray(PKG_HEAD_BYTES + length);
111 | var index = 0;
112 | buffer[index++] = type & 0xff;
113 | buffer[index++] = (length >> 16) & 0xff;
114 | buffer[index++] = (length >> 8) & 0xff;
115 | buffer[index++] = length & 0xff;
116 | if(body) {
117 | copyArray(buffer, index, body, 0, length);
118 | }
119 | return buffer;
120 | };
121 |
122 | /**
123 | * Package protocol decode.
124 | * See encode for package format.
125 | *
126 | * @param {ByteArray} buffer byte array containing package content
127 | * @return {Object} {type: package type, buffer: body byte array}
128 | */
129 | Package.decode = function(buffer){
130 | var offset = 0;
131 | var bytes = new ByteArray(buffer);
132 | var length = 0;
133 | var rs = [];
134 | while(offset < bytes.length) {
135 | var type = bytes[offset++];
136 | length = ((bytes[offset++]) << 16 | (bytes[offset++]) << 8 | bytes[offset++]) >>> 0;
137 | var body = length ? new ByteArray(length) : null;
138 | copyArray(body, 0, bytes, offset, length);
139 | offset += length;
140 | rs.push({'type': type, 'body': body});
141 | }
142 | return rs.length === 1 ? rs[0]: rs;
143 | };
144 |
145 | /**
146 | * Message protocol encode.
147 | *
148 | * @param {Number} id message id
149 | * @param {Number} type message type
150 | * @param {Number} compressRoute whether compress route
151 | * @param {Number|String} route route code or route string
152 | * @param {Buffer} msg message body bytes
153 | * @return {Buffer} encode result
154 | */
155 | Message.encode = function(id, type, compressRoute, route, msg){
156 | // caculate message max length
157 | var idBytes = msgHasId(type) ? caculateMsgIdBytes(id) : 0;
158 | var msgLen = MSG_FLAG_BYTES + idBytes;
159 |
160 | if(msgHasRoute(type)) {
161 | if(compressRoute) {
162 | if(typeof route !== 'number'){
163 | throw new Error('error flag for number route!');
164 | }
165 | msgLen += MSG_ROUTE_CODE_BYTES;
166 | } else {
167 | msgLen += MSG_ROUTE_LEN_BYTES;
168 | if(route) {
169 | route = Protocol.strencode(route);
170 | if(route.length>255) {
171 | throw new Error('route maxlength is overflow');
172 | }
173 | msgLen += route.length;
174 | }
175 | }
176 | }
177 |
178 | if(msg) {
179 | msgLen += msg.length;
180 | }
181 |
182 | var buffer = new ByteArray(msgLen);
183 | var offset = 0;
184 |
185 | // add flag
186 | offset = encodeMsgFlag(type, compressRoute, buffer, offset);
187 |
188 | // add message id
189 | if(msgHasId(type)) {
190 | offset = encodeMsgId(id, buffer, offset);
191 | }
192 |
193 | // add route
194 | if(msgHasRoute(type)) {
195 | offset = encodeMsgRoute(compressRoute, route, buffer, offset);
196 | }
197 |
198 | // add body
199 | if(msg) {
200 | offset = encodeMsgBody(msg, buffer, offset);
201 | }
202 |
203 | return buffer;
204 | };
205 |
206 | /**
207 | * Message protocol decode.
208 | *
209 | * @param {Buffer|Uint8Array} buffer message bytes
210 | * @return {Object} message object
211 | */
212 | Message.decode = function(buffer) {
213 | var bytes = new ByteArray(buffer);
214 | var bytesLen = bytes.length || bytes.byteLength;
215 | var offset = 0;
216 | var id = 0;
217 | var route = null;
218 |
219 | // parse flag
220 | var flag = bytes[offset++];
221 | var compressRoute = flag & MSG_COMPRESS_ROUTE_MASK;
222 | var type = (flag >> 1) & MSG_TYPE_MASK;
223 |
224 | // parse id
225 | if(msgHasId(type)) {
226 | var m = parseInt(bytes[offset]);
227 | var i = 0;
228 | do{
229 | var m = parseInt(bytes[offset]);
230 | id = id + ((m & 0x7f) * Math.pow(2,(7*i)));
231 | offset++;
232 | i++;
233 | }while(m >= 128);
234 | }
235 |
236 | // parse route
237 | if(msgHasRoute(type)) {
238 | if(compressRoute) {
239 | route = (bytes[offset++]) << 8 | bytes[offset++];
240 | } else {
241 | var routeLen = bytes[offset++];
242 | if(routeLen) {
243 | route = new ByteArray(routeLen);
244 | copyArray(route, 0, bytes, offset, routeLen);
245 | route = Protocol.strdecode(route);
246 | } else {
247 | route = '';
248 | }
249 | offset += routeLen;
250 | }
251 | }
252 |
253 | // parse body
254 | var bodyLen = bytesLen - offset;
255 | var body = new ByteArray(bodyLen);
256 |
257 | copyArray(body, 0, bytes, offset, bodyLen);
258 |
259 | return {'id': id, 'type': type, 'compressRoute': compressRoute,
260 | 'route': route, 'body': body};
261 | };
262 |
263 | var copyArray = function(dest, doffset, src, soffset, length) {
264 | if('function' === typeof src.copy) {
265 | // Buffer
266 | src.copy(dest, doffset, soffset, soffset + length);
267 | } else {
268 | // Uint8Array
269 | for(var index=0; index>= 7;
289 | } while(id > 0);
290 | return len;
291 | };
292 |
293 | var encodeMsgFlag = function(type, compressRoute, buffer, offset) {
294 | if(type !== Message.TYPE_REQUEST && type !== Message.TYPE_NOTIFY &&
295 | type !== Message.TYPE_RESPONSE && type !== Message.TYPE_PUSH) {
296 | throw new Error('unkonw message type: ' + type);
297 | }
298 |
299 | buffer[offset] = (type << 1) | (compressRoute ? 1 : 0);
300 |
301 | return offset + MSG_FLAG_BYTES;
302 | };
303 |
304 | var encodeMsgId = function(id, buffer, offset) {
305 | do{
306 | var tmp = id % 128;
307 | var next = Math.floor(id/128);
308 |
309 | if(next !== 0){
310 | tmp = tmp + 128;
311 | }
312 | buffer[offset++] = tmp;
313 |
314 | id = next;
315 | } while(id !== 0);
316 |
317 | return offset;
318 | };
319 |
320 | var encodeMsgRoute = function(compressRoute, route, buffer, offset) {
321 | if (compressRoute) {
322 | if(route > MSG_ROUTE_CODE_MAX){
323 | throw new Error('route number is overflow');
324 | }
325 |
326 | buffer[offset++] = (route >> 8) & 0xff;
327 | buffer[offset++] = route & 0xff;
328 | } else {
329 | if(route) {
330 | buffer[offset++] = route.length & 0xff;
331 | copyArray(buffer, offset, route, 0, route.length);
332 | offset += route.length;
333 | } else {
334 | buffer[offset++] = 0;
335 | }
336 | }
337 |
338 | return offset;
339 | };
340 |
341 | var encodeMsgBody = function(msg, buffer, offset) {
342 | copyArray(buffer, offset, msg, 0, msg.length);
343 | return offset + msg.length;
344 | };
345 |
346 | if(typeof(window) != "undefined") {
347 | window.Protocol = Protocol;
348 | }
349 | })(typeof(window)=="undefined" ? module.exports : (this.Protocol = {}),typeof(window)=="undefined" ? Buffer : Uint8Array, this);
350 |
--------------------------------------------------------------------------------