text: /* global attachEvent */
  
  const XMLHttpRequest = require("../../contrib/xmlhttprequest-ssl/XMLHttpRequest");
  const Polling = require("./polling");
  const Emitter = require("component-emitter");
  const { pick } = require("../util");
  const globalThis = require("../globalThis");
  const { installTimeoutFunctions } = require("../timeouts");
  
  const debug = require("debug")("engine.io-client:polling-xhr");
  
  /**
   * Empty function
   */
  
  function empty() {}
  
  const hasXHR2 = (function() {
    const xhr = new XMLHttpRequest({ xdomain: false });
    return null != xhr.responseType;
  })();
  
  class XHR extends 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;
    }
  }
  
  class Request extends Emitter {
    /**
     * Request constructor
     *
     * @param {Object} options
     * @api public
     */
    constructor(uri, opts) {
      super();
      installTimeoutFunctions(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 = pick(
        this.opts,
        "agent",
        "enablesXDR",
        "pfx",
        "key",
        "passphrase",
        "cert",
        "ca",
        "ciphers",
        "rejectUnauthorized",
        "autoUnref"
      );
      opts.xdomain = !!this.opts.xd;
      opts.xscheme = !!this.opts.xs;
  
      const xhr = (this.xhr = new XMLHttpRequest(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;
        }
  
        if (this.hasXDR()) {
          xhr.onload = () => {
            this.onLoad();
          };
          xhr.onerror = () => {
            this.onError(xhr.responseText);
          };
        } else {
          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;
      }
      // xmlhttprequest
      if (this.hasXDR()) {
        this.xhr.onload = this.xhr.onerror = empty;
      } else {
        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);
      }
    }
  
    /**
     * Check if it has XDomainRequest.
     *
     * @api private
     */
    hasXDR() {
      return typeof XDomainRequest !== "undefined" && !this.xs && this.enablesXDR;
    }
  
    /**
     * Aborts the request.
     *
     * @api public
     */
    abort() {
      this.cleanup();
    }
  }
  
  /**
   * 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.
   */
  
  Request.requestsCount = 0;
  Request.requests = {};
  
  if (typeof document !== "undefined") {
    if (typeof attachEvent === "function") {
      attachEvent("onunload", unloadHandler);
    } else if (typeof addEventListener === "function") {
      const terminationEvent = "onpagehide" in globalThis ? "pagehide" : "unload";
      addEventListener(terminationEvent, unloadHandler, false);
    }
  }
  
  function unloadHandler() {
    for (let i in Request.requests) {
      if (Request.requests.hasOwnProperty(i)) {
        Request.requests[i].abort();
      }
    }
  }
  
  module.exports = XHR;
  module.exports.Request = Request;
  