websocket.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.WS = void 0;
  7. const transport_js_1 = require("../transport.js");
  8. const parseqs_1 = __importDefault(require("parseqs"));
  9. const yeast_1 = __importDefault(require("yeast"));
  10. const util_js_1 = require("../util.js");
  11. const websocket_constructor_js_1 = require("./websocket-constructor.js");
  12. const debug_1 = __importDefault(require("debug")); // debug()
  13. const engine_io_parser_1 = require("engine.io-parser");
  14. const debug = (0, debug_1.default)("engine.io-client:websocket"); // debug()
  15. // detect ReactNative environment
  16. const isReactNative = typeof navigator !== "undefined" &&
  17. typeof navigator.product === "string" &&
  18. navigator.product.toLowerCase() === "reactnative";
  19. class WS extends transport_js_1.Transport {
  20. /**
  21. * WebSocket transport constructor.
  22. *
  23. * @api {Object} connection options
  24. * @api public
  25. */
  26. constructor(opts) {
  27. super(opts);
  28. this.supportsBinary = !opts.forceBase64;
  29. }
  30. /**
  31. * Transport name.
  32. *
  33. * @api public
  34. */
  35. get name() {
  36. return "websocket";
  37. }
  38. /**
  39. * Opens socket.
  40. *
  41. * @api private
  42. */
  43. doOpen() {
  44. if (!this.check()) {
  45. // let probe timeout
  46. return;
  47. }
  48. const uri = this.uri();
  49. const protocols = this.opts.protocols;
  50. // React Native only supports the 'headers' option, and will print a warning if anything else is passed
  51. const opts = isReactNative
  52. ? {}
  53. : (0, util_js_1.pick)(this.opts, "agent", "perMessageDeflate", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "localAddress", "protocolVersion", "origin", "maxPayload", "family", "checkServerIdentity");
  54. if (this.opts.extraHeaders) {
  55. opts.headers = this.opts.extraHeaders;
  56. }
  57. try {
  58. this.ws =
  59. websocket_constructor_js_1.usingBrowserWebSocket && !isReactNative
  60. ? protocols
  61. ? new websocket_constructor_js_1.WebSocket(uri, protocols)
  62. : new websocket_constructor_js_1.WebSocket(uri)
  63. : new websocket_constructor_js_1.WebSocket(uri, protocols, opts);
  64. }
  65. catch (err) {
  66. return this.emit("error", err);
  67. }
  68. this.ws.binaryType = this.socket.binaryType || websocket_constructor_js_1.defaultBinaryType;
  69. this.addEventListeners();
  70. }
  71. /**
  72. * Adds event listeners to the socket
  73. *
  74. * @api private
  75. */
  76. addEventListeners() {
  77. this.ws.onopen = () => {
  78. if (this.opts.autoUnref) {
  79. this.ws._socket.unref();
  80. }
  81. this.onOpen();
  82. };
  83. this.ws.onclose = this.onClose.bind(this);
  84. this.ws.onmessage = ev => this.onData(ev.data);
  85. this.ws.onerror = e => this.onError("websocket error", e);
  86. }
  87. /**
  88. * Writes data to socket.
  89. *
  90. * @param {Array} array of packets.
  91. * @api private
  92. */
  93. write(packets) {
  94. this.writable = false;
  95. // encodePacket efficient as it uses WS framing
  96. // no need for encodePayload
  97. for (let i = 0; i < packets.length; i++) {
  98. const packet = packets[i];
  99. const lastPacket = i === packets.length - 1;
  100. (0, engine_io_parser_1.encodePacket)(packet, this.supportsBinary, data => {
  101. // always create a new object (GH-437)
  102. const opts = {};
  103. if (!websocket_constructor_js_1.usingBrowserWebSocket) {
  104. if (packet.options) {
  105. opts.compress = packet.options.compress;
  106. }
  107. if (this.opts.perMessageDeflate) {
  108. const len = "string" === typeof data ? Buffer.byteLength(data) : data.length;
  109. if (len < this.opts.perMessageDeflate.threshold) {
  110. opts.compress = false;
  111. }
  112. }
  113. }
  114. // Sometimes the websocket has already been closed but the browser didn't
  115. // have a chance of informing us about it yet, in that case send will
  116. // throw an error
  117. try {
  118. if (websocket_constructor_js_1.usingBrowserWebSocket) {
  119. // TypeError is thrown when passing the second argument on Safari
  120. this.ws.send(data);
  121. }
  122. else {
  123. this.ws.send(data, opts);
  124. }
  125. }
  126. catch (e) {
  127. debug("websocket closed before onclose event");
  128. }
  129. if (lastPacket) {
  130. // fake drain
  131. // defer to next tick to allow Socket to clear writeBuffer
  132. (0, websocket_constructor_js_1.nextTick)(() => {
  133. this.writable = true;
  134. this.emit("drain");
  135. }, this.setTimeoutFn);
  136. }
  137. });
  138. }
  139. }
  140. /**
  141. * Closes socket.
  142. *
  143. * @api private
  144. */
  145. doClose() {
  146. if (typeof this.ws !== "undefined") {
  147. this.ws.close();
  148. this.ws = null;
  149. }
  150. }
  151. /**
  152. * Generates uri for connection.
  153. *
  154. * @api private
  155. */
  156. uri() {
  157. let query = this.query || {};
  158. const schema = this.opts.secure ? "wss" : "ws";
  159. let port = "";
  160. // avoid port if default for schema
  161. if (this.opts.port &&
  162. (("wss" === schema && Number(this.opts.port) !== 443) ||
  163. ("ws" === schema && Number(this.opts.port) !== 80))) {
  164. port = ":" + this.opts.port;
  165. }
  166. // append timestamp to URI
  167. if (this.opts.timestampRequests) {
  168. query[this.opts.timestampParam] = (0, yeast_1.default)();
  169. }
  170. // communicate binary support capabilities
  171. if (!this.supportsBinary) {
  172. query.b64 = 1;
  173. }
  174. const encodedQuery = parseqs_1.default.encode(query);
  175. const ipv6 = this.opts.hostname.indexOf(":") !== -1;
  176. return (schema +
  177. "://" +
  178. (ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) +
  179. port +
  180. this.opts.path +
  181. (encodedQuery.length ? "?" + encodedQuery : ""));
  182. }
  183. /**
  184. * Feature detection for WebSocket.
  185. *
  186. * @return {Boolean} whether this transport is available.
  187. * @api public
  188. */
  189. check() {
  190. return (!!websocket_constructor_js_1.WebSocket &&
  191. !("__initialize" in websocket_constructor_js_1.WebSocket && this.name === WS.prototype.name));
  192. }
  193. }
  194. exports.WS = WS;