All files / lib/transports http.js

100% Statements 86/86
93.02% Branches 40/43
100% Functions 7/7
100% Lines 86/86
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 1433x 3x 3x 3x 3x   3x 20x 20x 1x   20x 20x 20x 20x           26x 26x 26x 26x 26x 26x 26x       22x 22x 22x 22x 22x 22x 22x   22x 10x     10x 2x 1x   1x       8x 8x 1x 7x 1x 6x 5x 5x 1x 1x 1x         21x 9x 1x       20x   20x   20x 4x 2x 2x 2x 1x   1x 2x   1x 1x 1x 1x 2x 2x 2x   1x   1x 1x       20x 3x     20x 20x 20x 20x   20x             22x 22x   20x 1x     20x   20x   20x 19x   1x 1x 1x             3x      
const fetch = require('isomorphic-fetch')
const errors = require('../errors')
const utils = require('../utils')
const URL = require('url-parse')
const urlTemplate = require('url-template')
 
const parseResponse = (response, decoders, responseCallback) => {
  return response.text().then(text => {
    if (responseCallback) {
      responseCallback(response, text)
    }
    const contentType = response.headers.get('Content-Type')
    const decoder = utils.negotiateDecoder(decoders, contentType)
    const options = {url: response.url}
    return decoder.decode(text, options)
  })
}
 
class HTTPTransport {
  constructor (options = {}) {
    this.schemes = ['http', 'https']
    this.auth = options.auth || null
    this.headers = options.headers || {}
    this.fetch = options.fetch || fetch
    this.FormData = options.FormData || window.FormData
    this.requestCallback = options.requestCallback
    this.responseCallback = options.responseCallback
  }
 
  buildRequest (link, decoders, params = {}) {
    const fields = link.fields
    const method = link.method.toUpperCase()
    let queryParams = {}
    let pathParams = {}
    let formParams = {}
    let fieldNames = []
    let hasBody = false
 
    for (let idx = 0, len = fields.length; idx < len; idx++) {
      const field = fields[idx]
 
      // Ensure any required fields are included
      if (!params.hasOwnProperty(field.name)) {
        if (field.required) {
          throw new errors.ParameterError(`Missing required field: "${field.name}"`)
        } else {
          continue
        }
      }
 
      fieldNames.push(field.name)
      if (field.location === 'query') {
        queryParams[field.name] = params[field.name]
      } else if (field.location === 'path') {
        pathParams[field.name] = params[field.name]
      } else if (field.location === 'form') {
        formParams[field.name] = params[field.name]
        hasBody = true
      } else Eif (field.location === 'body') {
        formParams = params[field.name]
        hasBody = true
      }
    }
 
    // Check for any parameters that did not have a matching field
    for (var property in params) {
      if (params.hasOwnProperty(property) && !fieldNames.includes(property)) {
        throw new errors.ParameterError(`Unknown parameter: "${property}"`)
      }
    }
 
    let requestOptions = {method: method, headers: {}}
 
    Object.assign(requestOptions.headers, this.headers)
 
    if (hasBody) {
      if (link.encoding === 'application/json') {
        requestOptions.body = JSON.stringify(formParams)
        requestOptions.headers['Content-Type'] = 'application/json'
      } else if (link.encoding === 'multipart/form-data') {
        let form = new this.FormData()
 
        for (let paramKey in formParams) {
          form.append(paramKey, formParams[paramKey])
        }
        requestOptions.body = form
      } else Eif (link.encoding === 'application/x-www-form-urlencoded') {
        let formBody = []
        for (let paramKey in formParams) {
          const encodedKey = encodeURIComponent(paramKey)
          const encodedValue = encodeURIComponent(formParams[paramKey])
          formBody.push(encodedKey + '=' + encodedValue)
        }
        formBody = formBody.join('&')
 
        requestOptions.body = formBody
        requestOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded'
      }
    }
 
    if (this.auth) {
      requestOptions = this.auth.authenticate(requestOptions)
    }
 
    let parsedUrl = urlTemplate.parse(link.url)
    parsedUrl = parsedUrl.expand(pathParams)
    parsedUrl = new URL(parsedUrl)
    parsedUrl.set('query', queryParams)
 
    return {
      url: parsedUrl.toString(),
      options: requestOptions
    }
  }
 
  action (link, decoders, params = {}) {
    const responseCallback = this.responseCallback
    const request = this.buildRequest(link, decoders, params)
 
    if (this.requestCallback) {
      this.requestCallback(request)
    }
 
    return this.fetch(request.url, request.options)
      .then(function (response) {
        return parseResponse(response, decoders, responseCallback)
          .then(function (data) {
            if (response.ok) {
              return data
            } else {
              const title = response.status + ' ' + response.statusText
              const error = new errors.ErrorMessage(title, data)
              return Promise.reject(error)
            }
          })
      })
  }
}
 
module.exports = {
  HTTPTransport: HTTPTransport
}