"use strict"; /* global attachEvent */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Request = exports.XHR = void 0; const xmlhttprequest_js_1 = __importDefault(require("./xmlhttprequest.js")); const debug_1 = __importDefault(require("debug")); // debug() const globalThis_js_1 = __importDefault(require("../globalThis.js")); const util_js_1 = require("../util.js"); const component_emitter_1 = require("@socket.io/component-emitter"); const polling_js_1 = require("./polling.js"); const debug = (0, debug_1.default)("engine.io-client:polling-xhr"); // debug() /** * Empty function */ function empty() { } const hasXHR2 = (function () { const xhr = new xmlhttprequest_js_1.default({ xdomain: false }); return null != xhr.responseType; })(); class XHR extends polling_js_1.Polling { /** * XHR Polling constructor. * * @param {Object} opts * @api public */ constructor(opts) { super(opts); if (typeof location !== "undefined") { const isSSL = "https:" === location.protocol; let port = location.port; // some user agents have empty `location.port` if (!port) { port = isSSL ? "443" : "80"; } this.xd = (typeof location !== "undefined" && opts.hostname !== location.hostname) || port !== opts.port; this.xs = opts.secure !== isSSL; } /** * XHR supports binary */ const forceBase64 = opts && opts.forceBase64; this.supportsBinary = hasXHR2 && !forceBase64; } /** * Creates a request. * * @param {String} method * @api private */ request(opts = {}) { Object.assign(opts, { xd: this.xd, xs: this.xs }, this.opts); return new Request(this.uri(), opts); } /** * Sends data. * * @param {String} data to send. * @param {Function} called upon flush. * @api private */ doWrite(data, fn) { const req = this.request({ method: "POST", data: data }); req.on("success", fn); req.on("error", err => { this.onError("xhr post error", err); }); } /** * Starts a poll cycle. * * @api private */ doPoll() { debug("xhr poll"); const req = this.request(); req.on("data", this.onData.bind(this)); req.on("error", err => { this.onError("xhr poll error", err); }); this.pollXhr = req; } } exports.XHR = XHR; class Request extends component_emitter_1.Emitter { /** * Request constructor * * @param {Object} options * @api public */ constructor(uri, opts) { super(); (0, util_js_1.installTimerFunctions)(this, opts); this.opts = opts; this.method = opts.method || "GET"; this.uri = uri; this.async = false !== opts.async; this.data = undefined !== opts.data ? opts.data : null; this.create(); } /** * Creates the XHR object and sends the request. * * @api private */ create() { const opts = (0, util_js_1.pick)(this.opts, "agent", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "autoUnref"); opts.xdomain = !!this.opts.xd; opts.xscheme = !!this.opts.xs; const xhr = (this.xhr = new xmlhttprequest_js_1.default(opts)); try { debug("xhr open %s: %s", this.method, this.uri); xhr.open(this.method, this.uri, this.async); try { if (this.opts.extraHeaders) { xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true); for (let i in this.opts.extraHeaders) { if (this.opts.extraHeaders.hasOwnProperty(i)) { xhr.setRequestHeader(i, this.opts.extraHeaders[i]); } } } } catch (e) { } if ("POST" === this.method) { try { xhr.setRequestHeader("Content-type", "text/plain;charset=UTF-8"); } catch (e) { } } try { xhr.setRequestHeader("Accept", "*/*"); } catch (e) { } // ie6 check if ("withCredentials" in xhr) { xhr.withCredentials = this.opts.withCredentials; } if (this.opts.requestTimeout) { xhr.timeout = this.opts.requestTimeout; } xhr.onreadystatechange = () => { if (4 !== xhr.readyState) return; if (200 === xhr.status || 1223 === xhr.status) { this.onLoad(); } else { // make sure the `error` event handler that's user-set // does not throw in the same tick and gets caught here this.setTimeoutFn(() => { this.onError(typeof xhr.status === "number" ? xhr.status : 0); }, 0); } }; debug("xhr data %s", this.data); xhr.send(this.data); } catch (e) { // Need to defer since .create() is called directly from the constructor // and thus the 'error' event can only be only bound *after* this exception // occurs. Therefore, also, we cannot throw here at all. this.setTimeoutFn(() => { this.onError(e); }, 0); return; } if (typeof document !== "undefined") { this.index = Request.requestsCount++; Request.requests[this.index] = this; } } /** * Called upon successful response. * * @api private */ onSuccess() { this.emit("success"); this.cleanup(); } /** * Called if we have data. * * @api private */ onData(data) { this.emit("data", data); this.onSuccess(); } /** * Called upon error. * * @api private */ onError(err) { this.emit("error", err); this.cleanup(true); } /** * Cleans up house. * * @api private */ cleanup(fromError) { if ("undefined" === typeof this.xhr || null === this.xhr) { return; } this.xhr.onreadystatechange = empty; if (fromError) { try { this.xhr.abort(); } catch (e) { } } if (typeof document !== "undefined") { delete Request.requests[this.index]; } this.xhr = null; } /** * Called upon load. * * @api private */ onLoad() { const data = this.xhr.responseText; if (data !== null) { this.onData(data); } } /** * Aborts the request. * * @api public */ abort() { this.cleanup(); } } exports.Request = Request; Request.requestsCount = 0; Request.requests = {}; /** * Aborts pending requests when unloading the window. This is needed to prevent * memory leaks (e.g. when using IE) and to ensure that no spurious error is * emitted. */ if (typeof document !== "undefined") { // @ts-ignore if (typeof attachEvent === "function") { // @ts-ignore attachEvent("onunload", unloadHandler); } else if (typeof addEventListener === "function") { const terminationEvent = "onpagehide" in globalThis_js_1.default ? "pagehide" : "unload"; addEventListener(terminationEvent, unloadHandler, false); } } function unloadHandler() { for (let i in Request.requests) { if (Request.requests.hasOwnProperty(i)) { Request.requests[i].abort(); } } }