text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  const tslib_1 = require("tslib");
  const uri = require("url");
  const util = require("util");
  const deps_1 = require("./deps");
  const pjson = require('../package.json');
  const debug = require('debug')('http');
  const debugHeaders = require('debug')('http:headers');
  const debugBody = require('debug')('http:body');
  function concat(stream) {
      return new Promise(resolve => {
          let strings = [];
          stream.on('data', data => strings.push(data));
          stream.on('end', () => resolve(strings.join('')));
      });
  }
  function caseInsensitiveObject() {
      let lowercaseKey = (k) => (typeof k === 'string' ? k.toLowerCase() : k);
      return new Proxy({}, {
          get: (t, k) => {
              k = lowercaseKey(k);
              return t[k];
          },
          set: (t, k, v) => {
              k = lowercaseKey(k);
              t[k] = v;
              return true;
          },
          deleteProperty: (t, k) => {
              k = lowercaseKey(k);
              if (k in t)
                  return false;
              return delete t[k];
          },
          has: (t, k) => {
              k = lowercaseKey(k);
              return k in t;
          },
      });
  }
  function lowercaseHeaders(headers) {
      let newHeaders = caseInsensitiveObject();
      for (let k of Object.keys(headers)) {
          newHeaders[k] = headers[k];
      }
      return newHeaders;
  }
  /**
   * Utility for simple HTTP calls
   * @class
   */
  class HTTP {
      /**
       * make an http GET request
       * @param {string} url - url or path to call
       * @param {HTTPRequestOptions} options
       * @returns {Promise}
       * @example
       * ```js
       * const http = require('http-call')
       * await http.get('https://google.com')
       * ```
       */
      static get(url, options = {}) {
          return this.request(url, Object.assign({}, options, { method: 'GET' }));
      }
      /**
       * make an http POST request
       * @param {string} url - url or path to call
       * @param {HTTPRequestOptions} options
       * @returns {Promise}
       * @example
       * ```js
       * const http = require('http-call')
       * await http.post('https://google.com')
       * ```
       */
      static post(url, options = {}) {
          return this.request(url, Object.assign({}, options, { method: 'POST' }));
      }
      /**
       * make an http PUT request
       * @param {string} url - url or path to call
       * @param {HTTPRequestOptions} options
       * @returns {Promise}
       * @example
       * ```js
       * const http = require('http-call')
       * await http.put('https://google.com')
       * ```
       */
      static put(url, options = {}) {
          return this.request(url, Object.assign({}, options, { method: 'PUT' }));
      }
      /**
       * make an http PATCH request
       * @param {string} url - url or path to call
       * @param {HTTPRequestOptions} options
       * @returns {Promise}
       * @example
       * ```js
       * const http = require('http-call')
       * await http.patch('https://google.com')
       * ```
       */
      static patch(url, options = {}) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              return this.request(url, Object.assign({}, options, { method: 'PATCH' }));
          });
      }
      /**
       * make an http DELETE request
       * @param {string} url - url or path to call
       * @param {HTTPRequestOptions} options
       * @returns {Promise}
       * @example
       * ```js
       * const http = require('http-call')
       * await http.delete('https://google.com')
       * ```
       */
      static delete(url, options = {}) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              return this.request(url, Object.assign({}, options, { method: 'DELETE' }));
          });
      }
      /**
       * make a streaming request
       * @param {string} url - url or path to call
       * @param {HTTPRequestOptions} options
       * @returns {Promise}
       * @example
       * ```js
       * const http = require('http-call')
       * let {response} = await http.get('https://google.com')
       * response.on('data', console.log)
       * ```
       */
      static stream(url, options = {}) {
          return this.request(url, Object.assign({}, options, { raw: true }));
      }
      static request(url, options = {}) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              let http = new this(url, options);
              yield http._request();
              return http;
          });
      }
      static defaults(options = {}) {
          return class CustomHTTP extends HTTP {
              static get defaultOptions() {
                  return Object.assign({}, super.defaultOptions, options);
              }
          };
      }
      static get defaultOptions() {
          return {
              method: 'GET',
              host: 'localhost',
              protocol: 'https:',
              path: '/',
              raw: false,
              partial: false,
              headers: {
                  'user-agent': `${pjson.name}/${pjson.version} node-${process.version}`,
              },
          };
      }
      get method() {
          return this.options.method || 'GET';
      }
      get statusCode() {
          if (!this.response)
              return 0;
          return this.response.statusCode || 0;
      }
      get secure() {
          return this.options.protocol === 'https:';
      }
      get url() {
          return `${this.options.protocol}//${this.options.host}${this.options.path}`;
      }
      set url(input) {
          let u = uri.parse(input);
          this.options.protocol = u.protocol || this.options.protocol;
          this.options.host = u.hostname || this.ctor.defaultOptions.host || 'localhost';
          this.options.path = u.path || '/';
          this.options.agent = this.options.agent || deps_1.deps.proxy.agent(this.secure);
          this.options.port = u.port || this.ctor.defaultOptions.port || (this.secure ? 443 : 80);
      }
      get headers() {
          if (!this.response)
              return {};
          return this.response.headers;
      }
      get partial() {
          if (this.method !== 'GET' || this.options.partial)
              return true;
          return !(this.headers['next-range'] && this.body instanceof Array);
      }
      get ctor() {
          return this.constructor;
      }
      constructor(url, options = {}) {
          this.options = Object.assign({}, this.ctor.defaultOptions, options, { headers: lowercaseHeaders(Object.assign({}, this.ctor.defaultOptions.headers, options.headers)) });
          if (!url)
              throw new Error('no url provided');
          this.url = url;
          if (this.options.body)
              this._parseBody(this.options.body);
      }
      _request() {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              this._debugRequest();
              try {
                  this.response = yield this._performRequest();
              }
              catch (err) {
                  debug(err);
                  return this._maybeRetry(err);
              }
              if (this._shouldParseResponseBody)
                  yield this._parse();
              this._debugResponse();
              if (this._responseRedirect)
                  return this._redirect();
              if (!this._responseOK) {
                  throw new HTTPError(this);
              }
              if (!this.partial)
                  yield this._getNextRange();
          });
      }
      _redirect() {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              if (!this._redirectRetries)
                  this._redirectRetries = 0;
              this._redirectRetries++;
              if (this._redirectRetries > 10)
                  throw new Error(`Redirect loop at ${this.url}`);
              if (!this.headers.location)
                  throw new Error(`Redirect from ${this.url} has no location header`);
              const location = this.headers.location;
              if (Array.isArray(location)) {
                  this.url = location[0];
              }
              else {
                  this.url = location;
              }
              yield this._request();
          });
      }
      _maybeRetry(err) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              if (!this._errorRetries)
                  this._errorRetries = 0;
              this._errorRetries++;
              const allowed = (err) => {
                  if (this._errorRetries > 5)
                      return false;
                  if (!err.code)
                      return false;
                  if (err.code === 'ENOTFOUND')
                      return true;
                  return require('is-retry-allowed')(err);
              };
              if (allowed(err)) {
                  let noise = Math.random() * 100;
                  // tslint:disable-next-line
                  yield this._wait((1 << this._errorRetries) * 100 + noise);
                  yield this._request();
                  return;
              }
              throw err;
          });
      }
      _debugRequest() {
          if (this.options.agent)
              debug('proxy: %o', this.options.agent);
          debug('--> %s %s', this.options.method, this.url);
          debugHeaders(this._redactedHeaders(this.options.headers));
          if (this.options.body)
              debugBody(this.options.body);
      }
      _debugResponse() {
          debug('<-- %s %s %s', this.method, this.url, this.statusCode);
          debugHeaders(this._redactedHeaders(this.headers));
          if (this.body)
              debugBody(this.body);
      }
      _performRequest() {
          return new Promise((resolve, reject) => {
              if (this.secure) {
                  this.request = deps_1.deps.https.request(this.options, resolve);
              }
              else {
                  this.request = deps_1.deps.http.request(this.options, resolve);
              }
              this.request.on('error', reject);
              if (this.options.body && deps_1.deps.isStream.readable(this.options.body)) {
                  this.options.body.pipe(this.request);
              }
              else {
                  this.request.end(this.options.body);
              }
          });
      }
      _parse() {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              this.body = yield concat(this.response);
              let json = this.response.headers['content-type'] && deps_1.deps.contentType.parse(this.response).type.startsWith('application/json');
              if (json)
                  this.body = JSON.parse(this.body);
          });
      }
      _parseBody(body) {
          if (deps_1.deps.isStream.readable(body)) {
              this.options.body = body;
              return;
          }
          if (!this.options.headers['content-type']) {
              this.options.headers['content-type'] = 'application/json';
          }
          if (this.options.headers['content-type'] === 'application/json') {
              this.options.body = JSON.stringify(body);
          }
          else {
              this.options.body = body;
          }
          this.options.headers['content-length'] = Buffer.byteLength(this.options.body).toString();
      }
      _getNextRange() {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              const next = this.headers['next-range'];
              this.options.headers.range = Array.isArray(next) ? next[0] : next;
              let prev = this.body;
              yield this._request();
              this.body = prev.concat(this.body);
          });
      }
      _redactedHeaders(headers) {
          headers = Object.assign({}, headers);
          if (headers.authorization)
              headers.authorization = '[REDACTED]';
          return headers;
      }
      get _responseOK() {
          if (!this.response)
              return false;
          return this.statusCode >= 200 && this.statusCode < 300;
      }
      get _responseRedirect() {
          if (!this.response)
              return false;
          return [301, 302, 303, 307, 308].includes(this.statusCode);
      }
      get _shouldParseResponseBody() {
          return !this._responseOK || (!this.options.raw && this._responseOK);
      }
      _wait(ms) {
          return new Promise(resolve => setTimeout(resolve, ms));
      }
  }
  exports.HTTP = HTTP;
  class HTTPError extends Error {
      constructor(http) {
          super();
          this.__httpcall = pjson.version;
          if (typeof http.body === 'string' || typeof http.body.message === 'string')
              this.message = http.body.message || http.body;
          else
              this.message = util.inspect(http.body);
          this.message = `HTTP Error ${http.statusCode} for ${http.method} ${http.url}\n${this.message}`;
          this.statusCode = http.statusCode;
          this.http = http;
          this.body = http.body;
      }
  }
  exports.HTTPError = HTTPError;
  