PageRenderTime 48ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/platforms/node/node_modules/socket.io/node_modules/socket.io-client/node_modules/engine.io-client/lib/socket.js

https://github.com/paramarco/visible
JavaScript | 627 lines | 359 code | 106 blank | 162 comment | 91 complexity | 06b2d9d50ce26bd69f96e6c24e8864a4 MD5 | raw file
Possible License(s): MIT, 0BSD, Apache-2.0, GPL-2.0, BSD-3-Clause
  1. /**
  2. * Module dependencies.
  3. */
  4. var transports = require('./transports');
  5. var Emitter = require('component-emitter');
  6. var debug = require('debug')('engine.io-client:socket');
  7. var index = require('indexof');
  8. var parser = require('engine.io-parser');
  9. var parseuri = require('parseuri');
  10. var parsejson = require('parsejson');
  11. var parseqs = require('parseqs');
  12. /**
  13. * Module exports.
  14. */
  15. module.exports = Socket;
  16. /**
  17. * Noop function.
  18. *
  19. * @api private
  20. */
  21. function noop(){}
  22. /**
  23. * Socket constructor.
  24. *
  25. * @param {String|Object} uri or options
  26. * @param {Object} options
  27. * @api public
  28. */
  29. function Socket(uri, opts){
  30. if (!(this instanceof Socket)) return new Socket(uri, opts);
  31. opts = opts || {};
  32. if (uri && 'object' == typeof uri) {
  33. opts = uri;
  34. uri = null;
  35. }
  36. if (uri) {
  37. uri = parseuri(uri);
  38. opts.host = uri.host;
  39. opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
  40. opts.port = uri.port;
  41. if (uri.query) opts.query = uri.query;
  42. }
  43. this.secure = null != opts.secure ? opts.secure :
  44. (global.location && 'https:' == location.protocol);
  45. if (opts.host) {
  46. var pieces = opts.host.split(':');
  47. opts.hostname = pieces.shift();
  48. if (pieces.length) opts.port = pieces.pop();
  49. }
  50. this.agent = opts.agent || false;
  51. this.hostname = opts.hostname ||
  52. (global.location ? location.hostname : 'localhost');
  53. this.port = opts.port || (global.location && location.port ?
  54. location.port :
  55. (this.secure ? 443 : 80));
  56. this.query = opts.query || {};
  57. if ('string' == typeof this.query) this.query = parseqs.decode(this.query);
  58. this.upgrade = false !== opts.upgrade;
  59. this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
  60. this.forceJSONP = !!opts.forceJSONP;
  61. this.forceBase64 = !!opts.forceBase64;
  62. this.timestampParam = opts.timestampParam || 't';
  63. this.timestampRequests = opts.timestampRequests;
  64. this.transports = opts.transports || ['polling', 'websocket'];
  65. this.readyState = '';
  66. this.writeBuffer = [];
  67. this.callbackBuffer = [];
  68. this.policyPort = opts.policyPort || 843;
  69. this.rememberUpgrade = opts.rememberUpgrade || false;
  70. this.open();
  71. this.binaryType = null;
  72. this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
  73. }
  74. Socket.priorWebsocketSuccess = false;
  75. /**
  76. * Mix in `Emitter`.
  77. */
  78. Emitter(Socket.prototype);
  79. /**
  80. * Protocol version.
  81. *
  82. * @api public
  83. */
  84. Socket.protocol = parser.protocol; // this is an int
  85. /**
  86. * Expose deps for legacy compatibility
  87. * and standalone browser access.
  88. */
  89. Socket.Socket = Socket;
  90. Socket.Transport = require('./transport');
  91. Socket.transports = require('./transports');
  92. Socket.parser = require('engine.io-parser');
  93. /**
  94. * Creates transport of the given type.
  95. *
  96. * @param {String} transport name
  97. * @return {Transport}
  98. * @api private
  99. */
  100. Socket.prototype.createTransport = function (name) {
  101. debug('creating transport "%s"', name);
  102. var query = clone(this.query);
  103. // append engine.io protocol identifier
  104. query.EIO = parser.protocol;
  105. // transport name
  106. query.transport = name;
  107. // session id if we already have one
  108. if (this.id) query.sid = this.id;
  109. var transport = new transports[name]({
  110. agent: this.agent,
  111. hostname: this.hostname,
  112. port: this.port,
  113. secure: this.secure,
  114. path: this.path,
  115. query: query,
  116. forceJSONP: this.forceJSONP,
  117. forceBase64: this.forceBase64,
  118. timestampRequests: this.timestampRequests,
  119. timestampParam: this.timestampParam,
  120. policyPort: this.policyPort,
  121. socket: this
  122. });
  123. return transport;
  124. };
  125. function clone (obj) {
  126. var o = {};
  127. for (var i in obj) {
  128. if (obj.hasOwnProperty(i)) {
  129. o[i] = obj[i];
  130. }
  131. }
  132. return o;
  133. }
  134. /**
  135. * Initializes transport to use and starts probe.
  136. *
  137. * @api private
  138. */
  139. Socket.prototype.open = function () {
  140. var transport;
  141. if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) {
  142. transport = 'websocket';
  143. } else {
  144. transport = this.transports[0];
  145. }
  146. this.readyState = 'opening';
  147. var transport = this.createTransport(transport);
  148. transport.open();
  149. this.setTransport(transport);
  150. };
  151. /**
  152. * Sets the current transport. Disables the existing one (if any).
  153. *
  154. * @api private
  155. */
  156. Socket.prototype.setTransport = function(transport){
  157. debug('setting transport %s', transport.name);
  158. var self = this;
  159. if (this.transport) {
  160. debug('clearing existing transport %s', this.transport.name);
  161. this.transport.removeAllListeners();
  162. }
  163. // set up transport
  164. this.transport = transport;
  165. // set up transport listeners
  166. transport
  167. .on('drain', function(){
  168. self.onDrain();
  169. })
  170. .on('packet', function(packet){
  171. self.onPacket(packet);
  172. })
  173. .on('error', function(e){
  174. self.onError(e);
  175. })
  176. .on('close', function(){
  177. self.onClose('transport close');
  178. });
  179. };
  180. /**
  181. * Probes a transport.
  182. *
  183. * @param {String} transport name
  184. * @api private
  185. */
  186. Socket.prototype.probe = function (name) {
  187. debug('probing transport "%s"', name);
  188. var transport = this.createTransport(name, { probe: 1 })
  189. , failed = false
  190. , self = this;
  191. Socket.priorWebsocketSuccess = false;
  192. function onTransportOpen(){
  193. if (self.onlyBinaryUpgrades) {
  194. var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
  195. failed = failed || upgradeLosesBinary;
  196. }
  197. if (failed) return;
  198. debug('probe transport "%s" opened', name);
  199. transport.send([{ type: 'ping', data: 'probe' }]);
  200. transport.once('packet', function (msg) {
  201. if (failed) return;
  202. if ('pong' == msg.type && 'probe' == msg.data) {
  203. debug('probe transport "%s" pong', name);
  204. self.upgrading = true;
  205. self.emit('upgrading', transport);
  206. Socket.priorWebsocketSuccess = 'websocket' == transport.name;
  207. debug('pausing current transport "%s"', self.transport.name);
  208. self.transport.pause(function () {
  209. if (failed) return;
  210. if ('closed' == self.readyState || 'closing' == self.readyState) {
  211. return;
  212. }
  213. debug('changing transport and sending upgrade packet');
  214. cleanup();
  215. self.setTransport(transport);
  216. transport.send([{ type: 'upgrade' }]);
  217. self.emit('upgrade', transport);
  218. transport = null;
  219. self.upgrading = false;
  220. self.flush();
  221. });
  222. } else {
  223. debug('probe transport "%s" failed', name);
  224. var err = new Error('probe error');
  225. err.transport = transport.name;
  226. self.emit('upgradeError', err);
  227. }
  228. });
  229. }
  230. function freezeTransport() {
  231. if (failed) return;
  232. // Any callback called by transport should be ignored since now
  233. failed = true;
  234. cleanup();
  235. transport.close();
  236. transport = null;
  237. }
  238. //Handle any error that happens while probing
  239. function onerror(err) {
  240. var error = new Error('probe error: ' + err);
  241. error.transport = transport.name;
  242. freezeTransport();
  243. debug('probe transport "%s" failed because of error: %s', name, err);
  244. self.emit('upgradeError', error);
  245. }
  246. function onTransportClose(){
  247. onerror("transport closed");
  248. }
  249. //When the socket is closed while we're probing
  250. function onclose(){
  251. onerror("socket closed");
  252. }
  253. //When the socket is upgraded while we're probing
  254. function onupgrade(to){
  255. if (transport && to.name != transport.name) {
  256. debug('"%s" works - aborting "%s"', to.name, transport.name);
  257. freezeTransport();
  258. }
  259. }
  260. //Remove all listeners on the transport and on self
  261. function cleanup(){
  262. transport.removeListener('open', onTransportOpen);
  263. transport.removeListener('error', onerror);
  264. transport.removeListener('close', onTransportClose);
  265. self.removeListener('close', onclose);
  266. self.removeListener('upgrading', onupgrade);
  267. }
  268. transport.once('open', onTransportOpen);
  269. transport.once('error', onerror);
  270. transport.once('close', onTransportClose);
  271. this.once('close', onclose);
  272. this.once('upgrading', onupgrade);
  273. transport.open();
  274. };
  275. /**
  276. * Called when connection is deemed open.
  277. *
  278. * @api public
  279. */
  280. Socket.prototype.onOpen = function () {
  281. debug('socket open');
  282. this.readyState = 'open';
  283. Socket.priorWebsocketSuccess = 'websocket' == this.transport.name;
  284. this.emit('open');
  285. this.flush();
  286. // we check for `readyState` in case an `open`
  287. // listener already closed the socket
  288. if ('open' == this.readyState && this.upgrade && this.transport.pause) {
  289. debug('starting upgrade probes');
  290. for (var i = 0, l = this.upgrades.length; i < l; i++) {
  291. this.probe(this.upgrades[i]);
  292. }
  293. }
  294. };
  295. /**
  296. * Handles a packet.
  297. *
  298. * @api private
  299. */
  300. Socket.prototype.onPacket = function (packet) {
  301. if ('opening' == this.readyState || 'open' == this.readyState) {
  302. debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
  303. this.emit('packet', packet);
  304. // Socket is live - any packet counts
  305. this.emit('heartbeat');
  306. switch (packet.type) {
  307. case 'open':
  308. this.onHandshake(parsejson(packet.data));
  309. break;
  310. case 'pong':
  311. this.setPing();
  312. break;
  313. case 'error':
  314. var err = new Error('server error');
  315. err.code = packet.data;
  316. this.emit('error', err);
  317. break;
  318. case 'message':
  319. this.emit('data', packet.data);
  320. this.emit('message', packet.data);
  321. break;
  322. }
  323. } else {
  324. debug('packet received with socket readyState "%s"', this.readyState);
  325. }
  326. };
  327. /**
  328. * Called upon handshake completion.
  329. *
  330. * @param {Object} handshake obj
  331. * @api private
  332. */
  333. Socket.prototype.onHandshake = function (data) {
  334. this.emit('handshake', data);
  335. this.id = data.sid;
  336. this.transport.query.sid = data.sid;
  337. this.upgrades = this.filterUpgrades(data.upgrades);
  338. this.pingInterval = data.pingInterval;
  339. this.pingTimeout = data.pingTimeout;
  340. this.onOpen();
  341. // In case open handler closes socket
  342. if ('closed' == this.readyState) return;
  343. this.setPing();
  344. // Prolong liveness of socket on heartbeat
  345. this.removeListener('heartbeat', this.onHeartbeat);
  346. this.on('heartbeat', this.onHeartbeat);
  347. };
  348. /**
  349. * Resets ping timeout.
  350. *
  351. * @api private
  352. */
  353. Socket.prototype.onHeartbeat = function (timeout) {
  354. clearTimeout(this.pingTimeoutTimer);
  355. var self = this;
  356. self.pingTimeoutTimer = setTimeout(function () {
  357. if ('closed' == self.readyState) return;
  358. self.onClose('ping timeout');
  359. }, timeout || (self.pingInterval + self.pingTimeout));
  360. };
  361. /**
  362. * Pings server every `this.pingInterval` and expects response
  363. * within `this.pingTimeout` or closes connection.
  364. *
  365. * @api private
  366. */
  367. Socket.prototype.setPing = function () {
  368. var self = this;
  369. clearTimeout(self.pingIntervalTimer);
  370. self.pingIntervalTimer = setTimeout(function () {
  371. debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
  372. self.ping();
  373. self.onHeartbeat(self.pingTimeout);
  374. }, self.pingInterval);
  375. };
  376. /**
  377. * Sends a ping packet.
  378. *
  379. * @api public
  380. */
  381. Socket.prototype.ping = function () {
  382. this.sendPacket('ping');
  383. };
  384. /**
  385. * Called on `drain` event
  386. *
  387. * @api private
  388. */
  389. Socket.prototype.onDrain = function() {
  390. for (var i = 0; i < this.prevBufferLen; i++) {
  391. if (this.callbackBuffer[i]) {
  392. this.callbackBuffer[i]();
  393. }
  394. }
  395. this.writeBuffer.splice(0, this.prevBufferLen);
  396. this.callbackBuffer.splice(0, this.prevBufferLen);
  397. // setting prevBufferLen = 0 is very important
  398. // for example, when upgrading, upgrade packet is sent over,
  399. // and a nonzero prevBufferLen could cause problems on `drain`
  400. this.prevBufferLen = 0;
  401. if (this.writeBuffer.length == 0) {
  402. this.emit('drain');
  403. } else {
  404. this.flush();
  405. }
  406. };
  407. /**
  408. * Flush write buffers.
  409. *
  410. * @api private
  411. */
  412. Socket.prototype.flush = function () {
  413. if ('closed' != this.readyState && this.transport.writable &&
  414. !this.upgrading && this.writeBuffer.length) {
  415. debug('flushing %d packets in socket', this.writeBuffer.length);
  416. this.transport.send(this.writeBuffer);
  417. // keep track of current length of writeBuffer
  418. // splice writeBuffer and callbackBuffer on `drain`
  419. this.prevBufferLen = this.writeBuffer.length;
  420. this.emit('flush');
  421. }
  422. };
  423. /**
  424. * Sends a message.
  425. *
  426. * @param {String} message.
  427. * @param {Function} callback function.
  428. * @return {Socket} for chaining.
  429. * @api public
  430. */
  431. Socket.prototype.write =
  432. Socket.prototype.send = function (msg, fn) {
  433. this.sendPacket('message', msg, fn);
  434. return this;
  435. };
  436. /**
  437. * Sends a packet.
  438. *
  439. * @param {String} packet type.
  440. * @param {String} data.
  441. * @param {Function} callback function.
  442. * @api private
  443. */
  444. Socket.prototype.sendPacket = function (type, data, fn) {
  445. var packet = { type: type, data: data };
  446. this.emit('packetCreate', packet);
  447. this.writeBuffer.push(packet);
  448. this.callbackBuffer.push(fn);
  449. this.flush();
  450. };
  451. /**
  452. * Closes the connection.
  453. *
  454. * @api private
  455. */
  456. Socket.prototype.close = function () {
  457. if ('opening' == this.readyState || 'open' == this.readyState) {
  458. this.onClose('forced close');
  459. debug('socket closing - telling transport to close');
  460. this.transport.close();
  461. }
  462. return this;
  463. };
  464. /**
  465. * Called upon transport error
  466. *
  467. * @api private
  468. */
  469. Socket.prototype.onError = function (err) {
  470. debug('socket error %j', err);
  471. Socket.priorWebsocketSuccess = false;
  472. this.emit('error', err);
  473. this.onClose('transport error', err);
  474. };
  475. /**
  476. * Called upon transport close.
  477. *
  478. * @api private
  479. */
  480. Socket.prototype.onClose = function (reason, desc) {
  481. if ('opening' == this.readyState || 'open' == this.readyState) {
  482. debug('socket close with reason: "%s"', reason);
  483. var self = this;
  484. // clear timers
  485. clearTimeout(this.pingIntervalTimer);
  486. clearTimeout(this.pingTimeoutTimer);
  487. // clean buffers in next tick, so developers can still
  488. // grab the buffers on `close` event
  489. setTimeout(function() {
  490. self.writeBuffer = [];
  491. self.callbackBuffer = [];
  492. self.prevBufferLen = 0;
  493. }, 0);
  494. // stop event from firing again for transport
  495. this.transport.removeAllListeners('close');
  496. // ensure transport won't stay open
  497. this.transport.close();
  498. // ignore further transport communication
  499. this.transport.removeAllListeners();
  500. // set ready state
  501. this.readyState = 'closed';
  502. // clear session id
  503. this.id = null;
  504. // emit close event
  505. this.emit('close', reason, desc);
  506. }
  507. };
  508. /**
  509. * Filters upgrades, returning only those matching client transports.
  510. *
  511. * @param {Array} server upgrades
  512. * @api private
  513. *
  514. */
  515. Socket.prototype.filterUpgrades = function (upgrades) {
  516. var filteredUpgrades = [];
  517. for (var i = 0, j = upgrades.length; i<j; i++) {
  518. if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]);
  519. }
  520. return filteredUpgrades;
  521. };