UNPKG

@defra/wls-connectors-lib

Version:

A library extracting the various connectivity functions for the wildlife service

123 lines (109 loc) 3.89 kB
/** * Generic fetch operations supporting API and Power Platform requests */ import db from 'debug' import pkg from 'node-fetch' const fetch = pkg.default const debug = db('connectors-lib:fetch') const debugTime = db('connectors-lib:fetch-performance') const DEFAULT_TIMEOUT = '20000' const APPLICATION_JSON = 'application/json' export class HTTPResponseError extends Error { constructor (response) { super(`HTTP Error Response: ${response.status} ${response.statusText}`) this.response = response } } /** * General behaviour on response. Will throw exceptions on any failure except * not found, and will de-serialize json responses, return null on no-content * or return the stream in all other cases * @param responsePromise * @returns {Promise<null|*>} */ export const checkResponseOkElseThrow = async responsePromise => { const response = await responsePromise debug(`HTTP response code: ${JSON.stringify(response.status)}`) if (response.ok) { if (response.status === 204) { return null } else { if (response.headers.get('content-type').includes(APPLICATION_JSON)) { return response.json() } else { return response.body } } } else { if (response.status === 404) { return null } else { throw new HTTPResponseError(response) } } } /** * Make a general HTTP request using node-fetch. Deal with timeouts and exceptions. * Allows different behaviour for the Power Platform and API etc. with respect to responses * @param url - The fully qualified endpoint of the exception * @param method - GET POST PUT DELETE * @param payload - The body of the request * @param headerFunc - A function to return the set of headers * @param responseFunc - A function to parse on the reponse * @param timeOutMS - The Timeout in milliseconds * @returns {Promise<*|*>} */ export const httpFetch = async (url, method, payload, headerFunc, responseFunc = checkResponseOkElseThrow, timeOutMS = DEFAULT_TIMEOUT, additionalOptions = {}) => { const headers = headerFunc && typeof headerFunc === 'function' ? await headerFunc() : { 'Content-Type': APPLICATION_JSON, Accept: APPLICATION_JSON } const abortController = new global.AbortController() const options = { headers, method, signal: abortController.signal, ...payload && { body: payload }, ...additionalOptions } debug(`Making HTTP request to ${url} with options: \n${JSON.stringify(options, null, 4)}`) // Create a timeout debug(`Setting timeout ${parseInt(timeOutMS)}...`) const timeout = setTimeout(() => { debug('Request timeout: abort controller ') abortController.abort() }, parseInt(timeOutMS)) try { // Make the request const startTs = Date.now() const responsePromise = fetch(url, options) // Run the supplied async response function const result = await responseFunc(responsePromise) debug(`HTTP response data: ${JSON.stringify(result)}`) const endTs = Date.now() debugTime(`${url},${options.method} ${endTs - startTs}ms`) return result } catch (err) { if (err.name === 'AbortError') { // Create a client timeout response console.error('Fetch ABORT error', err) throw new HTTPResponseError({ status: 408, statusText: 'Request Timeout' }) } else if (err.name === 'FetchError') { console.error('Fetch REQUEST error', err) throw err } else { if (err.response) { const msg = 'response error: ' + err.response.headers.get('content-type').includes(APPLICATION_JSON) ? JSON.stringify(await err.response.json()) : await err.response.body() console.error(`Unknown error thrown in fetch: ${msg}`) throw new Error(msg) } else { throw err } } } finally { debug('Request timeout clear ') clearTimeout(timeout) } }