You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

987 lines
27 KiB

7 years ago
  1. 'use strict';
  2. /*!
  3. * ws: a node.js websocket client
  4. * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
  5. * MIT Licensed
  6. */
  7. var url = require('url')
  8. , util = require('util')
  9. , http = require('http')
  10. , https = require('https')
  11. , crypto = require('crypto')
  12. , stream = require('stream')
  13. , Ultron = require('ultron')
  14. , Options = require('options')
  15. , Sender = require('./Sender')
  16. , Receiver = require('./Receiver')
  17. , SenderHixie = require('./Sender.hixie')
  18. , ReceiverHixie = require('./Receiver.hixie')
  19. , Extensions = require('./Extensions')
  20. , PerMessageDeflate = require('./PerMessageDeflate')
  21. , EventEmitter = require('events').EventEmitter;
  22. /**
  23. * Constants
  24. */
  25. // Default protocol version
  26. var protocolVersion = 13;
  27. // Close timeout
  28. var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly
  29. /**
  30. * WebSocket implementation
  31. *
  32. * @constructor
  33. * @param {String} address Connection address.
  34. * @param {String|Array} protocols WebSocket protocols.
  35. * @param {Object} options Additional connection options.
  36. * @api public
  37. */
  38. function WebSocket(address, protocols, options) {
  39. if (this instanceof WebSocket === false) {
  40. return new WebSocket(address, protocols, options);
  41. }
  42. EventEmitter.call(this);
  43. if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) {
  44. // accept the "options" Object as the 2nd argument
  45. options = protocols;
  46. protocols = null;
  47. }
  48. if ('string' === typeof protocols) {
  49. protocols = [ protocols ];
  50. }
  51. if (!Array.isArray(protocols)) {
  52. protocols = [];
  53. }
  54. this._socket = null;
  55. this._ultron = null;
  56. this._closeReceived = false;
  57. this.bytesReceived = 0;
  58. this.readyState = null;
  59. this.supports = {};
  60. this.extensions = {};
  61. this._binaryType = 'nodebuffer';
  62. if (Array.isArray(address)) {
  63. initAsServerClient.apply(this, address.concat(options));
  64. } else {
  65. initAsClient.apply(this, [address, protocols, options]);
  66. }
  67. }
  68. /**
  69. * Inherits from EventEmitter.
  70. */
  71. util.inherits(WebSocket, EventEmitter);
  72. /**
  73. * Ready States
  74. */
  75. ["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) {
  76. WebSocket.prototype[state] = WebSocket[state] = index;
  77. });
  78. /**
  79. * Gracefully closes the connection, after sending a description message to the server
  80. *
  81. * @param {Object} data to be sent to the server
  82. * @api public
  83. */
  84. WebSocket.prototype.close = function close(code, data) {
  85. if (this.readyState === WebSocket.CLOSED) return;
  86. if (this.readyState === WebSocket.CONNECTING) {
  87. this.readyState = WebSocket.CLOSED;
  88. return;
  89. }
  90. if (this.readyState === WebSocket.CLOSING) {
  91. if (this._closeReceived && this._isServer) {
  92. this.terminate();
  93. }
  94. return;
  95. }
  96. var self = this;
  97. try {
  98. this.readyState = WebSocket.CLOSING;
  99. this._closeCode = code;
  100. this._closeMessage = data;
  101. var mask = !this._isServer;
  102. this._sender.close(code, data, mask, function(err) {
  103. if (err) self.emit('error', err);
  104. if (self._closeReceived && self._isServer) {
  105. self.terminate();
  106. } else {
  107. // ensure that the connection is cleaned up even when no response of closing handshake.
  108. clearTimeout(self._closeTimer);
  109. self._closeTimer = setTimeout(cleanupWebsocketResources.bind(self, true), closeTimeout);
  110. }
  111. });
  112. } catch (e) {
  113. this.emit('error', e);
  114. }
  115. };
  116. /**
  117. * Pause the client stream
  118. *
  119. * @api public
  120. */
  121. WebSocket.prototype.pause = function pauser() {
  122. if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
  123. return this._socket.pause();
  124. };
  125. /**
  126. * Sends a ping
  127. *
  128. * @param {Object} data to be sent to the server
  129. * @param {Object} Members - mask: boolean, binary: boolean
  130. * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open
  131. * @api public
  132. */
  133. WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) {
  134. if (this.readyState !== WebSocket.OPEN) {
  135. if (dontFailWhenClosed === true) return;
  136. throw new Error('not opened');
  137. }
  138. options = options || {};
  139. if (typeof options.mask === 'undefined') options.mask = !this._isServer;
  140. this._sender.ping(data, options);
  141. };
  142. /**
  143. * Sends a pong
  144. *
  145. * @param {Object} data to be sent to the server
  146. * @param {Object} Members - mask: boolean, binary: boolean
  147. * @param {boolean} dontFailWhenClosed indicates whether or not to throw if the connection isnt open
  148. * @api public
  149. */
  150. WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) {
  151. if (this.readyState !== WebSocket.OPEN) {
  152. if (dontFailWhenClosed === true) return;
  153. throw new Error('not opened');
  154. }
  155. options = options || {};
  156. if (typeof options.mask === 'undefined') options.mask = !this._isServer;
  157. this._sender.pong(data, options);
  158. };
  159. /**
  160. * Resume the client stream
  161. *
  162. * @api public
  163. */
  164. WebSocket.prototype.resume = function resume() {
  165. if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
  166. return this._socket.resume();
  167. };
  168. /**
  169. * Sends a piece of data
  170. *
  171. * @param {Object} data to be sent to the server
  172. * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean
  173. * @param {function} Optional callback which is executed after the send completes
  174. * @api public
  175. */
  176. WebSocket.prototype.send = function send(data, options, cb) {
  177. if (typeof options === 'function') {
  178. cb = options;
  179. options = {};
  180. }
  181. if (this.readyState !== WebSocket.OPEN) {
  182. if (typeof cb === 'function') cb(new Error('not opened'));
  183. else throw new Error('not opened');
  184. return;
  185. }
  186. if (!data) data = '';
  187. if (this._queue) {
  188. var self = this;
  189. this._queue.push(function() { self.send(data, options, cb); });
  190. return;
  191. }
  192. options = options || {};
  193. options.fin = true;
  194. if (typeof options.binary === 'undefined') {
  195. options.binary = (data instanceof ArrayBuffer || data instanceof Buffer ||
  196. data instanceof Uint8Array ||
  197. data instanceof Uint16Array ||
  198. data instanceof Uint32Array ||
  199. data instanceof Int8Array ||
  200. data instanceof Int16Array ||
  201. data instanceof Int32Array ||
  202. data instanceof Float32Array ||
  203. data instanceof Float64Array);
  204. }
  205. if (typeof options.mask === 'undefined') options.mask = !this._isServer;
  206. if (typeof options.compress === 'undefined') options.compress = true;
  207. if (!this.extensions[PerMessageDeflate.extensionName]) {
  208. options.compress = false;
  209. }
  210. var readable = typeof stream.Readable === 'function'
  211. ? stream.Readable
  212. : stream.Stream;
  213. if (data instanceof readable) {
  214. startQueue(this);
  215. var self = this;
  216. sendStream(this, data, options, function send(error) {
  217. process.nextTick(function tock() {
  218. executeQueueSends(self);
  219. });
  220. if (typeof cb === 'function') cb(error);
  221. });
  222. } else {
  223. this._sender.send(data, options, cb);
  224. }
  225. };
  226. /**
  227. * Streams data through calls to a user supplied function
  228. *
  229. * @param {Object} Members - mask: boolean, binary: boolean, compress: boolean
  230. * @param {function} 'function (error, send)' which is executed on successive ticks of which send is 'function (data, final)'.
  231. * @api public
  232. */
  233. WebSocket.prototype.stream = function stream(options, cb) {
  234. if (typeof options === 'function') {
  235. cb = options;
  236. options = {};
  237. }
  238. var self = this;
  239. if (typeof cb !== 'function') throw new Error('callback must be provided');
  240. if (this.readyState !== WebSocket.OPEN) {
  241. if (typeof cb === 'function') cb(new Error('not opened'));
  242. else throw new Error('not opened');
  243. return;
  244. }
  245. if (this._queue) {
  246. this._queue.push(function () { self.stream(options, cb); });
  247. return;
  248. }
  249. options = options || {};
  250. if (typeof options.mask === 'undefined') options.mask = !this._isServer;
  251. if (typeof options.compress === 'undefined') options.compress = true;
  252. if (!this.extensions[PerMessageDeflate.extensionName]) {
  253. options.compress = false;
  254. }
  255. startQueue(this);
  256. function send(data, final) {
  257. try {
  258. if (self.readyState !== WebSocket.OPEN) throw new Error('not opened');
  259. options.fin = final === true;
  260. self._sender.send(data, options);
  261. if (!final) process.nextTick(cb.bind(null, null, send));
  262. else executeQueueSends(self);
  263. } catch (e) {
  264. if (typeof cb === 'function') cb(e);
  265. else {
  266. delete self._queue;
  267. self.emit('error', e);
  268. }
  269. }
  270. }
  271. process.nextTick(cb.bind(null, null, send));
  272. };
  273. /**
  274. * Immediately shuts down the connection
  275. *
  276. * @api public
  277. */
  278. WebSocket.prototype.terminate = function terminate() {
  279. if (this.readyState === WebSocket.CLOSED) return;
  280. if (this._socket) {
  281. this.readyState = WebSocket.CLOSING;
  282. // End the connection
  283. try { this._socket.end(); }
  284. catch (e) {
  285. // Socket error during end() call, so just destroy it right now
  286. cleanupWebsocketResources.call(this, true);
  287. return;
  288. }
  289. // Add a timeout to ensure that the connection is completely
  290. // cleaned up within 30 seconds, even if the clean close procedure
  291. // fails for whatever reason
  292. // First cleanup any pre-existing timeout from an earlier "terminate" call,
  293. // if one exists. Otherwise terminate calls in quick succession will leak timeouts
  294. // and hold the program open for `closeTimout` time.
  295. if (this._closeTimer) { clearTimeout(this._closeTimer); }
  296. this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout);
  297. } else if (this.readyState === WebSocket.CONNECTING) {
  298. cleanupWebsocketResources.call(this, true);
  299. }
  300. };
  301. /**
  302. * Expose bufferedAmount
  303. *
  304. * @api public
  305. */
  306. Object.defineProperty(WebSocket.prototype, 'bufferedAmount', {
  307. get: function get() {
  308. var amount = 0;
  309. if (this._socket) {
  310. amount = this._socket.bufferSize || 0;
  311. }
  312. return amount;
  313. }
  314. });
  315. /**
  316. * Expose binaryType
  317. *
  318. * This deviates from the W3C interface since ws doesn't support the required
  319. * default "blob" type (instead we define a custom "nodebuffer" type).
  320. *
  321. * @see http://dev.w3.org/html5/websockets/#the-websocket-interface
  322. * @api public
  323. */
  324. Object.defineProperty(WebSocket.prototype, 'binaryType', {
  325. get: function get() {
  326. return this._binaryType;
  327. },
  328. set: function set(type) {
  329. if (type === 'arraybuffer' || type === 'nodebuffer')
  330. this._binaryType = type;
  331. else
  332. throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"');
  333. }
  334. });
  335. /**
  336. * Emulates the W3C Browser based WebSocket interface using function members.
  337. *
  338. * @see http://dev.w3.org/html5/websockets/#the-websocket-interface
  339. * @api public
  340. */
  341. ['open', 'error', 'close', 'message'].forEach(function(method) {
  342. Object.defineProperty(WebSocket.prototype, 'on' + method, {
  343. /**
  344. * Returns the current listener
  345. *
  346. * @returns {Mixed} the set function or undefined
  347. * @api public
  348. */
  349. get: function get() {
  350. var listener = this.listeners(method)[0];
  351. return listener ? (listener._listener ? listener._listener : listener) : undefined;
  352. },
  353. /**
  354. * Start listening for events
  355. *
  356. * @param {Function} listener the listener
  357. * @returns {Mixed} the set function or undefined
  358. * @api public
  359. */
  360. set: function set(listener) {
  361. this.removeAllListeners(method);
  362. this.addEventListener(method, listener);
  363. }
  364. });
  365. });
  366. /**
  367. * Emulates the W3C Browser based WebSocket interface using addEventListener.
  368. *
  369. * @see https://developer.mozilla.org/en/DOM/element.addEventListener
  370. * @see http://dev.w3.org/html5/websockets/#the-websocket-interface
  371. * @api public
  372. */
  373. WebSocket.prototype.addEventListener = function(method, listener) {
  374. var target = this;
  375. function onMessage (data, flags) {
  376. if (flags.binary && this.binaryType === 'arraybuffer')
  377. data = new Uint8Array(data).buffer;
  378. listener.call(target, new MessageEvent(data, !!flags.binary, target));
  379. }
  380. function onClose (code, message) {
  381. listener.call(target, new CloseEvent(code, message, target));
  382. }
  383. function onError (event) {
  384. event.type = 'error';
  385. event.target = target;
  386. listener.call(target, event);
  387. }
  388. function onOpen () {
  389. listener.call(target, new OpenEvent(target));
  390. }
  391. if (typeof listener === 'function') {
  392. if (method === 'message') {
  393. // store a reference so we can return the original function from the
  394. // addEventListener hook
  395. onMessage._listener = listener;
  396. this.on(method, onMessage);
  397. } else if (method === 'close') {
  398. // store a reference so we can return the original function from the
  399. // addEventListener hook
  400. onClose._listener = listener;
  401. this.on(method, onClose);
  402. } else if (method === 'error') {
  403. // store a reference so we can return the original function from the
  404. // addEventListener hook
  405. onError._listener = listener;
  406. this.on(method, onError);
  407. } else if (method === 'open') {
  408. // store a reference so we can return the original function from the
  409. // addEventListener hook
  410. onOpen._listener = listener;
  411. this.on(method, onOpen);
  412. } else {
  413. this.on(method, listener);
  414. }
  415. }
  416. };
  417. module.exports = WebSocket;
  418. module.exports.buildHostHeader = buildHostHeader
  419. /**
  420. * W3C MessageEvent
  421. *
  422. * @see http://www.w3.org/TR/html5/comms.html
  423. * @constructor
  424. * @api private
  425. */
  426. function MessageEvent(dataArg, isBinary, target) {
  427. this.type = 'message';
  428. this.data = dataArg;
  429. this.target = target;
  430. this.binary = isBinary; // non-standard.
  431. }
  432. /**
  433. * W3C CloseEvent
  434. *
  435. * @see http://www.w3.org/TR/html5/comms.html
  436. * @constructor
  437. * @api private
  438. */
  439. function CloseEvent(code, reason, target) {
  440. this.type = 'close';
  441. this.wasClean = (typeof code === 'undefined' || code === 1000);
  442. this.code = code;
  443. this.reason = reason;
  444. this.target = target;
  445. }
  446. /**
  447. * W3C OpenEvent
  448. *
  449. * @see http://www.w3.org/TR/html5/comms.html
  450. * @constructor
  451. * @api private
  452. */
  453. function OpenEvent(target) {
  454. this.type = 'open';
  455. this.target = target;
  456. }
  457. // Append port number to Host header, only if specified in the url
  458. // and non-default
  459. function buildHostHeader(isSecure, hostname, port) {
  460. var headerHost = hostname;
  461. if (hostname) {
  462. if ((isSecure && (port != 443)) || (!isSecure && (port != 80))){
  463. headerHost = headerHost + ':' + port;
  464. }
  465. }
  466. return headerHost;
  467. }
  468. /**
  469. * Entirely private apis,
  470. * which may or may not be bound to a sepcific WebSocket instance.
  471. */
  472. function initAsServerClient(req, socket, upgradeHead, options) {
  473. options = new Options({
  474. protocolVersion: protocolVersion,
  475. protocol: null,
  476. extensions: {},
  477. maxPayload: 0
  478. }).merge(options);
  479. // expose state properties
  480. this.protocol = options.value.protocol;
  481. this.protocolVersion = options.value.protocolVersion;
  482. this.extensions = options.value.extensions;
  483. this.supports.binary = (this.protocolVersion !== 'hixie-76');
  484. this.upgradeReq = req;
  485. this.readyState = WebSocket.CONNECTING;
  486. this._isServer = true;
  487. this.maxPayload = options.value.maxPayload;
  488. // establish connection
  489. if (options.value.protocolVersion === 'hixie-76') {
  490. establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead);
  491. } else {
  492. establishConnection.call(this, Receiver, Sender, socket, upgradeHead);
  493. }
  494. }
  495. function initAsClient(address, protocols, options) {
  496. options = new Options({
  497. origin: null,
  498. protocolVersion: protocolVersion,
  499. host: null,
  500. headers: null,
  501. protocol: protocols.join(','),
  502. agent: null,
  503. // ssl-related options
  504. pfx: null,
  505. key: null,
  506. passphrase: null,
  507. cert: null,
  508. ca: null,
  509. ciphers: null,
  510. rejectUnauthorized: null,
  511. perMessageDeflate: true,
  512. localAddress: null
  513. }).merge(options);
  514. if (options.value.protocolVersion !== 8 && options.value.protocolVersion !== 13) {
  515. throw new Error('unsupported protocol version');
  516. }
  517. // verify URL and establish http class
  518. var serverUrl = url.parse(address);
  519. var isUnixSocket = serverUrl.protocol === 'ws+unix:';
  520. if (!serverUrl.host && !isUnixSocket) throw new Error('invalid url');
  521. var isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
  522. var httpObj = isSecure ? https : http;
  523. var port = serverUrl.port || (isSecure ? 443 : 80);
  524. var auth = serverUrl.auth;
  525. // prepare extensions
  526. var extensionsOffer = {};
  527. var perMessageDeflate;
  528. if (options.value.perMessageDeflate) {
  529. perMessageDeflate = new PerMessageDeflate(typeof options.value.perMessageDeflate !== true ? options.value.perMessageDeflate : {}, false);
  530. extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer();
  531. }
  532. // expose state properties
  533. this._isServer = false;
  534. this.url = address;
  535. this.protocolVersion = options.value.protocolVersion;
  536. this.supports.binary = (this.protocolVersion !== 'hixie-76');
  537. // begin handshake
  538. var key = new Buffer(options.value.protocolVersion + '-' + Date.now()).toString('base64');
  539. var shasum = crypto.createHash('sha1');
  540. shasum.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
  541. var expectedServerKey = shasum.digest('base64');
  542. var agent = options.value.agent;
  543. var headerHost = buildHostHeader(isSecure, serverUrl.hostname, port)
  544. var requestOptions = {
  545. port: port,
  546. host: serverUrl.hostname,
  547. headers: {
  548. 'Connection': 'Upgrade',
  549. 'Upgrade': 'websocket',
  550. 'Host': headerHost,
  551. 'Sec-WebSocket-Version': options.value.protocolVersion,
  552. 'Sec-WebSocket-Key': key
  553. }
  554. };
  555. // If we have basic auth.
  556. if (auth) {
  557. requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64');
  558. }
  559. if (options.value.protocol) {
  560. requestOptions.headers['Sec-WebSocket-Protocol'] = options.value.protocol;
  561. }
  562. if (options.value.host) {
  563. requestOptions.headers.Host = options.value.host;
  564. }
  565. if (options.value.headers) {
  566. for (var header in options.value.headers) {
  567. if (options.value.headers.hasOwnProperty(header)) {
  568. requestOptions.headers[header] = options.value.headers[header];
  569. }
  570. }
  571. }
  572. if (Object.keys(extensionsOffer).length) {
  573. requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer);
  574. }
  575. if (options.isDefinedAndNonNull('pfx')
  576. || options.isDefinedAndNonNull('key')
  577. || options.isDefinedAndNonNull('passphrase')
  578. || options.isDefinedAndNonNull('cert')
  579. || options.isDefinedAndNonNull('ca')
  580. || options.isDefinedAndNonNull('ciphers')
  581. || options.isDefinedAndNonNull('rejectUnauthorized')) {
  582. if (options.isDefinedAndNonNull('pfx')) requestOptions.pfx = options.value.pfx;
  583. if (options.isDefinedAndNonNull('key')) requestOptions.key = options.value.key;
  584. if (options.isDefinedAndNonNull('passphrase')) requestOptions.passphrase = options.value.passphrase;
  585. if (options.isDefinedAndNonNull('cert')) requestOptions.cert = options.value.cert;
  586. if (options.isDefinedAndNonNull('ca')) requestOptions.ca = options.value.ca;
  587. if (options.isDefinedAndNonNull('ciphers')) requestOptions.ciphers = options.value.ciphers;
  588. if (options.isDefinedAndNonNull('rejectUnauthorized')) requestOptions.rejectUnauthorized = options.value.rejectUnauthorized;
  589. if (!agent) {
  590. // global agent ignores client side certificates
  591. agent = new httpObj.Agent(requestOptions);
  592. }
  593. }
  594. requestOptions.path = serverUrl.path || '/';
  595. if (agent) {
  596. requestOptions.agent = agent;
  597. }
  598. if (isUnixSocket) {
  599. requestOptions.socketPath = serverUrl.pathname;
  600. }
  601. if (options.value.localAddress) {
  602. requestOptions.localAddress = options.value.localAddress;
  603. }
  604. if (options.value.origin) {
  605. if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin;
  606. else requestOptions.headers.Origin = options.value.origin;
  607. }
  608. var self = this;
  609. var req = httpObj.request(requestOptions);
  610. req.on('error', function onerror(error) {
  611. self.emit('error', error);
  612. cleanupWebsocketResources.call(self, error);
  613. });
  614. req.once('response', function response(res) {
  615. var error;
  616. if (!self.emit('unexpected-response', req, res)) {
  617. error = new Error('unexpected server response (' + res.statusCode + ')');
  618. req.abort();
  619. self.emit('error', error);
  620. }
  621. cleanupWebsocketResources.call(self, error);
  622. });
  623. req.once('upgrade', function upgrade(res, socket, upgradeHead) {
  624. if (self.readyState === WebSocket.CLOSED) {
  625. // client closed before server accepted connection
  626. self.emit('close');
  627. self.removeAllListeners();
  628. socket.end();
  629. return;
  630. }
  631. var serverKey = res.headers['sec-websocket-accept'];
  632. if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) {
  633. self.emit('error', 'invalid server key');
  634. self.removeAllListeners();
  635. socket.end();
  636. return;
  637. }
  638. var serverProt = res.headers['sec-websocket-protocol'];
  639. var protList = (options.value.protocol || "").split(/, */);
  640. var protError = null;
  641. if (!options.value.protocol && serverProt) {
  642. protError = 'server sent a subprotocol even though none requested';
  643. } else if (options.value.protocol && !serverProt) {
  644. protError = 'server sent no subprotocol even though requested';
  645. } else if (serverProt && protList.indexOf(serverProt) === -1) {
  646. protError = 'server responded with an invalid protocol';
  647. }
  648. if (protError) {
  649. self.emit('error', protError);
  650. self.removeAllListeners();
  651. socket.end();
  652. return;
  653. } else if (serverProt) {
  654. self.protocol = serverProt;
  655. }
  656. var serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']);
  657. if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) {
  658. try {
  659. perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]);
  660. } catch (err) {
  661. self.emit('error', 'invalid extension parameter');
  662. self.removeAllListeners();
  663. socket.end();
  664. return;
  665. }
  666. self.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
  667. }
  668. establishConnection.call(self, Receiver, Sender, socket, upgradeHead);
  669. // perform cleanup on http resources
  670. req.removeAllListeners();
  671. req = null;
  672. agent = null;
  673. });
  674. req.end();
  675. this.readyState = WebSocket.CONNECTING;
  676. }
  677. function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) {
  678. var ultron = this._ultron = new Ultron(socket)
  679. , called = false
  680. , self = this;
  681. socket.setTimeout(0);
  682. socket.setNoDelay(true);
  683. this._receiver = new ReceiverClass(this.extensions,this.maxPayload);
  684. this._socket = socket;
  685. // socket cleanup handlers
  686. ultron.on('end', cleanupWebsocketResources.bind(this));
  687. ultron.on('close', cleanupWebsocketResources.bind(this));
  688. ultron.on('error', cleanupWebsocketResources.bind(this));
  689. // ensure that the upgradeHead is added to the receiver
  690. function firstHandler(data) {
  691. if (called || self.readyState === WebSocket.CLOSED) return;
  692. called = true;
  693. socket.removeListener('data', firstHandler);
  694. ultron.on('data', realHandler);
  695. if (upgradeHead && upgradeHead.length > 0) {
  696. realHandler(upgradeHead);
  697. upgradeHead = null;
  698. }
  699. if (data) realHandler(data);
  700. }
  701. // subsequent packets are pushed straight to the receiver
  702. function realHandler(data) {
  703. self.bytesReceived += data.length;
  704. self._receiver.add(data);
  705. }
  706. ultron.on('data', firstHandler);
  707. // if data was passed along with the http upgrade,
  708. // this will schedule a push of that on to the receiver.
  709. // this has to be done on next tick, since the caller
  710. // hasn't had a chance to set event handlers on this client
  711. // object yet.
  712. process.nextTick(firstHandler);
  713. // receiver event handlers
  714. self._receiver.ontext = function ontext(data, flags) {
  715. flags = flags || {};
  716. self.emit('message', data, flags);
  717. };
  718. self._receiver.onbinary = function onbinary(data, flags) {
  719. flags = flags || {};
  720. flags.binary = true;
  721. self.emit('message', data, flags);
  722. };
  723. self._receiver.onping = function onping(data, flags) {
  724. flags = flags || {};
  725. self.pong(data, {
  726. mask: !self._isServer,
  727. binary: flags.binary === true
  728. }, true);
  729. self.emit('ping', data, flags);
  730. };
  731. self._receiver.onpong = function onpong(data, flags) {
  732. self.emit('pong', data, flags || {});
  733. };
  734. self._receiver.onclose = function onclose(code, data, flags) {
  735. flags = flags || {};
  736. self._closeReceived = true;
  737. self.close(code, data);
  738. };
  739. self._receiver.onerror = function onerror(reason, errorCode) {
  740. // close the connection when the receiver reports a HyBi error code
  741. self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, '');
  742. self.emit('error', (reason instanceof Error) ? reason : (new Error(reason)));
  743. };
  744. // finalize the client
  745. this._sender = new SenderClass(socket, this.extensions);
  746. this._sender.on('error', function onerror(error) {
  747. self.close(1002, '');
  748. self.emit('error', error);
  749. });
  750. this.readyState = WebSocket.OPEN;
  751. this.emit('open');
  752. }
  753. function startQueue(instance) {
  754. instance._queue = instance._queue || [];
  755. }
  756. function executeQueueSends(instance) {
  757. var queue = instance._queue;
  758. if (typeof queue === 'undefined') return;
  759. delete instance._queue;
  760. for (var i = 0, l = queue.length; i < l; ++i) {
  761. queue[i]();
  762. }
  763. }
  764. function sendStream(instance, stream, options, cb) {
  765. stream.on('data', function incoming(data) {
  766. if (instance.readyState !== WebSocket.OPEN) {
  767. if (typeof cb === 'function') cb(new Error('not opened'));
  768. else {
  769. delete instance._queue;
  770. instance.emit('error', new Error('not opened'));
  771. }
  772. return;
  773. }
  774. options.fin = false;
  775. instance._sender.send(data, options);
  776. });
  777. stream.on('end', function end() {
  778. if (instance.readyState !== WebSocket.OPEN) {
  779. if (typeof cb === 'function') cb(new Error('not opened'));
  780. else {
  781. delete instance._queue;
  782. instance.emit('error', new Error('not opened'));
  783. }
  784. return;
  785. }
  786. options.fin = true;
  787. instance._sender.send(null, options);
  788. if (typeof cb === 'function') cb(null);
  789. });
  790. }
  791. function cleanupWebsocketResources(error) {
  792. if (this.readyState === WebSocket.CLOSED) return;
  793. this.readyState = WebSocket.CLOSED;
  794. clearTimeout(this._closeTimer);
  795. this._closeTimer = null;
  796. // If the connection was closed abnormally (with an error), or if
  797. // the close control frame was not received then the close code
  798. // must default to 1006.
  799. if (error || !this._closeReceived) {
  800. this._closeCode = 1006;
  801. }
  802. this.emit('close', this._closeCode || 1000, this._closeMessage || '');
  803. if (this._socket) {
  804. if (this._ultron) this._ultron.destroy();
  805. this._socket.on('error', function onerror() {
  806. try { this.destroy(); }
  807. catch (e) {}
  808. });
  809. try {
  810. if (!error) this._socket.end();
  811. else this._socket.destroy();
  812. } catch (e) { /* Ignore termination errors */ }
  813. this._socket = null;
  814. this._ultron = null;
  815. }
  816. if (this._sender) {
  817. this._sender.removeAllListeners();
  818. this._sender = null;
  819. }
  820. if (this._receiver) {
  821. this._receiver.cleanup();
  822. this._receiver = null;
  823. }
  824. if (this.extensions[PerMessageDeflate.extensionName]) {
  825. this.extensions[PerMessageDeflate.extensionName].cleanup();
  826. }
  827. this.extensions = null;
  828. this.removeAllListeners();
  829. this.on('error', function onerror() {}); // catch all errors after this
  830. delete this._queue;
  831. }