text: /**
   * Module of mixed-in functions shared between node and client code
   */
  const isObject = require('./is-object');
  
  /**
   * Expose `RequestBase`.
   */
  
  module.exports = RequestBase;
  
  /**
   * Initialize a new `RequestBase`.
   *
   * @api public
   */
  
  function RequestBase(obj) {
    if (obj) return mixin(obj);
  }
  
  /**
   * Mixin the prototype properties.
   *
   * @param {Object} obj
   * @return {Object}
   * @api private
   */
  
  function mixin(obj) {
    for (const key in RequestBase.prototype) {
      if (Object.prototype.hasOwnProperty.call(RequestBase.prototype, key))
        obj[key] = RequestBase.prototype[key];
    }
  
    return obj;
  }
  
  /**
   * Clear previous timeout.
   *
   * @return {Request} for chaining
   * @api public
   */
  
  RequestBase.prototype.clearTimeout = function() {
    clearTimeout(this._timer);
    clearTimeout(this._responseTimeoutTimer);
    clearTimeout(this._uploadTimeoutTimer);
    delete this._timer;
    delete this._responseTimeoutTimer;
    delete this._uploadTimeoutTimer;
    return this;
  };
  
  /**
   * Override default response body parser
   *
   * This function will be called to convert incoming data into request.body
   *
   * @param {Function}
   * @api public
   */
  
  RequestBase.prototype.parse = function(fn) {
    this._parser = fn;
    return this;
  };
  
  /**
   * Set format of binary response body.
   * In browser valid formats are 'blob' and 'arraybuffer',
   * which return Blob and ArrayBuffer, respectively.
   *
   * In Node all values result in Buffer.
   *
   * Examples:
   *
   *      req.get('/')
   *        .responseType('blob')
   *        .end(callback);
   *
   * @param {String} val
   * @return {Request} for chaining
   * @api public
   */
  
  RequestBase.prototype.responseType = function(val) {
    this._responseType = val;
    return this;
  };
  
  /**
   * Override default request body serializer
   *
   * This function will be called to convert data set via .send or .attach into payload to send
   *
   * @param {Function}
   * @api public
   */
  
  RequestBase.prototype.serialize = function(fn) {
    this._serializer = fn;
    return this;
  };
  
  /**
   * Set timeouts.
   *
   * - response timeout is time between sending request and receiving the first byte of the response. Includes DNS and connection time.
   * - deadline is the time from start of the request to receiving response body in full. If the deadline is too short large files may not load at all on slow connections.
   * - upload is the time  since last bit of data was sent or received. This timeout works only if deadline timeout is off
   *
   * Value of 0 or false means no timeout.
   *
   * @param {Number|Object} ms or {response, deadline}
   * @return {Request} for chaining
   * @api public
   */
  
  RequestBase.prototype.timeout = function(options) {
    if (!options || typeof options !== 'object') {
      this._timeout = options;
      this._responseTimeout = 0;
      this._uploadTimeout = 0;
      return this;
    }
  
    for (const option in options) {
      if (Object.prototype.hasOwnProperty.call(options, option)) {
        switch (option) {
          case 'deadline':
            this._timeout = options.deadline;
            break;
          case 'response':
            this._responseTimeout = options.response;
            break;
          case 'upload':
            this._uploadTimeout = options.upload;
            break;
          default:
            console.warn('Unknown timeout option', option);
        }
      }
    }
  
    return this;
  };
  
  /**
   * Set number of retry attempts on error.
   *
   * Failed requests will be retried 'count' times if timeout or err.code >= 500.
   *
   * @param {Number} count
   * @param {Function} [fn]
   * @return {Request} for chaining
   * @api public
   */
  
  RequestBase.prototype.retry = function(count, fn) {
    // Default to 1 if no count passed or true
    if (arguments.length === 0 || count === true) count = 1;
    if (count <= 0) count = 0;
    this._maxRetries = count;
    this._retries = 0;
    this._retryCallback = fn;
    return this;
  };
  
  const ERROR_CODES = ['ECONNRESET', 'ETIMEDOUT', 'EADDRINFO', 'ESOCKETTIMEDOUT'];
  
  /**
   * Determine if a request should be retried.
   * (Borrowed from segmentio/superagent-retry)
   *
   * @param {Error} err an error
   * @param {Response} [res] response
   * @returns {Boolean} if segment should be retried
   */
  RequestBase.prototype._shouldRetry = function(err, res) {
    if (!this._maxRetries || this._retries++ >= this._maxRetries) {
      return false;
    }
  
    if (this._retryCallback) {
      try {
        const override = this._retryCallback(err, res);
        if (override === true) return true;
        if (override === false) return false;
        // undefined falls back to defaults
      } catch (err_) {
        console.error(err_);
      }
    }
  
    if (res && res.status && res.status >= 500 && res.status !== 501) return true;
    if (err) {
      if (err.code && ERROR_CODES.includes(err.code)) return true;
      // Superagent timeout
      if (err.timeout && err.code === 'ECONNABORTED') return true;
      if (err.crossDomain) return true;
    }
  
    return false;
  };
  
  /**
   * Retry request
   *
   * @return {Request} for chaining
   * @api private
   */
  
  RequestBase.prototype._retry = function() {
    this.clearTimeout();
  
    // node
    if (this.req) {
      this.req = null;
      this.req = this.request();
    }
  
    this._aborted = false;
    this.timedout = false;
    this.timedoutError = null;
  
    return this._end();
  };
  
  /**
   * Promise support
   *
   * @param {Function} resolve
   * @param {Function} [reject]
   * @return {Request}
   */
  
  RequestBase.prototype.then = function(resolve, reject) {
    if (!this._fullfilledPromise) {
      const self = this;
      if (this._endCalled) {
        console.warn(
          'Warning: superagent request was sent twice, because both .end() and .then() were called. Never call .end() if you use promises'
        );
      }
  
      this._fullfilledPromise = new Promise((resolve, reject) => {
        self.on('abort', () => {
          if (this.timedout && this.timedoutError) {
            reject(this.timedoutError);
            return;
          }
  
          const err = new Error('Aborted');
          err.code = 'ABORTED';
          err.status = this.status;
          err.method = this.method;
          err.url = this.url;
          reject(err);
        });
        self.end((err, res) => {
          if (err) reject(err);
          else resolve(res);
        });
      });
    }
  
    return this._fullfilledPromise.then(resolve, reject);
  };
  
  RequestBase.prototype.catch = function(cb) {
    return this.then(undefined, cb);
  };
  
  /**
   * Allow for extension
   */
  
  RequestBase.prototype.use = function(fn) {
    fn(this);
    return this;
  };
  
  RequestBase.prototype.ok = function(cb) {
    if (typeof cb !== 'function') throw new Error('Callback required');
    this._okCallback = cb;
    return this;
  };
  
  RequestBase.prototype._isResponseOK = function(res) {
    if (!res) {
      return false;
    }
  
    if (this._okCallback) {
      return this._okCallback(res);
    }
  
    return res.status >= 200 && res.status < 300;
  };
  
  /**
   * Get request header `field`.
   * Case-insensitive.
   *
   * @param {String} field
   * @return {String}
   * @api public
   */
  
  RequestBase.prototype.get = function(field) {
    return this._header[field.toLowerCase()];
  };
  
  /**
   * Get case-insensitive header `field` value.
   * This is a deprecated internal API. Use `.get(field)` instead.
   *
   * (getHeader is no longer used internally by the superagent code base)
   *
   * @param {String} field
   * @return {String}
   * @api private
   * @deprecated
   */
  
  RequestBase.prototype.getHeader = RequestBase.prototype.get;
  
  /**
   * Set header `field` to `val`, or multiple fields with one object.
   * Case-insensitive.
   *
   * Examples:
   *
   *      req.get('/')
   *        .set('Accept', 'application/json')
   *        .set('X-API-Key', 'foobar')
   *        .end(callback);
   *
   *      req.get('/')
   *        .set({ Accept: 'application/json', 'X-API-Key': 'foobar' })
   *        .end(callback);
   *
   * @param {String|Object} field
   * @param {String} val
   * @return {Request} for chaining
   * @api public
   */
  
  RequestBase.prototype.set = function(field, val) {
    if (isObject(field)) {
      for (const key in field) {
        if (Object.prototype.hasOwnProperty.call(field, key))
          this.set(key, field[key]);
      }
  
      return this;
    }
  
    this._header[field.toLowerCase()] = val;
    this.header[field] = val;
    return this;
  };
  
  /**
   * Remove header `field`.
   * Case-insensitive.
   *
   * Example:
   *
   *      req.get('/')
   *        .unset('User-Agent')
   *        .end(callback);
   *
   * @param {String} field field name
   */
  RequestBase.prototype.unset = function(field) {
    delete this._header[field.toLowerCase()];
    delete this.header[field];
    return this;
  };
  
  /**
   * Write the field `name` and `val`, or multiple fields with one object
   * for "multipart/form-data" request bodies.
   *
   * ``` js
   * request.post('/upload')
   *   .field('foo', 'bar')
   *   .end(callback);
   *
   * request.post('/upload')
   *   .field({ foo: 'bar', baz: 'qux' })
   *   .end(callback);
   * ```
   *
   * @param {String|Object} name name of field
   * @param {String|Blob|File|Buffer|fs.ReadStream} val value of field
   * @return {Request} for chaining
   * @api public
   */
  RequestBase.prototype.field = function(name, val) {
    // name should be either a string or an object.
    if (name === null || undefined === name) {
      throw new Error('.field(name, val) name can not be empty');
    }
  
    if (this._data) {
      throw new Error(
        ".field() can't be used if .send() is used. Please use only .send() or only .field() & .attach()"
      );
    }
  
    if (isObject(name)) {
      for (const key in name) {
        if (Object.prototype.hasOwnProperty.call(name, key))
          this.field(key, name[key]);
      }
  
      return this;
    }
  
    if (Array.isArray(val)) {
      for (const i in val) {
        if (Object.prototype.hasOwnProperty.call(val, i))
          this.field(name, val[i]);
      }
  
      return this;
    }
  
    // val should be defined now
    if (val === null || undefined === val) {
      throw new Error('.field(name, val) val can not be empty');
    }
  
    if (typeof val === 'boolean') {
      val = String(val);
    }
  
    this._getFormData().append(name, val);
    return this;
  };
  
  /**
   * Abort the request, and clear potential timeout.
   *
   * @return {Request} request
   * @api public
   */
  RequestBase.prototype.abort = function() {
    if (this._aborted) {
      return this;
    }
  
    this._aborted = true;
    if (this.xhr) this.xhr.abort(); // browser
    if (this.req) this.req.abort(); // node
    this.clearTimeout();
    this.emit('abort');
    return this;
  };
  
  RequestBase.prototype._auth = function(user, pass, options, base64Encoder) {
    switch (options.type) {
      case 'basic':
        this.set('Authorization', `Basic ${base64Encoder(`${user}:${pass}`)}`);
        break;
  
      case 'auto':
        this.username = user;
        this.password = pass;
        break;
  
      case 'bearer': // usage would be .auth(accessToken, { type: 'bearer' })
        this.set('Authorization', `Bearer ${user}`);
        break;
      default:
        break;
    }
  
    return this;
  };
  
  /**
   * Enable transmission of cookies with x-domain requests.
   *
   * Note that for this to work the origin must not be
   * using "Access-Control-Allow-Origin" with a wildcard,
   * and also must set "Access-Control-Allow-Credentials"
   * to "true".
   *
   * @api public
   */
  
  RequestBase.prototype.withCredentials = function(on) {
    // This is browser-only functionality. Node side is no-op.
    if (on === undefined) on = true;
    this._withCredentials = on;
    return this;
  };
  
  /**
   * Set the max redirects to `n`. Does nothing in browser XHR implementation.
   *
   * @param {Number} n
   * @return {Request} for chaining
   * @api public
   */
  
  RequestBase.prototype.redirects = function(n) {
    this._maxRedirects = n;
    return this;
  };
  
  /**
   * Maximum size of buffered response body, in bytes. Counts uncompressed size.
   * Default 200MB.
   *
   * @param {Number} n number of bytes
   * @return {Request} for chaining
   */
  RequestBase.prototype.maxResponseSize = function(n) {
    if (typeof n !== 'number') {
      throw new TypeError('Invalid argument');
    }
  
    this._maxResponseSize = n;
    return this;
  };
  
  /**
   * Convert to a plain javascript object (not JSON string) of scalar properties.
   * Note as this method is designed to return a useful non-this value,
   * it cannot be chained.
   *
   * @return {Object} describing method, url, and data of this request
   * @api public
   */
  
  RequestBase.prototype.toJSON = function() {
    return {
      method: this.method,
      url: this.url,
      data: this._data,
      headers: this._header
    };
  };
  
  /**
   * Send `data` as the request body, defaulting the `.type()` to "json" when
   * an object is given.
   *
   * Examples:
   *
   *       // manual json
   *       request.post('/user')
   *         .type('json')
   *         .send('{"name":"tj"}')
   *         .end(callback)
   *
   *       // auto json
   *       request.post('/user')
   *         .send({ name: 'tj' })
   *         .end(callback)
   *
   *       // manual x-www-form-urlencoded
   *       request.post('/user')
   *         .type('form')
   *         .send('name=tj')
   *         .end(callback)
   *
   *       // auto x-www-form-urlencoded
   *       request.post('/user')
   *         .type('form')
   *         .send({ name: 'tj' })
   *         .end(callback)
   *
   *       // defaults to x-www-form-urlencoded
   *      request.post('/user')
   *        .send('name=tobi')
   *        .send('species=ferret')
   *        .end(callback)
   *
   * @param {String|Object} data
   * @return {Request} for chaining
   * @api public
   */
  
  // eslint-disable-next-line complexity
  RequestBase.prototype.send = function(data) {
    const isObj = isObject(data);
    let type = this._header['content-type'];
  
    if (this._formData) {
      throw new Error(
        ".send() can't be used if .attach() or .field() is used. Please use only .send() or only .field() & .attach()"
      );
    }
  
    if (isObj && !this._data) {
      if (Array.isArray(data)) {
        this._data = [];
      } else if (!this._isHost(data)) {
        this._data = {};
      }
    } else if (data && this._data && this._isHost(this._data)) {
      throw new Error("Can't merge these send calls");
    }
  
    // merge
    if (isObj && isObject(this._data)) {
      for (const key in data) {
        if (Object.prototype.hasOwnProperty.call(data, key))
          this._data[key] = data[key];
      }
    } else if (typeof data === 'string') {
      // default to x-www-form-urlencoded
      if (!type) this.type('form');
      type = this._header['content-type'];
      if (type === 'application/x-www-form-urlencoded') {
        this._data = this._data ? `${this._data}&${data}` : data;
      } else {
        this._data = (this._data || '') + data;
      }
    } else {
      this._data = data;
    }
  
    if (!isObj || this._isHost(data)) {
      return this;
    }
  
    // default to json
    if (!type) this.type('json');
    return this;
  };
  
  /**
   * Sort `querystring` by the sort function
   *
   *
   * Examples:
   *
   *       // default order
   *       request.get('/user')
   *         .query('name=Nick')
   *         .query('search=Manny')
   *         .sortQuery()
   *         .end(callback)
   *
   *       // customized sort function
   *       request.get('/user')
   *         .query('name=Nick')
   *         .query('search=Manny')
   *         .sortQuery(function(a, b){
   *           return a.length - b.length;
   *         })
   *         .end(callback)
   *
   *
   * @param {Function} sort
   * @return {Request} for chaining
   * @api public
   */
  
  RequestBase.prototype.sortQuery = function(sort) {
    // _sort default to true but otherwise can be a function or boolean
    this._sort = typeof sort === 'undefined' ? true : sort;
    return this;
  };
  
  /**
   * Compose querystring to append to req.url
   *
   * @api private
   */
  RequestBase.prototype._finalizeQueryString = function() {
    const query = this._query.join('&');
    if (query) {
      this.url += (this.url.includes('?') ? '&' : '?') + query;
    }
  
    this._query.length = 0; // Makes the call idempotent
  
    if (this._sort) {
      const index = this.url.indexOf('?');
      if (index >= 0) {
        const queryArr = this.url.slice(index + 1).split('&');
        if (typeof this._sort === 'function') {
          queryArr.sort(this._sort);
        } else {
          queryArr.sort();
        }
  
        this.url = this.url.slice(0, index) + '?' + queryArr.join('&');
      }
    }
  };
  
  // For backwards compat only
  RequestBase.prototype._appendQueryString = () => {
    console.warn('Unsupported');
  };
  
  /**
   * Invoke callback with timeout error.
   *
   * @api private
   */
  
  RequestBase.prototype._timeoutError = function(reason, timeout, errno) {
    if (this._aborted) {
      return;
    }
  
    const err = new Error(`${reason + timeout}ms exceeded`);
    err.timeout = timeout;
    err.code = 'ECONNABORTED';
    err.errno = errno;
    this.timedout = true;
    this.timedoutError = err;
    this.abort();
    this.callback(err);
  };
  
  RequestBase.prototype._setTimeouts = function() {
    const self = this;
  
    // deadline
    if (this._timeout && !this._timer) {
      this._timer = setTimeout(() => {
        self._timeoutError('Timeout of ', self._timeout, 'ETIME');
      }, this._timeout);
    }
  
    // response timeout
    if (this._responseTimeout && !this._responseTimeoutTimer) {
      this._responseTimeoutTimer = setTimeout(() => {
        self._timeoutError(
          'Response timeout of ',
          self._responseTimeout,
          'ETIMEDOUT'
        );
      }, this._responseTimeout);
    }
  };
  