├── 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 | --------------------------------------------------------------------------------