Source: NS1_request.js

"use strict";

let superagent = require('superagent')

let api_key,
    api_url = 'https://api.nsone.net/v1/'

/** 
 * Class representing all HTTP requests to the NS1 API. Uses the superagent
 * lib to be cross compatible w/ Node.js and Browser based environment.
 * @memberof NS1
 */
class NS1Request {

  /**
   * Creates a request and returns a promise object for that request.
   * @param {String} method - The HTTP verb to use in this request e.g. get, post, etc. Must be lower case.
   * @param {String} uri    - The URI to query against the base API URL
   * @param {Object} query  - Any parameters to be sent in the query string for GET requests or in the req body for others
   * @param {Object/FormData} files  - Key / value mapped object containing file paths for uploads, or a FormData object if on the browser.
   * @return {Promise} an ES2015 promise w/ then and catch methods for continuation handling.
   */
  constructor(method, uri, query, files) {
    if (uri[0] === '/') {
      uri = uri.substring(1)
    }

    this.method = method
    this.uri    = uri
    this.is_json_response = true // flag to adjust later on when dealing with binary file responses

    this.request = superagent[method].apply(
                     superagent, 
                     [`${api_url}${uri}`]
                   ).set('X-NSONE-Key', api_key)
                   .set('X-NSONE-Js-Api', require('../package.json').version)

              
    apply_data.call(this, query, files)
    return create_promise.call(this)               
  }

  /**
   * Returns the current API key being used by the class.
   * @return {String} The API key
   */
  static get_api_key() { 
    return api_key
  }
  
  /**
   * Sets the API key used by the class.
   * @param {String} key - The API key supplied by the user
   */
  static set_api_key(key) {
    api_key = key 
  }

  /**
   * Returns the current API url base being used.
   * @return {String} The API url
   */
  static get_api_url() {
    return api_url
  }

  /**
   * Sets the API url used by the class.
   * @param {String} root - The URL to set as the base for API calls
   */
  static set_api_url(root) {
    if (root[root.length - 1] !== '/') {
      root = root + '/'
    }
    api_url = root
  }
}

/**
 * Applies data to the this.request superagent object. Works with query params or
 * file attachments.
 *
 * @param {Object} query  - Any parameters to be sent in the query string for GET requests or in the req body for others
 * @param {Object/FormData} files  - Key / value mapped object containing file paths for uploads, or a FormData object if it's coming from the browser
 * @private
 */
function apply_data(query, files) {
  if (query !== undefined) {
    if (this.method === 'get') {
      this.request = this.request.query(query)
    } else {
      this.request = this.request.send(query)
    }
  }

  if (files !== undefined) {
    if (files instanceof FormData) {
      this.request = this.request.send(files)
    } else {
      Object.keys(files).forEach((key) => {
        this.request = this.request.attach(key, files[key])
      })
    }
  }
}

/**
 * Creates an ES2015 Promise which parses the message internally as JSON and
 * returns it as the resolve() method's sole argument.
 *
 * @return {Promise}
 * @private
 */
function create_promise() {
  return new Promise((resolve, reject) => {
    this.request.end((err, response) => {
      if (err) reject(handle_error.call(this, err, response))

      if (this.is_json_response && this.method != 'del' && response.text !== '') {
        try {
          var parsed = JSON.parse(response.text)
        } catch(parse_err) {
          throw new Error(`NS1 API Response couldn't parse as JSON: ${parse_err}`)
        }
      } else {
        // TODO: Determine if there's a need to filter/reject empty response bodies on 200's
      }

      return resolve(parsed || true)
    })
  })
}

/**
 * Handles error messaging. Some errors will come back as JSON w/ the error details
 * in the message field of the returning object. Other times JSOn won't be able to parse.
 * This should return an appropriate JS Error object w/ the right message.
 *
 * @param {Error} err
 * @param {String} response
 * @return {Error}
 * @private
 */
function handle_error(err, response) {
  if (response && response.text) {
    let final_message
    try {
      final_message = JSON.parse(response.text).message
    } catch(e) {
      final_message = response.text
    }
    return new Error(`NS1 API Request Failed on \n ${this.method.toUpperCase()} ${api_url}${this.uri} \n ${final_message} \n`) 
  } else {
    return new Error(`NS1 API Request Failed on \n ${this.method.toUpperCase()} ${api_url}${this.uri} \n ${err.message} \n`) 
  }
}

module.exports = NS1Request