UNPKG

integreat-transporter-http

Version:

HTTP transporter for Integreat

166 lines 6.31 kB
import debugFn from 'debug'; import got from 'got'; import queryString from 'query-string'; import { createResponse, createResponseWithError } from './utils/response.js'; import { isDate } from './utils/is.js'; const debug = debugFn('integreat:transporter:http'); function prepareLogUrl(url, query) { const searchIndex = url.indexOf('?'); const bareUrl = searchIndex >= 0 ? url.slice(0, searchIndex) : url; const querystring = query.toString(); return querystring ? `${bareUrl}?${querystring}` : bareUrl; } const logRequest = (request, noLogging) => { if (!noLogging) { const message = `Sending ${request.method} ${request.url}`; debug('%s: %o %s', message, request.headers, request.body); } }; const logResponse = (response, { url, method }, noLogging) => { if (!noLogging) { const { status, error } = response; const message = status === 'ok' ? `Success from ${method} ${url}` : `Error '${status}' from ${method} ${url}: ${error}`; debug('%s: %o', message, response); } }; function generateUrl({ uri, baseUri } = {}) { if (baseUri) { if (uri && uri[0] === '/') { return baseUri.endsWith('/') ? `${baseUri}${uri.slice(1)}` : `${baseUri}${uri}`; } else if (uri) { return baseUri.endsWith('/') ? `${baseUri}${uri}` : `${baseUri}/${uri}`; } else { return baseUri; } } else { return uri; } } function extractQueryParamsFromUri(uri) { if (typeof uri === 'string') { const position = uri.indexOf('?'); if (position > -1) { return queryString.parse(uri.slice(position)); } } return {}; } const prepareQueryValue = (value) => isDate(value) ? value.toISOString() : value === null ? '' : ['string', 'number', 'boolean'].includes(typeof value) ? String(value) : JSON.stringify(value); const prepareQueryParams = (params) => new URLSearchParams(Object.entries(params) .filter(([_key, value]) => value !== undefined) .reduce((params, [key, value]) => Array.isArray(value) ? [ ...params, ...value.map((val) => [key, prepareQueryValue(val)]), ] : [...params, [key, prepareQueryValue(value)]], [])); const generateQueryParams = ({ queryParams, authAsQuery, uri } = {}, auth) => prepareQueryParams({ ...extractQueryParamsFromUri(uri), ...queryParams, ...(authAsQuery && auth && auth !== true ? auth : {}), }); const removeContentTypeIf = (headers, doRemove) => doRemove ? Object.entries(headers).reduce((headers, [key, value]) => key.toLowerCase() === 'content-type' ? headers : { ...headers, [key]: value }, {}) : headers; const createHeaders = (options, data, headers, auth) => ({ 'user-agent': 'integreat-transporter-http/1.4', ...(typeof data === 'string' ? { 'Content-Type': 'text/plain' } : { 'Content-Type': 'application/json' }), ...options?.headers, ...headers, ...(auth === true || options?.authAsQuery || options?.authInData ? {} : auth), }); const selectMethod = (options, data) => options?.method || (data ? 'PUT' : 'GET'); const prepareBody = (data) => typeof data === 'string' || data === undefined ? data : JSON.stringify(data); function retryFromOptions(options) { const { retry, maxDelay } = options?.rateLimit ?? {}; const retryNum = typeof retry === 'string' ? parseInt(retry) : retry; const maxDelayNum = typeof maxDelay === 'string' ? parseInt(maxDelay) : maxDelay; if (typeof retryNum === 'number' && !Number.isNaN(retryNum)) { return { limit: retryNum, maxRetryAfter: typeof maxDelayNum === 'number' && !Number.isNaN(maxDelayNum) ? maxDelayNum * 1000 : undefined, statusCodes: [429], methods: ['GET', 'HEAD', 'OPTIONS'], }; } else { return { limit: 0 }; } } function optionsFromEndpoint({ payload, meta: { options, auth } = {}, }) { const method = selectMethod(options, payload.data); return [ generateUrl(options), { searchParams: generateQueryParams(options, auth), method, body: method === 'GET' ? undefined : prepareBody(payload.data), headers: removeContentTypeIf(createHeaders(options, payload.data, payload.headers, auth), method === 'GET'), retry: retryFromOptions(options), throwHttpErrors: false, timeout: { response: typeof options?.timeout === 'number' ? options.timeout : 120000, }, }, ]; } const isOkResponse = (gotResponse) => gotResponse.statusCode >= 200 && gotResponse.statusCode < 400; function extractResponseData(response, format) { if (format === 'base64') { return response.rawBody.toString('base64'); } else { return response.body; } } function responseFormatFromAction(action) { const format = action.meta?.options?.responseFormat; return typeof format === 'string' ? format : 'string'; } const responseFromGotResponse = (gotResponse, url, action) => isOkResponse(gotResponse) ? createResponse(action, 'ok', extractResponseData(gotResponse, responseFormatFromAction(action)), undefined, gotResponse.headers) : createResponseWithError(action, url, gotResponse); export default async function send(action, connection) { const [url, options] = optionsFromEndpoint(action); if (!url) { return createResponse(action, 'badrequest', undefined, 'No uri is provided in the action'); } const logOptions = { url: prepareLogUrl(url, options.searchParams), ...options, }; const noLogging = !!action.meta?.noLogging; if (connection?.waitFn) { await connection.waitFn(); } try { logRequest(logOptions, noLogging); const gotResponse = await got(url, options); const response = responseFromGotResponse(gotResponse, url, action); logResponse(response, logOptions, noLogging); return response; } catch (error) { return createResponseWithError(action, url, error); } } //# sourceMappingURL=send.js.map