"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WS = void 0; const transport_js_1 = require("../transport.js"); const parseqs_js_1 = require("../contrib/parseqs.js"); const yeast_js_1 = require("../contrib/yeast.js"); const util_js_1 = require("../util.js"); const websocket_constructor_js_1 = require("./websocket-constructor.js"); const debug_1 = __importDefault(require("debug")); // debug() const engine_io_parser_1 = require("engine.io-parser"); const debug = (0, debug_1.default)("engine.io-client:websocket"); // debug() // detect ReactNative environment const isReactNative = typeof navigator !== "undefined" && typeof navigator.product === "string" && navigator.product.toLowerCase() === "reactnative"; class WS extends transport_js_1.Transport { /** * WebSocket transport constructor. * * @api {Object} connection options * @api public */ constructor(opts) { super(opts); this.supportsBinary = !opts.forceBase64; } /** * Transport name. * * @api public */ get name() { return "websocket"; } /** * Opens socket. * * @api private */ doOpen() { if (!this.check()) { // let probe timeout return; } const uri = this.uri(); const protocols = this.opts.protocols; // React Native only supports the 'headers' option, and will print a warning if anything else is passed const opts = isReactNative ? {} : (0, util_js_1.pick)(this.opts, "agent", "perMessageDeflate", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "localAddress", "protocolVersion", "origin", "maxPayload", "family", "checkServerIdentity"); if (this.opts.extraHeaders) { opts.headers = this.opts.extraHeaders; } try { this.ws = websocket_constructor_js_1.usingBrowserWebSocket && !isReactNative ? protocols ? new websocket_constructor_js_1.WebSocket(uri, protocols) : new websocket_constructor_js_1.WebSocket(uri) : new websocket_constructor_js_1.WebSocket(uri, protocols, opts); } catch (err) { return this.emitReserved("error", err); } this.ws.binaryType = this.socket.binaryType || websocket_constructor_js_1.defaultBinaryType; this.addEventListeners(); } /** * Adds event listeners to the socket * * @api private */ addEventListeners() { this.ws.onopen = () => { if (this.opts.autoUnref) { this.ws._socket.unref(); } this.onOpen(); }; this.ws.onclose = closeEvent => this.onClose({ description: "websocket connection closed", context: closeEvent }); this.ws.onmessage = ev => this.onData(ev.data); this.ws.onerror = e => this.onError("websocket error", e); } /** * Writes data to socket. * * @param {Array} array of packets. * @api private */ write(packets) { this.writable = false; // encodePacket efficient as it uses WS framing // no need for encodePayload for (let i = 0; i < packets.length; i++) { const packet = packets[i]; const lastPacket = i === packets.length - 1; (0, engine_io_parser_1.encodePacket)(packet, this.supportsBinary, data => { // always create a new object (GH-437) const opts = {}; if (!websocket_constructor_js_1.usingBrowserWebSocket) { if (packet.options) { opts.compress = packet.options.compress; } if (this.opts.perMessageDeflate) { const len = // @ts-ignore "string" === typeof data ? Buffer.byteLength(data) : data.length; if (len < this.opts.perMessageDeflate.threshold) { opts.compress = false; } } } // Sometimes the websocket has already been closed but the browser didn't // have a chance of informing us about it yet, in that case send will // throw an error try { if (websocket_constructor_js_1.usingBrowserWebSocket) { // TypeError is thrown when passing the second argument on Safari this.ws.send(data); } else { this.ws.send(data, opts); } } catch (e) { debug("websocket closed before onclose event"); } if (lastPacket) { // fake drain // defer to next tick to allow Socket to clear writeBuffer (0, websocket_constructor_js_1.nextTick)(() => { this.writable = true; this.emitReserved("drain"); }, this.setTimeoutFn); } }); } } /** * Closes socket. * * @api private */ doClose() { if (typeof this.ws !== "undefined") { this.ws.close(); this.ws = null; } } /** * Generates uri for connection. * * @api private */ uri() { let query = this.query || {}; const schema = this.opts.secure ? "wss" : "ws"; let port = ""; // avoid port if default for schema if (this.opts.port && (("wss" === schema && Number(this.opts.port) !== 443) || ("ws" === schema && Number(this.opts.port) !== 80))) { port = ":" + this.opts.port; } // append timestamp to URI if (this.opts.timestampRequests) { query[this.opts.timestampParam] = (0, yeast_js_1.yeast)(); } // communicate binary support capabilities if (!this.supportsBinary) { query.b64 = 1; } const encodedQuery = (0, parseqs_js_1.encode)(query); const ipv6 = this.opts.hostname.indexOf(":") !== -1; return (schema + "://" + (ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) + port + this.opts.path + (encodedQuery.length ? "?" + encodedQuery : "")); } /** * Feature detection for WebSocket. * * @return {Boolean} whether this transport is available. * @api public */ check() { return !!websocket_constructor_js_1.WebSocket; } } exports.WS = WS;