integreat-transporter-http
Version:
HTTP transporter for Integreat
166 lines • 6.31 kB
JavaScript
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