123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- (function() {
- function Emitter(obj) {
- if (obj) return mixin(obj);
- }
- /**
- * Mixin the emitter properties.
- *
- * @param {Object} obj
- * @return {Object}
- * @api private
- */
- function mixin(obj) {
- for (var key in Emitter.prototype) {
- obj[key] = Emitter.prototype[key];
- }
- return obj;
- }
- /**
- * Listen on the given `event` with `fn`.
- *
- * @param {String} event
- * @param {Function} fn
- * @return {Emitter}
- * @api public
- */
- Emitter.prototype.on =
- Emitter.prototype.addEventListener = function(event, fn){
- this._callbacks = this._callbacks || {};
- (this._callbacks[event] = this._callbacks[event] || [])
- .push(fn);
- return this;
- };
- /**
- * Adds an `event` listener that will be invoked a single
- * time then automatically removed.
- *
- * @param {String} event
- * @param {Function} fn
- * @return {Emitter}
- * @api public
- */
- Emitter.prototype.once = function(event, fn){
- var self = this;
- this._callbacks = this._callbacks || {};
- function on() {
- self.off(event, on);
- fn.apply(this, arguments);
- }
- on.fn = fn;
- this.on(event, on);
- return this;
- };
- /**
- * Remove the given callback for `event` or all
- * registered callbacks.
- *
- * @param {String} event
- * @param {Function} fn
- * @return {Emitter}
- * @api public
- */
- Emitter.prototype.off =
- Emitter.prototype.removeListener =
- Emitter.prototype.removeAllListeners =
- Emitter.prototype.removeEventListener = function(event, fn){
- this._callbacks = this._callbacks || {};
- // all
- if (0 == arguments.length) {
- this._callbacks = {};
- return this;
- }
- // specific event
- var callbacks = this._callbacks[event];
- if (!callbacks) return this;
- // remove all handlers
- if (1 == arguments.length) {
- delete this._callbacks[event];
- return this;
- }
- // remove specific handler
- var cb;
- for (var i = 0; i < callbacks.length; i++) {
- cb = callbacks[i];
- if (cb === fn || cb.fn === fn) {
- callbacks.splice(i, 1);
- break;
- }
- }
- return this;
- };
- /**
- * Emit `event` with the given args.
- *
- * @param {String} event
- * @param {Mixed} ...
- * @return {Emitter}
- */
- Emitter.prototype.emit = function(event){
- this._callbacks = this._callbacks || {};
- var args = [].slice.call(arguments, 1)
- , callbacks = this._callbacks[event];
- if (callbacks) {
- callbacks = callbacks.slice(0);
- for (var i = 0, len = callbacks.length; i < len; ++i) {
- callbacks[i].apply(this, args);
- }
- }
- return this;
- };
- /**
- * Return array of callbacks for `event`.
- *
- * @param {String} event
- * @return {Array}
- * @api public
- */
- Emitter.prototype.listeners = function(event){
- this._callbacks = this._callbacks || {};
- return this._callbacks[event] || [];
- };
- /**
- * Check if this emitter has `event` handlers.
- *
- * @param {String} event
- * @return {Boolean}
- * @api public
- */
- Emitter.prototype.hasListeners = function(event){
- return !! this.listeners(event).length;
- };
- var JS_WS_CLIENT_TYPE = 'js-websocket';
- var JS_WS_CLIENT_VERSION = '0.0.1';
- var Protocol = window.Protocol;
- var decodeIO_encoder = null;
- var decodeIO_decoder = null;
- var Package = Protocol.Package;
- var Message = Protocol.Message;
- var EventEmitter = Emitter;
- var rsa = window.rsa;
- if(typeof(window) != "undefined" && typeof(sys) != 'undefined' && sys.localStorage) {
- window.localStorage = sys.localStorage;
- }
- var RES_OK = 200;
- var RES_FAIL = 500;
- var RES_OLD_CLIENT = 501;
- if (typeof Object.create !== 'function') {
- Object.create = function (o) {
- function F() {}
- F.prototype = o;
- return new F();
- };
- }
- var root = window;
- var pomelo = Object.create(EventEmitter.prototype); // object extend from object
- root.pomelo = pomelo;
- var socket = null;
- var reqId = 0;
- var callbacks = {};
- var handlers = {};
- //Map from request id to route
- var routeMap = {};
- var dict = {}; // route string to code
- var abbrs = {}; // code to route string
- var heartbeatInterval = 0;
- var heartbeatTimeout = 0;
- var nextHeartbeatTimeout = 0;
- var gapThreshold = 100; // heartbeat gap threashold
- var heartbeatId = null;
- var heartbeatTimeoutId = null;
- var handshakeCallback = null;
- var decode = null;
- var encode = null;
- var reconnect = false;
- var reconncetTimer = null;
- var reconnectUrl = null;
- var reconnectAttempts = 0;
- var reconnectionDelay = 5000;
- var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
- var useCrypto;
- var handshakeBuffer = {
- 'sys': {
- type: JS_WS_CLIENT_TYPE,
- version: JS_WS_CLIENT_VERSION,
- rsa: {}
- },
- 'user': {
- }
- };
- var initCallback = null;
- pomelo.init = function(params, cb) {
- initCallback = cb;
- var host = params.host;
- var port = params.port;
- var path = params.path;
- encode = params.encode || defaultEncode;
- decode = params.decode || defaultDecode;
- var url = 'ws://' + host;
- if(port) {
- url += ':' + port;
- }
- if(path) {
- url += path;
- }
- handshakeBuffer.user = params.user;
- if(params.encrypt) {
- useCrypto = true;
- rsa.generate(1024, "10001");
- var data = {
- rsa_n: rsa.n.toString(16),
- rsa_e: rsa.e
- };
- handshakeBuffer.sys.rsa = data;
- }
- handshakeCallback = params.handshakeCallback || heartbeat;
- connect(params, url, cb);
- };
- var defaultDecode = pomelo.decode = function(data) {
- var msg = Message.decode(data);
- if(msg.id > 0){
- msg.route = routeMap[msg.id];
- delete routeMap[msg.id];
- if(!msg.route){
- return;
- }
- }
- msg.body = deCompose(msg);
- return msg;
- };
- var defaultEncode = pomelo.encode = function(reqId, route, msg) {
- var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY;
- if(decodeIO_encoder && decodeIO_encoder.lookup(route)) {
- var Builder = decodeIO_encoder.build(route);
- msg = new Builder(msg).encodeNB();
- } else {
- msg = Protocol.strencode(JSON.stringify(msg));
- }
- var compressRoute = 0;
- if(dict && dict[route]) {
- route = dict[route];
- compressRoute = 1;
- }
- return Message.encode(reqId, type, compressRoute, route, msg);
- };
- var connect = function(params, url, cb) {
- console.log('connect to ' + url);
- var params = params || {};
- var maxReconnectAttempts = params.maxReconnectAttempts || DEFAULT_MAX_RECONNECT_ATTEMPTS;
- reconnectUrl = url;
- var onopen = function(event) {
- if(!!reconnect) {
- pomelo.emit('reconnect');
- }
- reset();
- var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer)));
- send(obj);
- };
- var onmessage = function(event) {
- processPackage(Package.decode(event.data), cb);
- // new package arrived, update the heartbeat timeout
- if(heartbeatTimeout) {
- nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
- }
- };
- var onerror = function(event) {
- pomelo.emit('io-error', event);
- console.error('socket error: ', event);
- };
- var onclose = function(event) {
- pomelo.emit('close',event);
- pomelo.emit('disconnect', event);
- console.log('socket close: ', event);
- if(!!params.reconnect && reconnectAttempts < maxReconnectAttempts) {
- reconnect = true;
- reconnectAttempts++;
- reconncetTimer = setTimeout(function() {
- connect(params, reconnectUrl, cb);
- }, reconnectionDelay);
- reconnectionDelay *= 2;
- }
- };
- socket = new WebSocket(url);
- socket.binaryType = 'arraybuffer';
- socket.onopen = onopen;
- socket.onmessage = onmessage;
- socket.onerror = onerror;
- socket.onclose = onclose;
- };
- pomelo.disconnect = function() {
- if(socket) {
- if(socket.disconnect) socket.disconnect();
- if(socket.close) socket.close();
- console.log('disconnect');
- socket = null;
- }
- if(heartbeatId) {
- clearTimeout(heartbeatId);
- heartbeatId = null;
- }
- if(heartbeatTimeoutId) {
- clearTimeout(heartbeatTimeoutId);
- heartbeatTimeoutId = null;
- }
- };
- var reset = function() {
- reconnect = false;
- reconnectionDelay = 1000 * 5;
- reconnectAttempts = 0;
- clearTimeout(reconncetTimer);
- };
- pomelo.request = function(route, msg, cb) {
- if(arguments.length === 2 && typeof msg === 'function') {
- cb = msg;
- msg = {};
- } else {
- msg = msg || {};
- }
- route = route || msg.route;
- if(!route) {
- return;
- }
- reqId++;
- sendMessage(reqId, route, msg);
- callbacks[reqId] = cb;
- routeMap[reqId] = route;
- };
- pomelo.notify = function(route, msg) {
- msg = msg || {};
- sendMessage(0, route, msg);
- };
- var sendMessage = function(reqId, route, msg) {
- if(useCrypto) {
- msg = JSON.stringify(msg);
- var sig = rsa.signString(msg, "sha256");
- msg = JSON.parse(msg);
- msg['__crypto__'] = sig;
- }
- if(encode) {
- msg = encode(reqId, route, msg);
- }
- var packet = Package.encode(Package.TYPE_DATA, msg);
- send(packet);
- };
- var send = function(packet) {
- socket.send(packet.buffer);
- };
- var handler = {};
- var heartbeat = function(data) {
- if(!heartbeatInterval) {
- // no heartbeat
- return;
- }
- var obj = Package.encode(Package.TYPE_HEARTBEAT);
- if(heartbeatTimeoutId) {
- clearTimeout(heartbeatTimeoutId);
- heartbeatTimeoutId = null;
- }
- if(heartbeatId) {
- // already in a heartbeat interval
- return;
- }
- heartbeatId = setTimeout(function() {
- heartbeatId = null;
- send(obj);
- console.log("send heartbeat");
- nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
- heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout);
- }, heartbeatInterval);
- };
- var heartbeatTimeoutCb = function() {
- var gap = nextHeartbeatTimeout - Date.now();
- if(gap > gapThreshold) {
- heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap);
- } else {
- console.error('server heartbeat timeout');
- pomelo.emit('heartbeat timeout');
- pomelo.disconnect();
- }
- };
- var handshake = function(data) {
- data = JSON.parse(Protocol.strdecode(data));
- if(data.code === RES_OLD_CLIENT) {
- pomelo.emit('error', 'client version not fullfill');
- return;
- }
- if(data.code !== RES_OK) {
- pomelo.emit('error', 'handshake fail');
- return;
- }
- handshakeInit(data);
- var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
- send(obj);
- if(initCallback) {
- initCallback(socket);
- }
- };
- var onData = function(data) {
- var msg = data;
- if(decode) {
- msg = decode(msg);
- }
- processMessage(pomelo, msg);
- };
- var onKick = function(data) {
- data = JSON.parse(Protocol.strdecode(data));
- pomelo.emit('onKick', data);
- };
- handlers[Package.TYPE_HANDSHAKE] = handshake;
- handlers[Package.TYPE_HEARTBEAT] = heartbeat;
- handlers[Package.TYPE_DATA] = onData;
- handlers[Package.TYPE_KICK] = onKick;
- var processPackage = function(msgs) {
- if(Array.isArray(msgs)) {
- for(var i=0; i<msgs.length; i++) {
- var msg = msgs[i];
- handlers[msg.type](msg.body);
- }
- } else {
- handlers[msgs.type](msgs.body);
- }
- };
- var processMessage = function(pomelo, msg) {
- if(!msg.id) {
- // server push message
- pomelo.emit(msg.route, msg.body);
- return;
- }
- //if have a id then find the callback function with the request
- var cb = callbacks[msg.id];
- delete callbacks[msg.id];
- if(typeof cb !== 'function') {
- return;
- }
- cb(msg.body);
- };
- var processMessageBatch = function(pomelo, msgs) {
- for(var i=0, l=msgs.length; i<l; i++) {
- processMessage(pomelo, msgs[i]);
- }
- };
- var deCompose = function(msg) {
- var route = msg.route;
- //Decompose route from dict
- if(msg.compressRoute) {
- if(!abbrs[route]){
- return {};
- }
- route = msg.route = abbrs[route];
- }
- if(decodeIO_decoder && decodeIO_decoder.lookup(route)) {
- return decodeIO_decoder.build(route).decode(msg.body);
- } else {
- return JSON.parse(Protocol.strdecode(msg.body));
- }
- return msg;
- };
- var handshakeInit = function(data) {
- if(data.sys && data.sys.heartbeat) {
- heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval
- heartbeatTimeout = heartbeatInterval * 2; // max heartbeat timeout
- } else {
- heartbeatInterval = 0;
- heartbeatTimeout = 0;
- }
- initData(data);
- if(typeof handshakeCallback === 'function') {
- handshakeCallback(data.user);
- }
- };
- //Initilize data used in pomelo client
- var initData = function(data) {
- if(!data || !data.sys) {
- return;
- }
- dict = data.sys.dict;
- //Init compress dict
- if(dict) {
- dict = dict;
- abbrs = {};
- for(var route in dict) {
- abbrs[dict[route]] = route;
- }
- }
- window.pomelo = pomelo;
- }})();
|