contentful-sdk-core
Version:
Core modules for the Contentful JS SDKs
132 lines (131 loc) • 4.9 kB
JavaScript
import copy from 'fast-copy';
import qs from 'qs';
import asyncToken from './async-token.js';
import rateLimitRetry from './rate-limit.js';
import rateLimitThrottle from './rate-limit-throttle.js';
// Matches 'sub.host:port' or 'host:port' and extracts hostname and port
// Also enforces toplevel domain specified, no spaces and no protocol
const HOST_REGEX = /^(?!\w+:\/\/)([^\s:]+\.?[^\s:]+)(?::(\d+))?(?!:)$/;
/**
* Create pre-configured axios instance
* @private
* @param {AxiosStatic} axios - Axios library
* @param {CreateHttpClientParams} options - Initialization parameters for the HTTP client
* @return {AxiosInstance} Initialized axios instance
*/
export default function createHttpClient(axios, options) {
const defaultConfig = {
insecure: false,
retryOnError: true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
logHandler: (level, data) => {
if (level === 'error' && data) {
const title = [data.name, data.message].filter((a) => a).join(' - ');
console.error(`[error] ${title}`);
console.error(data);
return;
}
console.log(`[${level}] ${data}`);
},
// Passed to axios
headers: {},
httpAgent: false,
httpsAgent: false,
timeout: 30000,
throttle: 0,
basePath: '',
adapter: undefined,
maxContentLength: 1073741824, // 1GB
maxBodyLength: 1073741824, // 1GB
};
const config = {
...defaultConfig,
...options,
};
if (!config.accessToken) {
const missingAccessTokenError = new TypeError('Expected parameter accessToken');
config.logHandler('error', missingAccessTokenError);
throw missingAccessTokenError;
}
// Construct axios baseURL option
const protocol = config.insecure ? 'http' : 'https';
const space = config.space ? `${config.space}/` : '';
let hostname = config.defaultHostname;
let port = config.insecure ? 80 : 443;
if (config.host && HOST_REGEX.test(config.host)) {
const parsed = config.host.split(':');
if (parsed.length === 2) {
;
[hostname, port] = parsed;
}
else {
hostname = parsed[0];
}
}
// Ensure that basePath does start but not end with a slash
if (config.basePath) {
config.basePath = `/${config.basePath.split('/').filter(Boolean).join('/')}`;
}
const baseURL = options.baseURL || `${protocol}://${hostname}:${port}${config.basePath}/spaces/${space}`;
if (!config.headers.Authorization && typeof config.accessToken !== 'function') {
config.headers.Authorization = 'Bearer ' + config.accessToken;
}
const axiosOptions = {
// Axios
baseURL,
headers: config.headers,
httpAgent: config.httpAgent,
httpsAgent: config.httpsAgent,
proxy: config.proxy,
timeout: config.timeout,
adapter: config.adapter,
maxContentLength: config.maxContentLength,
maxBodyLength: config.maxBodyLength,
paramsSerializer: {
serialize: (params) => {
return qs.stringify(params);
},
},
// Contentful
logHandler: config.logHandler,
responseLogger: config.responseLogger,
requestLogger: config.requestLogger,
retryOnError: config.retryOnError,
};
const instance = axios.create(axiosOptions);
instance.httpClientParams = options;
/**
* Creates a new axios instance with the same default base parameters as the
* current one, and with any overrides passed to the newParams object
* This is useful as the SDKs use dependency injection to get the axios library
* and the version of the library comes from different places depending
* on whether it's a browser build or a node.js build.
* @private
* @param {CreateHttpClientParams} newParams - Initialization parameters for the HTTP client
* @return {AxiosInstance} Initialized axios instance
*/
instance.cloneWithNewParams = function (newParams) {
return createHttpClient(axios, {
...copy(options),
...newParams,
});
};
/**
* Apply interceptors.
* Please note that the order of interceptors is important
*/
if (config.onBeforeRequest) {
instance.interceptors.request.use(config.onBeforeRequest);
}
if (typeof config.accessToken === 'function') {
asyncToken(instance, config.accessToken);
}
if (config.throttle) {
rateLimitThrottle(instance, config.throttle);
}
rateLimitRetry(instance, config.retryLimit);
if (config.onError) {
instance.interceptors.response.use((response) => response, config.onError);
}
return instance;
}