octoblu/browser-meshblu-http

View on GitHub
src/meshblu-request.coffee

Summary

Maintainability
Test Coverage
qs          = require 'qs'
SrvFailover = require 'srv-failover'
superagent  = require 'superagent'
URL         = require 'url'

_ = {
  defaults:  require 'lodash/defaults'
  dropRight: require 'lodash/dropRight'
  each:      require 'lodash/each'
  isEmpty:   require 'lodash/isEmpty'
  join:      require 'lodash/join'
  minBy:     require 'lodash/minBy'
  pick:      require 'lodash/pick'
  split:     require 'lodash/split'
  takeRight: require 'lodash/takeRight'
  toLower:   require 'lodash/toLower'
}

discardReturn = require './discard-return.coffee'

class MeshbluRequest
  constructor: (options={}) ->
    {
      @protocol
      @hostname
      @port
      service
      domain
      secure
      resolveSrv
      @dnsHttpServer
      @serviceName
    } = options

    return unless resolveSrv
    protocol = 'http'
    protocol = 'https' if secure
    @srvFailover = new SrvFailover {domain, service, protocol}

  delete: (pathname, options, callback) =>
    requestOptions = _.pick(options, 'uuid', 'token', 'bearerToken', 'headers')
    requestOptions.pathname = pathname
    query = qs.stringify options.query

    @_resolveBaseUrl pathname, (error, baseUri) =>
      return callback error if error?
      @_doRequest({method: 'delete', baseUri, requestOptions, query}, callback)

  get: (pathname, options, callback) =>
    requestOptions = _.pick(options, 'uuid', 'token', 'bearerToken', 'headers')
    requestOptions.pathname = pathname
    query = qs.stringify options.query

    @_resolveBaseUrl pathname, (error, baseUri) =>
      return callback error if error?
      @_doRequest({method: 'get', baseUri, requestOptions, query}, callback)

  patch: (pathname, options, callback) =>
    requestOptions = _.pick(options, 'uuid', 'token', 'bearerToken', 'headers')
    requestOptions.pathname = pathname
    body = options.body

    @_resolveBaseUrl pathname, (error, baseUri) =>
      return callback error if error?
      @_doRequest({method: 'patch', baseUri, requestOptions, body}, callback)

  post: (pathname, options, callback) =>
    requestOptions = _.pick(options, 'uuid', 'token', 'bearerToken', 'headers')
    requestOptions.pathname = pathname
    body = options.body

    @_resolveBaseUrl pathname, (error, baseUri) =>
      return callback error if error?
      @_doRequest({method: 'post', baseUri, requestOptions, body}, callback)

  put: (pathname, options, callback) =>
    requestOptions = _.pick(options, 'uuid', 'token', 'bearerToken', 'headers')
    requestOptions.pathname = pathname
    body = options.body

    @_resolveBaseUrl pathname, (error, baseUri) =>
      return callback error if error?
      @_doRequest({method: 'put', baseUri, requestOptions, body}, callback)

  _doRequest: ({method, baseUri, requestOptions, query, body}, callback) =>
    @_request(method, baseUri, requestOptions).query(query).send(body).end (error, response)=>
      if error?.crossDomain
        return @_retrySrvRequest(error, {method, baseUri, requestOptions, query, body}, callback)
      @_handleResponse(callback)(error, response)

  _handleResponse: (callback) => (error, response) =>
    return callback error if error?
    return callback null if response.notFound
    return callback new Error 'Invalid Response Code' unless response.ok
    return callback null, response.body

  _inBrowser: => window?

  _request: (method, baseUri, {pathname, uuid, token, bearerToken, headers}) =>
    method = _.toLower method
    theRequest = superagent[method](@_url baseUri, pathname)
    theRequest.auth uuid, token if uuid? && token?
    theRequest.set('Authorization', "Bearer #{bearerToken}") if bearerToken?
    theRequest.set('x-meshblu-service-name', @serviceName) if @serviceName?
    theRequest.accept('application/json')
    theRequest.set('Content-Type', 'application/json')
    _.each headers, (value, key) =>
      theRequest.set key, value
    return theRequest

  _resolveBaseUrl: (pathname, cb) =>
    callback = discardReturn cb

    return callback null, URL.format {@protocol, @hostname, @port} unless @srvFailover?

    @srvFailover.resolveUrl (error, baseUrl) =>
      return callback error if error?
      return callback null, baseUrl if @_inBrowser()

      superagent.options(@_url(baseUrl, pathname)).end (error) =>
        if error?#  || response.statusCode != 204
          @srvFailover.markBadUrl baseUrl, ttl: 60000
          return @_resolveBaseUrl pathname, callback
        return callback null, baseUrl

  _retrySrvRequest: (error, options, callback) =>
    return callback error unless @srvFailover?

    {method, baseUri, requestOptions, query, body} = options
    @srvFailover.markBadUrl baseUri, ttl: 60000
    @srvFailover.resolveUrl (error, baseUri) =>
      return callback error if error?
      return @_doRequest {method, baseUri, requestOptions, query, body}, callback

  _url: (baseUri, pathname) =>
    {protocol, hostname, port} = URL.parse baseUri
    return URL.format({ hostname, protocol, port, pathname })

module.exports = MeshbluRequest