wmakeev/moysklad

View on GitHub
src/methods/fetchUrl.js

Summary

Maintainability
F
1 wk
Test Coverage
A
100%
'use strict'

const have = require('../have')
const getResponseError = require('../getResponseError')
const {
  MoyskladRequestError,
  MoyskladApiError,
  MoyskladCollectionError,
  MoyskladUnexpectedRedirectError
} = require('../errors')

let globalRequestId = 0

module.exports = async function fetchUrl(url, options = {}) {
  const requestId = ++globalRequestId

  have.strict(arguments, { url: 'url', options: 'opt Object' })

  let result, error

  // Специфические параметры (не передаются в опции fetch)
  let rawResponse = false
  let rawRedirect = false
  let muteApiErrors = false
  let muteCollectionErrors = false

  const emit = this.emitter ? this.emitter.emit.bind(this.emitter) : null

  const fetchOptions = {
    redirect: 'manual',
    ...options,
    headers: {
      'User-Agent': this.getOptions().userAgent,
      'Content-Type': 'application/json',
      'Accept-Encoding': 'gzip',
      ...options.headers
    }
  }

  if (!fetchOptions.headers.Authorization) {
    fetchOptions.credentials = 'include'
  }

  // получаем специфичные параметры
  if (fetchOptions.rawResponse) {
    rawResponse = true
    delete fetchOptions.rawResponse
  }
  if (fetchOptions.rawRedirect) {
    rawRedirect = true
    delete fetchOptions.rawRedirect
  }
  if (/* depricated */ fetchOptions.muteErrors) {
    muteApiErrors = true
    delete fetchOptions.muteErrors
  }
  if (fetchOptions.muteApiErrors) {
    muteApiErrors = true
    delete fetchOptions.muteApiErrors
  }
  if (fetchOptions.muteCollectionErrors) {
    muteCollectionErrors = true
    delete fetchOptions.muteCollectionErrors
  }

  // X-Lognex
  if (fetchOptions.precision) {
    fetchOptions.headers['X-Lognex-Precision'] = 'true'
    delete fetchOptions.precision
  }
  if (fetchOptions.webHookDisable) {
    fetchOptions.headers['X-Lognex-WebHook-Disable'] = 'true'
    delete fetchOptions.webHookDisable
  }
  if (fetchOptions.downloadExpirationSeconds) {
    fetchOptions.headers['X-Lognex-Download-Expiration-Seconds'] = String(
      fetchOptions.downloadExpirationSeconds
    )
    delete fetchOptions.downloadExpirationSeconds
  }

  const authHeader = this.getAuthHeader()
  if (authHeader) {
    fetchOptions.headers.Authorization = this.getAuthHeader()
  }

  if (emit) emit('request', { requestId, url, options: fetchOptions })

  /** @type {Response} */
  const response = await this.fetch(url, fetchOptions)

  if (emit)
    emit('response', { requestId, url, options: fetchOptions, response })

  if (rawResponse) return response

  if (response.status >= 300 && response.status < 400) {
    if (rawRedirect) {
      return response
    } else {
      throw new MoyskladUnexpectedRedirectError(response)
    }
  }

  // response.ok → response.status >= 200 && response.status < 300
  if (response.status < 200 || response.status >= 300) {
    error = new MoyskladRequestError(
      [response.status, response.statusText].filter(it => it).join(' '),
      response
    )
  }

  // разбираем тело запроса
  if (
    response.headers.has('Content-Type') &&
    response.headers.get('Content-Type').indexOf('application/json') !== -1
  ) {
    // response.json() может вызвать ошибку, если тело ответа пустое
    const resBodyText = await response.text()

    if (resBodyText) {
      result = JSON.parse(resBodyText)
    } else {
      result = undefined
    }

    error = getResponseError(result, response) || error
  }

  if (emit) {
    emit('response:body', {
      requestId,
      url,
      options: fetchOptions,
      response,
      body: result
    })
  }

  if (error) {
    if (error instanceof MoyskladApiError && muteApiErrors) {
      return result
    }

    if (error instanceof MoyskladCollectionError && muteCollectionErrors) {
      return result
    }

    if (emit) emit('error', error, { requestId })
    throw error
  }

  return result
}