Ein Projekt das es ermöglicht Beerpong über das Internet von zwei unabhängigen positionen aus zu spielen. Entstehung im Rahmen einer Praktikumsaufgabe im Fach Interaktion.
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.

WebSocket.js 25KB

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