pomelo-client-json.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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. function mixin(obj) {
  13. for (var key in Emitter.prototype) {
  14. obj[key] = Emitter.prototype[key];
  15. }
  16. return obj;
  17. }
  18. /**
  19. * Listen on the given `event` with `fn`.
  20. *
  21. * @param {String} event
  22. * @param {Function} fn
  23. * @return {Emitter}
  24. * @api public
  25. */
  26. Emitter.prototype.on =
  27. Emitter.prototype.addEventListener = function(event, fn){
  28. this._callbacks = this._callbacks || {};
  29. (this._callbacks[event] = this._callbacks[event] || [])
  30. .push(fn);
  31. return this;
  32. };
  33. /**
  34. * Adds an `event` listener that will be invoked a single
  35. * time then automatically removed.
  36. *
  37. * @param {String} event
  38. * @param {Function} fn
  39. * @return {Emitter}
  40. * @api public
  41. */
  42. Emitter.prototype.once = function(event, fn){
  43. var self = this;
  44. this._callbacks = this._callbacks || {};
  45. function on() {
  46. self.off(event, on);
  47. fn.apply(this, arguments);
  48. }
  49. on.fn = fn;
  50. this.on(event, on);
  51. return this;
  52. };
  53. /**
  54. * Remove the given callback for `event` or all
  55. * registered callbacks.
  56. *
  57. * @param {String} event
  58. * @param {Function} fn
  59. * @return {Emitter}
  60. * @api public
  61. */
  62. Emitter.prototype.off =
  63. Emitter.prototype.removeListener =
  64. Emitter.prototype.removeAllListeners =
  65. Emitter.prototype.removeEventListener = function(event, fn){
  66. this._callbacks = this._callbacks || {};
  67. // all
  68. if (0 == arguments.length) {
  69. this._callbacks = {};
  70. return this;
  71. }
  72. // specific event
  73. var callbacks = this._callbacks[event];
  74. if (!callbacks) return this;
  75. // remove all handlers
  76. if (1 == arguments.length) {
  77. delete this._callbacks[event];
  78. return this;
  79. }
  80. // remove specific handler
  81. var cb;
  82. for (var i = 0; i < callbacks.length; i++) {
  83. cb = callbacks[i];
  84. if (cb === fn || cb.fn === fn) {
  85. callbacks.splice(i, 1);
  86. break;
  87. }
  88. }
  89. return this;
  90. };
  91. /**
  92. * Emit `event` with the given args.
  93. *
  94. * @param {String} event
  95. * @param {Mixed} ...
  96. * @return {Emitter}
  97. */
  98. Emitter.prototype.emit = function(event){
  99. this._callbacks = this._callbacks || {};
  100. var args = [].slice.call(arguments, 1)
  101. , callbacks = this._callbacks[event];
  102. if (callbacks) {
  103. callbacks = callbacks.slice(0);
  104. for (var i = 0, len = callbacks.length; i < len; ++i) {
  105. callbacks[i].apply(this, args);
  106. }
  107. }
  108. return this;
  109. };
  110. /**
  111. * Return array of callbacks for `event`.
  112. *
  113. * @param {String} event
  114. * @return {Array}
  115. * @api public
  116. */
  117. Emitter.prototype.listeners = function(event){
  118. this._callbacks = this._callbacks || {};
  119. return this._callbacks[event] || [];
  120. };
  121. /**
  122. * Check if this emitter has `event` handlers.
  123. *
  124. * @param {String} event
  125. * @return {Boolean}
  126. * @api public
  127. */
  128. Emitter.prototype.hasListeners = function(event){
  129. return !! this.listeners(event).length;
  130. };
  131. var JS_WS_CLIENT_TYPE = 'js-websocket';
  132. var JS_WS_CLIENT_VERSION = '0.0.1';
  133. var Protocol = window.Protocol;
  134. var decodeIO_encoder = null;
  135. var decodeIO_decoder = null;
  136. var Package = Protocol.Package;
  137. var Message = Protocol.Message;
  138. var EventEmitter = Emitter;
  139. var rsa = window.rsa;
  140. if(typeof(window) != "undefined" && typeof(sys) != 'undefined' && sys.localStorage) {
  141. window.localStorage = sys.localStorage;
  142. }
  143. var RES_OK = 200;
  144. var RES_FAIL = 500;
  145. var RES_OLD_CLIENT = 501;
  146. if (typeof Object.create !== 'function') {
  147. Object.create = function (o) {
  148. function F() {}
  149. F.prototype = o;
  150. return new F();
  151. };
  152. }
  153. var root = window;
  154. var pomelo = Object.create(EventEmitter.prototype); // object extend from object
  155. root.pomelo = pomelo;
  156. var socket = null;
  157. var reqId = 0;
  158. var callbacks = {};
  159. var handlers = {};
  160. //Map from request id to route
  161. var routeMap = {};
  162. var dict = {}; // route string to code
  163. var abbrs = {}; // code to route string
  164. var heartbeatInterval = 0;
  165. var heartbeatTimeout = 0;
  166. var nextHeartbeatTimeout = 0;
  167. var gapThreshold = 100; // heartbeat gap threashold
  168. var heartbeatId = null;
  169. var heartbeatTimeoutId = null;
  170. var handshakeCallback = null;
  171. var decode = null;
  172. var encode = null;
  173. var reconnect = false;
  174. var reconncetTimer = null;
  175. var reconnectUrl = null;
  176. var reconnectAttempts = 0;
  177. var reconnectionDelay = 5000;
  178. var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
  179. var useCrypto;
  180. var handshakeBuffer = {
  181. 'sys': {
  182. type: JS_WS_CLIENT_TYPE,
  183. version: JS_WS_CLIENT_VERSION,
  184. rsa: {}
  185. },
  186. 'user': {
  187. }
  188. };
  189. var initCallback = null;
  190. pomelo.init = function(params, cb) {
  191. initCallback = cb;
  192. var host = params.host;
  193. var port = params.port;
  194. var path = params.path;
  195. encode = params.encode || defaultEncode;
  196. decode = params.decode || defaultDecode;
  197. var url = 'ws://' + host;
  198. if(port) {
  199. url += ':' + port;
  200. }
  201. if(path) {
  202. url += path;
  203. }
  204. handshakeBuffer.user = params.user;
  205. if(params.encrypt) {
  206. useCrypto = true;
  207. rsa.generate(1024, "10001");
  208. var data = {
  209. rsa_n: rsa.n.toString(16),
  210. rsa_e: rsa.e
  211. };
  212. handshakeBuffer.sys.rsa = data;
  213. }
  214. handshakeCallback = params.handshakeCallback || heartbeat;
  215. connect(params, url, cb);
  216. };
  217. var defaultDecode = pomelo.decode = function(data) {
  218. var msg = Message.decode(data);
  219. if(msg.id > 0){
  220. msg.route = routeMap[msg.id];
  221. delete routeMap[msg.id];
  222. if(!msg.route){
  223. return;
  224. }
  225. }
  226. msg.body = deCompose(msg);
  227. return msg;
  228. };
  229. var defaultEncode = pomelo.encode = function(reqId, route, msg) {
  230. var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY;
  231. if(decodeIO_encoder && decodeIO_encoder.lookup(route)) {
  232. var Builder = decodeIO_encoder.build(route);
  233. msg = new Builder(msg).encodeNB();
  234. } else {
  235. msg = Protocol.strencode(JSON.stringify(msg));
  236. }
  237. var compressRoute = 0;
  238. if(dict && dict[route]) {
  239. route = dict[route];
  240. compressRoute = 1;
  241. }
  242. return Message.encode(reqId, type, compressRoute, route, msg);
  243. };
  244. var connect = function(params, url, cb) {
  245. console.log('connect to ' + url);
  246. var params = params || {};
  247. var maxReconnectAttempts = params.maxReconnectAttempts || DEFAULT_MAX_RECONNECT_ATTEMPTS;
  248. reconnectUrl = url;
  249. var onopen = function(event) {
  250. if(!!reconnect) {
  251. pomelo.emit('reconnect');
  252. }
  253. reset();
  254. var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer)));
  255. send(obj);
  256. };
  257. var onmessage = function(event) {
  258. processPackage(Package.decode(event.data), cb);
  259. // new package arrived, update the heartbeat timeout
  260. if(heartbeatTimeout) {
  261. nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
  262. }
  263. };
  264. var onerror = function(event) {
  265. pomelo.emit('io-error', event);
  266. console.error('socket error: ', event);
  267. };
  268. var onclose = function(event) {
  269. pomelo.emit('close',event);
  270. pomelo.emit('disconnect', event);
  271. console.log('socket close: ', event);
  272. if(!!params.reconnect && reconnectAttempts < maxReconnectAttempts) {
  273. reconnect = true;
  274. reconnectAttempts++;
  275. reconncetTimer = setTimeout(function() {
  276. connect(params, reconnectUrl, cb);
  277. }, reconnectionDelay);
  278. reconnectionDelay *= 2;
  279. }
  280. };
  281. socket = new WebSocket(url);
  282. socket.binaryType = 'arraybuffer';
  283. socket.onopen = onopen;
  284. socket.onmessage = onmessage;
  285. socket.onerror = onerror;
  286. socket.onclose = onclose;
  287. };
  288. pomelo.disconnect = function() {
  289. if(socket) {
  290. if(socket.disconnect) socket.disconnect();
  291. if(socket.close) socket.close();
  292. console.log('disconnect');
  293. socket = null;
  294. }
  295. if(heartbeatId) {
  296. clearTimeout(heartbeatId);
  297. heartbeatId = null;
  298. }
  299. if(heartbeatTimeoutId) {
  300. clearTimeout(heartbeatTimeoutId);
  301. heartbeatTimeoutId = null;
  302. }
  303. };
  304. var reset = function() {
  305. reconnect = false;
  306. reconnectionDelay = 1000 * 5;
  307. reconnectAttempts = 0;
  308. clearTimeout(reconncetTimer);
  309. };
  310. pomelo.request = function(route, msg, cb) {
  311. if(arguments.length === 2 && typeof msg === 'function') {
  312. cb = msg;
  313. msg = {};
  314. } else {
  315. msg = msg || {};
  316. }
  317. route = route || msg.route;
  318. if(!route) {
  319. return;
  320. }
  321. reqId++;
  322. sendMessage(reqId, route, msg);
  323. callbacks[reqId] = cb;
  324. routeMap[reqId] = route;
  325. };
  326. pomelo.notify = function(route, msg) {
  327. msg = msg || {};
  328. sendMessage(0, route, msg);
  329. };
  330. var sendMessage = function(reqId, route, msg) {
  331. if(useCrypto) {
  332. msg = JSON.stringify(msg);
  333. var sig = rsa.signString(msg, "sha256");
  334. msg = JSON.parse(msg);
  335. msg['__crypto__'] = sig;
  336. }
  337. if(encode) {
  338. msg = encode(reqId, route, msg);
  339. }
  340. var packet = Package.encode(Package.TYPE_DATA, msg);
  341. send(packet);
  342. };
  343. var send = function(packet) {
  344. socket.send(packet.buffer);
  345. };
  346. var handler = {};
  347. var heartbeat = function(data) {
  348. if(!heartbeatInterval) {
  349. // no heartbeat
  350. return;
  351. }
  352. var obj = Package.encode(Package.TYPE_HEARTBEAT);
  353. if(heartbeatTimeoutId) {
  354. clearTimeout(heartbeatTimeoutId);
  355. heartbeatTimeoutId = null;
  356. }
  357. if(heartbeatId) {
  358. // already in a heartbeat interval
  359. return;
  360. }
  361. heartbeatId = setTimeout(function() {
  362. heartbeatId = null;
  363. send(obj);
  364. console.log("send heartbeat");
  365. nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
  366. heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout);
  367. }, heartbeatInterval);
  368. };
  369. var heartbeatTimeoutCb = function() {
  370. var gap = nextHeartbeatTimeout - Date.now();
  371. if(gap > gapThreshold) {
  372. heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap);
  373. } else {
  374. console.error('server heartbeat timeout');
  375. pomelo.emit('heartbeat timeout');
  376. pomelo.disconnect();
  377. }
  378. };
  379. var handshake = function(data) {
  380. data = JSON.parse(Protocol.strdecode(data));
  381. if(data.code === RES_OLD_CLIENT) {
  382. pomelo.emit('error', 'client version not fullfill');
  383. return;
  384. }
  385. if(data.code !== RES_OK) {
  386. pomelo.emit('error', 'handshake fail');
  387. return;
  388. }
  389. handshakeInit(data);
  390. var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
  391. send(obj);
  392. if(initCallback) {
  393. initCallback(socket);
  394. }
  395. };
  396. var onData = function(data) {
  397. var msg = data;
  398. if(decode) {
  399. msg = decode(msg);
  400. }
  401. processMessage(pomelo, msg);
  402. };
  403. var onKick = function(data) {
  404. data = JSON.parse(Protocol.strdecode(data));
  405. pomelo.emit('onKick', data);
  406. };
  407. handlers[Package.TYPE_HANDSHAKE] = handshake;
  408. handlers[Package.TYPE_HEARTBEAT] = heartbeat;
  409. handlers[Package.TYPE_DATA] = onData;
  410. handlers[Package.TYPE_KICK] = onKick;
  411. var processPackage = function(msgs) {
  412. if(Array.isArray(msgs)) {
  413. for(var i=0; i<msgs.length; i++) {
  414. var msg = msgs[i];
  415. handlers[msg.type](msg.body);
  416. }
  417. } else {
  418. handlers[msgs.type](msgs.body);
  419. }
  420. };
  421. var processMessage = function(pomelo, msg) {
  422. if(!msg.id) {
  423. // server push message
  424. pomelo.emit(msg.route, msg.body);
  425. return;
  426. }
  427. //if have a id then find the callback function with the request
  428. var cb = callbacks[msg.id];
  429. delete callbacks[msg.id];
  430. if(typeof cb !== 'function') {
  431. return;
  432. }
  433. cb(msg.body);
  434. };
  435. var processMessageBatch = function(pomelo, msgs) {
  436. for(var i=0, l=msgs.length; i<l; i++) {
  437. processMessage(pomelo, msgs[i]);
  438. }
  439. };
  440. var deCompose = function(msg) {
  441. var route = msg.route;
  442. //Decompose route from dict
  443. if(msg.compressRoute) {
  444. if(!abbrs[route]){
  445. return {};
  446. }
  447. route = msg.route = abbrs[route];
  448. }
  449. if(decodeIO_decoder && decodeIO_decoder.lookup(route)) {
  450. return decodeIO_decoder.build(route).decode(msg.body);
  451. } else {
  452. return JSON.parse(Protocol.strdecode(msg.body));
  453. }
  454. return msg;
  455. };
  456. var handshakeInit = function(data) {
  457. if(data.sys && data.sys.heartbeat) {
  458. heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval
  459. heartbeatTimeout = heartbeatInterval * 2; // max heartbeat timeout
  460. } else {
  461. heartbeatInterval = 0;
  462. heartbeatTimeout = 0;
  463. }
  464. initData(data);
  465. if(typeof handshakeCallback === 'function') {
  466. handshakeCallback(data.user);
  467. }
  468. };
  469. //Initilize data used in pomelo client
  470. var initData = function(data) {
  471. if(!data || !data.sys) {
  472. return;
  473. }
  474. dict = data.sys.dict;
  475. //Init compress dict
  476. if(dict) {
  477. dict = dict;
  478. abbrs = {};
  479. for(var route in dict) {
  480. abbrs[dict[route]] = route;
  481. }
  482. }
  483. window.pomelo = pomelo;
  484. }})();