PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/iolen/node_modules/socket.io/node_modules/socket.io-client/components/learnboost-engine.io-client/lib/socket.js

https://github.com/lenines/rubyrest
JavaScript | 492 lines | 288 code | 78 blank | 126 comment | 71 complexity | df2f97f20423e931da9120d41c9c1ce8 MD5 | raw file
Possible License(s): MIT
  1. /**
  2. * Module dependencies.
  3. */
  4. var util = require('./util')
  5. , transports = require('./transports')
  6. , Emitter = require('./emitter')
  7. , debug = require('debug')('engine-client:socket');
  8. /**
  9. * Module exports.
  10. */
  11. module.exports = Socket;
  12. /**
  13. * Global reference.
  14. */
  15. var global = 'undefined' != typeof window ? window : global;
  16. /**
  17. * Socket constructor.
  18. *
  19. * @param {Object} options
  20. * @api public
  21. */
  22. function Socket(opts){
  23. if (!(this instanceof Socket)) return new Socket(opts);
  24. if ('string' == typeof opts) {
  25. var uri = util.parseUri(opts);
  26. opts = arguments[1] || {};
  27. opts.host = uri.host;
  28. opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
  29. opts.port = uri.port;
  30. }
  31. opts = opts || {};
  32. this.secure = null != opts.secure ? opts.secure : (global.location && 'https:' == location.protocol);
  33. this.host = opts.host || opts.hostname || (global.location ? location.hostname : 'localhost');
  34. this.port = opts.port || (global.location && location.port ? location.port : (this.secure ? 443 : 80));
  35. this.query = opts.query || {};
  36. this.query.uid = rnd();
  37. this.upgrade = false !== opts.upgrade;
  38. this.resource = opts.resource || 'default';
  39. this.path = (opts.path || '/engine.io').replace(/\/$/, '');
  40. this.path += '/' + this.resource + '/';
  41. this.forceJSONP = !!opts.forceJSONP;
  42. this.timestampParam = opts.timestampParam || 't';
  43. this.timestampRequests = !!opts.timestampRequests;
  44. this.flashPath = opts.flashPath || '';
  45. this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
  46. this.readyState = '';
  47. this.writeBuffer = [];
  48. this.policyPort = opts.policyPort || 843;
  49. this.open();
  50. Socket.sockets.push(this);
  51. Socket.sockets.evs.emit('add', this);
  52. };
  53. /**
  54. * Mix in `Emitter`.
  55. */
  56. Emitter(Socket.prototype);
  57. /**
  58. * Protocol version.
  59. *
  60. * @api public
  61. */
  62. Socket.protocol = 1;
  63. /**
  64. * Static EventEmitter.
  65. */
  66. Socket.sockets = [];
  67. Socket.sockets.evs = new Emitter;
  68. /**
  69. * Expose deps for legacy compatibility
  70. * and standalone browser access.
  71. */
  72. Socket.Socket = Socket;
  73. Socket.Transport = require('./transport');
  74. Socket.Emitter = require('./emitter');
  75. Socket.transports = require('./transports');
  76. Socket.util = require('./util');
  77. Socket.parser = require('./parser');
  78. /**
  79. * Creates transport of the given type.
  80. *
  81. * @param {String} transport name
  82. * @return {Transport}
  83. * @api private
  84. */
  85. Socket.prototype.createTransport = function (name) {
  86. debug('creating transport "%s"', name);
  87. var query = clone(this.query);
  88. query.transport = name;
  89. if (this.id) {
  90. query.sid = this.id;
  91. }
  92. var transport = new transports[name]({
  93. host: this.host
  94. , port: this.port
  95. , secure: this.secure
  96. , path: this.path
  97. , query: query
  98. , forceJSONP: this.forceJSONP
  99. , timestampRequests: this.timestampRequests
  100. , timestampParam: this.timestampParam
  101. , flashPath: this.flashPath
  102. , policyPort: this.policyPort
  103. });
  104. return transport;
  105. };
  106. function clone (obj) {
  107. var o = {};
  108. for (var i in obj) {
  109. if (obj.hasOwnProperty(i)) {
  110. o[i] = obj[i];
  111. }
  112. }
  113. return o;
  114. }
  115. /**
  116. * Initializes transport to use and starts probe.
  117. *
  118. * @api private
  119. */
  120. Socket.prototype.open = function () {
  121. this.readyState = 'opening';
  122. var transport = this.createTransport(this.transports[0]);
  123. transport.open();
  124. this.setTransport(transport);
  125. };
  126. /**
  127. * Sets the current transport. Disables the existing one (if any).
  128. *
  129. * @api private
  130. */
  131. Socket.prototype.setTransport = function (transport) {
  132. var self = this;
  133. if (this.transport) {
  134. debug('clearing existing transport');
  135. this.transport.removeAllListeners();
  136. }
  137. // set up transport
  138. this.transport = transport;
  139. // set up transport listeners
  140. transport
  141. .on('drain', function () {
  142. self.flush();
  143. })
  144. .on('packet', function (packet) {
  145. self.onPacket(packet);
  146. })
  147. .on('error', function (e) {
  148. self.onError(e);
  149. })
  150. .on('close', function () {
  151. self.onClose('transport close');
  152. });
  153. };
  154. /**
  155. * Probes a transport.
  156. *
  157. * @param {String} transport name
  158. * @api private
  159. */
  160. Socket.prototype.probe = function (name) {
  161. debug('probing transport "%s"', name);
  162. var transport = this.createTransport(name, { probe: 1 })
  163. , failed = false
  164. , self = this;
  165. transport.once('open', function () {
  166. if (failed) return;
  167. debug('probe transport "%s" opened', name);
  168. transport.send([{ type: 'ping', data: 'probe' }]);
  169. transport.once('packet', function (msg) {
  170. if (failed) return;
  171. if ('pong' == msg.type && 'probe' == msg.data) {
  172. debug('probe transport "%s" pong', name);
  173. self.upgrading = true;
  174. self.emit('upgrading', transport);
  175. debug('pausing current transport "%s"', self.transport.name);
  176. self.transport.pause(function () {
  177. if (failed) return;
  178. if ('closed' == self.readyState || 'closing' == self.readyState) {
  179. return;
  180. }
  181. debug('changing transport and sending upgrade packet');
  182. transport.removeListener('error', onerror);
  183. self.emit('upgrade', transport);
  184. self.setTransport(transport);
  185. transport.send([{ type: 'upgrade' }]);
  186. transport = null;
  187. self.upgrading = false;
  188. self.flush();
  189. });
  190. } else {
  191. debug('probe transport "%s" failed', name);
  192. var err = new Error('probe error');
  193. err.transport = transport.name;
  194. self.emit('error', err);
  195. }
  196. });
  197. });
  198. transport.once('error', onerror);
  199. function onerror(err) {
  200. if (failed) return;
  201. // Any callback called by transport should be ignored since now
  202. failed = true;
  203. var error = new Error('probe error: ' + err);
  204. error.transport = transport.name;
  205. transport.close();
  206. transport = null;
  207. debug('probe transport "%s" failed because of error: %s', name, err);
  208. self.emit('error', error);
  209. };
  210. transport.open();
  211. this.once('close', function () {
  212. if (transport) {
  213. debug('socket closed prematurely - aborting probe');
  214. failed = true;
  215. transport.close();
  216. transport = null;
  217. }
  218. });
  219. this.once('upgrading', function (to) {
  220. if (transport && to.name != transport.name) {
  221. debug('"%s" works - aborting "%s"', to.name, transport.name);
  222. transport.close();
  223. transport = null;
  224. }
  225. });
  226. };
  227. /**
  228. * Called when connection is deemed open.
  229. *
  230. * @api public
  231. */
  232. Socket.prototype.onOpen = function () {
  233. debug('socket open');
  234. this.readyState = 'open';
  235. this.emit('open');
  236. this.onopen && this.onopen.call(this);
  237. this.flush();
  238. // we check for `readyState` in case an `open`
  239. // listener alreay closed the socket
  240. if ('open' == this.readyState && this.upgrade && this.transport.pause) {
  241. debug('starting upgrade probes');
  242. for (var i = 0, l = this.upgrades.length; i < l; i++) {
  243. this.probe(this.upgrades[i]);
  244. }
  245. }
  246. };
  247. /**
  248. * Handles a packet.
  249. *
  250. * @api private
  251. */
  252. Socket.prototype.onPacket = function (packet) {
  253. if ('opening' == this.readyState || 'open' == this.readyState) {
  254. debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
  255. this.emit('packet', packet);
  256. // Socket is live - any packet counts
  257. this.emit('heartbeat');
  258. switch (packet.type) {
  259. case 'open':
  260. this.onHandshake(util.parseJSON(packet.data));
  261. break;
  262. case 'pong':
  263. this.ping();
  264. break;
  265. case 'error':
  266. var err = new Error('server error');
  267. err.code = packet.data;
  268. this.emit('error', err);
  269. break;
  270. case 'message':
  271. this.emit('message', packet.data);
  272. var event = { data: packet.data };
  273. event.toString = function () {
  274. return packet.data;
  275. };
  276. this.onmessage && this.onmessage.call(this, event);
  277. break;
  278. }
  279. } else {
  280. debug('packet received with socket readyState "%s"', this.readyState);
  281. }
  282. };
  283. /**
  284. * Called upon handshake completion.
  285. *
  286. * @param {Object} handshake obj
  287. * @api private
  288. */
  289. Socket.prototype.onHandshake = function (data) {
  290. this.emit('handshake', data);
  291. this.id = data.sid;
  292. this.transport.query.sid = data.sid;
  293. this.upgrades = data.upgrades;
  294. this.pingInterval = data.pingInterval;
  295. this.pingTimeout = data.pingTimeout;
  296. this.onOpen();
  297. this.ping();
  298. // Prolong liveness of socket on heartbeat
  299. this.removeListener('heartbeat', this.onHeartbeat);
  300. this.on('heartbeat', this.onHeartbeat);
  301. };
  302. /**
  303. * Resets ping timeout.
  304. *
  305. * @api private
  306. */
  307. Socket.prototype.onHeartbeat = function (timeout) {
  308. clearTimeout(this.pingTimeoutTimer);
  309. var self = this;
  310. self.pingTimeoutTimer = setTimeout(function () {
  311. if ('closed' == self.readyState) return;
  312. self.onClose('ping timeout');
  313. }, timeout || (self.pingInterval + self.pingTimeout));
  314. };
  315. /**
  316. * Pings server every `this.pingInterval` and expects response
  317. * within `this.pingTimeout` or closes connection.
  318. *
  319. * @api private
  320. */
  321. Socket.prototype.ping = function () {
  322. var self = this;
  323. clearTimeout(self.pingIntervalTimer);
  324. self.pingIntervalTimer = setTimeout(function () {
  325. debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
  326. self.sendPacket('ping');
  327. self.onHeartbeat(self.pingTimeout);
  328. }, self.pingInterval);
  329. };
  330. /**
  331. * Flush write buffers.
  332. *
  333. * @api private
  334. */
  335. Socket.prototype.flush = function () {
  336. if ('closed' != this.readyState && this.transport.writable &&
  337. !this.upgrading && this.writeBuffer.length) {
  338. debug('flushing %d packets in socket', this.writeBuffer.length);
  339. this.transport.send(this.writeBuffer);
  340. this.writeBuffer = [];
  341. }
  342. };
  343. /**
  344. * Sends a message.
  345. *
  346. * @param {String} message.
  347. * @return {Socket} for chaining.
  348. * @api public
  349. */
  350. Socket.prototype.write =
  351. Socket.prototype.send = function (msg) {
  352. this.sendPacket('message', msg);
  353. return this;
  354. };
  355. /**
  356. * Sends a packet.
  357. *
  358. * @param {String} packet type.
  359. * @param {String} data.
  360. * @api private
  361. */
  362. Socket.prototype.sendPacket = function (type, data) {
  363. var packet = { type: type, data: data };
  364. this.emit('packetCreate', packet);
  365. this.writeBuffer.push(packet);
  366. this.flush();
  367. };
  368. /**
  369. * Closes the connection.
  370. *
  371. * @api private
  372. */
  373. Socket.prototype.close = function () {
  374. if ('opening' == this.readyState || 'open' == this.readyState) {
  375. this.onClose('forced close');
  376. debug('socket closing - telling transport to close');
  377. this.transport.close();
  378. this.transport.removeAllListeners();
  379. }
  380. return this;
  381. };
  382. /**
  383. * Called upon transport error
  384. *
  385. * @api private
  386. */
  387. Socket.prototype.onError = function (err) {
  388. this.emit('error', err);
  389. this.onClose('transport error', err);
  390. };
  391. /**
  392. * Called upon transport close.
  393. *
  394. * @api private
  395. */
  396. Socket.prototype.onClose = function (reason, desc) {
  397. if ('closed' != this.readyState) {
  398. debug('socket close with reason: "%s"', reason);
  399. clearTimeout(this.pingIntervalTimer);
  400. clearTimeout(this.pingTimeoutTimer);
  401. this.readyState = 'closed';
  402. this.emit('close', reason, desc);
  403. this.onclose && this.onclose.call(this);
  404. this.id = null;
  405. }
  406. };
  407. /**
  408. * Generates a random uid.
  409. *
  410. * @api private
  411. */
  412. function rnd () {
  413. return String(Math.random()).substr(5) + String(Math.random()).substr(5);
  414. }