websocket.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /**
  2. * Module dependencies.
  3. */
  4. var Transport = require('../transport');
  5. var parser = require('engine.io-parser');
  6. var parseqs = require('parseqs');
  7. var inherit = require('component-inherit');
  8. var yeast = require('yeast');
  9. var debug = require('debug')('engine.io-client:websocket');
  10. var BrowserWebSocket, NodeWebSocket;
  11. if (typeof WebSocket !== 'undefined') {
  12. BrowserWebSocket = WebSocket;
  13. } else if (typeof self !== 'undefined') {
  14. BrowserWebSocket = self.WebSocket || self.MozWebSocket;
  15. } else {
  16. try {
  17. NodeWebSocket = require('ws');
  18. } catch (e) { }
  19. }
  20. /**
  21. * Get either the `WebSocket` or `MozWebSocket` globals
  22. * in the browser or try to resolve WebSocket-compatible
  23. * interface exposed by `ws` for Node-like environment.
  24. */
  25. var WebSocketImpl = BrowserWebSocket || NodeWebSocket;
  26. /**
  27. * Module exports.
  28. */
  29. module.exports = WS;
  30. /**
  31. * WebSocket transport constructor.
  32. *
  33. * @api {Object} connection options
  34. * @api public
  35. */
  36. function WS (opts) {
  37. var forceBase64 = (opts && opts.forceBase64);
  38. if (forceBase64) {
  39. this.supportsBinary = false;
  40. }
  41. this.perMessageDeflate = opts.perMessageDeflate;
  42. this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode;
  43. this.protocols = opts.protocols;
  44. if (!this.usingBrowserWebSocket) {
  45. WebSocketImpl = NodeWebSocket;
  46. }
  47. Transport.call(this, opts);
  48. }
  49. /**
  50. * Inherits from Transport.
  51. */
  52. inherit(WS, Transport);
  53. /**
  54. * Transport name.
  55. *
  56. * @api public
  57. */
  58. WS.prototype.name = 'websocket';
  59. /*
  60. * WebSockets support binary
  61. */
  62. WS.prototype.supportsBinary = true;
  63. /**
  64. * Opens socket.
  65. *
  66. * @api private
  67. */
  68. WS.prototype.doOpen = function () {
  69. if (!this.check()) {
  70. // let probe timeout
  71. return;
  72. }
  73. var uri = this.uri();
  74. var protocols = this.protocols;
  75. var opts = {
  76. agent: this.agent,
  77. perMessageDeflate: this.perMessageDeflate
  78. };
  79. // SSL options for Node.js client
  80. opts.pfx = this.pfx;
  81. opts.key = this.key;
  82. opts.passphrase = this.passphrase;
  83. opts.cert = this.cert;
  84. opts.ca = this.ca;
  85. opts.ciphers = this.ciphers;
  86. opts.rejectUnauthorized = this.rejectUnauthorized;
  87. if (this.extraHeaders) {
  88. opts.headers = this.extraHeaders;
  89. }
  90. if (this.localAddress) {
  91. opts.localAddress = this.localAddress;
  92. }
  93. try {
  94. this.ws =
  95. this.usingBrowserWebSocket && !this.isReactNative
  96. ? protocols
  97. ? new WebSocketImpl(uri, protocols)
  98. : new WebSocketImpl(uri)
  99. : new WebSocketImpl(uri, protocols, opts);
  100. } catch (err) {
  101. return this.emit('error', err);
  102. }
  103. if (this.ws.binaryType === undefined) {
  104. this.supportsBinary = false;
  105. }
  106. if (this.ws.supports && this.ws.supports.binary) {
  107. this.supportsBinary = true;
  108. this.ws.binaryType = 'nodebuffer';
  109. } else {
  110. this.ws.binaryType = 'arraybuffer';
  111. }
  112. this.addEventListeners();
  113. };
  114. /**
  115. * Adds event listeners to the socket
  116. *
  117. * @api private
  118. */
  119. WS.prototype.addEventListeners = function () {
  120. var self = this;
  121. this.ws.onopen = function () {
  122. self.onOpen();
  123. };
  124. this.ws.onclose = function () {
  125. self.onClose();
  126. };
  127. this.ws.onmessage = function (ev) {
  128. self.onData(ev.data);
  129. };
  130. this.ws.onerror = function (e) {
  131. self.onError('websocket error', e);
  132. };
  133. };
  134. /**
  135. * Writes data to socket.
  136. *
  137. * @param {Array} array of packets.
  138. * @api private
  139. */
  140. WS.prototype.write = function (packets) {
  141. var self = this;
  142. this.writable = false;
  143. // encodePacket efficient as it uses WS framing
  144. // no need for encodePayload
  145. var total = packets.length;
  146. for (var i = 0, l = total; i < l; i++) {
  147. (function (packet) {
  148. parser.encodePacket(packet, self.supportsBinary, function (data) {
  149. if (!self.usingBrowserWebSocket) {
  150. // always create a new object (GH-437)
  151. var opts = {};
  152. if (packet.options) {
  153. opts.compress = packet.options.compress;
  154. }
  155. if (self.perMessageDeflate) {
  156. var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length;
  157. if (len < self.perMessageDeflate.threshold) {
  158. opts.compress = false;
  159. }
  160. }
  161. }
  162. // Sometimes the websocket has already been closed but the browser didn't
  163. // have a chance of informing us about it yet, in that case send will
  164. // throw an error
  165. try {
  166. if (self.usingBrowserWebSocket) {
  167. // TypeError is thrown when passing the second argument on Safari
  168. self.ws.send(data);
  169. } else {
  170. self.ws.send(data, opts);
  171. }
  172. } catch (e) {
  173. debug('websocket closed before onclose event');
  174. }
  175. --total || done();
  176. });
  177. })(packets[i]);
  178. }
  179. function done () {
  180. self.emit('flush');
  181. // fake drain
  182. // defer to next tick to allow Socket to clear writeBuffer
  183. setTimeout(function () {
  184. self.writable = true;
  185. self.emit('drain');
  186. }, 0);
  187. }
  188. };
  189. /**
  190. * Called upon close
  191. *
  192. * @api private
  193. */
  194. WS.prototype.onClose = function () {
  195. Transport.prototype.onClose.call(this);
  196. };
  197. /**
  198. * Closes socket.
  199. *
  200. * @api private
  201. */
  202. WS.prototype.doClose = function () {
  203. if (typeof this.ws !== 'undefined') {
  204. this.ws.close();
  205. }
  206. };
  207. /**
  208. * Generates uri for connection.
  209. *
  210. * @api private
  211. */
  212. WS.prototype.uri = function () {
  213. var query = this.query || {};
  214. var schema = this.secure ? 'wss' : 'ws';
  215. var port = '';
  216. // avoid port if default for schema
  217. if (this.port && (('wss' === schema && Number(this.port) !== 443) ||
  218. ('ws' === schema && Number(this.port) !== 80))) {
  219. port = ':' + this.port;
  220. }
  221. // append timestamp to URI
  222. if (this.timestampRequests) {
  223. query[this.timestampParam] = yeast();
  224. }
  225. // communicate binary support capabilities
  226. if (!this.supportsBinary) {
  227. query.b64 = 1;
  228. }
  229. query = parseqs.encode(query);
  230. // prepend ? to query
  231. if (query.length) {
  232. query = '?' + query;
  233. }
  234. var ipv6 = this.hostname.indexOf(':') !== -1;
  235. return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;
  236. };
  237. /**
  238. * Feature detection for WebSocket.
  239. *
  240. * @return {Boolean} whether this transport is available.
  241. * @api public
  242. */
  243. WS.prototype.check = function () {
  244. return !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name);
  245. };