@merkur/plugin-http-client
Version:
Merkur event emitter plugin.
279 lines (236 loc) • 6.63 kB
JavaScript
;
var core = require('@merkur/core');
function setDefaultConfig(widget, newDefaultConfig) {
widget.$in.httpClient.defaultConfig = {
...widget.$in.httpClient.defaultConfig,
...newDefaultConfig,
};
}
function getDefaultTransformers(widget) {
return [
transformBody(),
transformQuery(),
transformTimeout(),
];
}
function httpClientPlugin() {
return {
async setup(widget) {
core.assignMissingKeys(widget, httpClientAPI());
widget.$in.httpClient = {
defaultConfig: {
method: 'GET',
transformers: getDefaultTransformers(),
headers: {},
query: {},
timeout: 15000,
},
};
widget.$dependencies.fetch = getFetchAPI();
widget.$dependencies.AbortController = AbortController;
return widget;
},
async create(widget) {
core.bindWidgetToFunctions(widget, widget.http);
return widget;
},
};
}
function httpClientAPI() {
return {
http: {
async request(widget, requestConfig) {
let response = null;
let request = {
...widget.$in.httpClient.defaultConfig,
...requestConfig,
};
const transformers = request.transformers;
[request, response] = await runTransformers(
widget,
transformers,
'transformRequest',
request,
response,
);
response = !response
? await widget.$dependencies.fetch(request.url, request)
: response;
[request, response] = await runTransformers(
widget,
transformers,
'transformResponse',
request,
response,
);
if (!response.ok) {
const error = new Error(
`Received ${response.status} status code when requesting ${request.url}`,
{
cause: { request, response },
},
);
// keep compatablity
error.request = request;
error.response = response;
return Promise.reject(error);
}
return { request, response };
},
},
};
}
async function runTransformers(widget, transformers, method, ...rest) {
for (const transformer of transformers) {
if (typeof transformer[method] === 'function') {
rest = await transformer[method](widget, ...rest);
}
}
return rest;
}
function getFetchAPI() {
if (typeof window === 'undefined') {
return global?.fetch;
}
return window?.fetch?.bind?.(window);
}
function transformQuery() {
return {
async transformRequest(widget, request, response) {
let newRequest = { ...request };
let { baseUrl = '', path = '/' } = request;
if (!request.url) {
newRequest.url = `${
baseUrl.endsWith('/')
? baseUrl.substring(0, baseUrl.length - 1)
: baseUrl
}/${path.startsWith('/') ? path.substring(1) : path}`;
} else {
newRequest.url = request.url;
}
const queryString = Object.keys(request.query)
.map((key) =>
[key, request.query[key]].map(encodeURIComponent).join('='),
)
.join('&');
const hasQuestionMark = newRequest.url.indexOf('?') !== -1;
if (hasQuestionMark) {
newRequest.url += queryString ? `&${queryString}` : '';
} else {
newRequest.url += queryString ? `?${queryString}` : '';
}
return [newRequest, response];
},
};
}
function transformBody() {
return {
async transformResponse(widget, request, response) {
if (response.status !== 204 && typeof response.json === 'function') {
const contentType = response.headers.get('content-type');
let body = null;
if (contentType && contentType.includes('application/json')) {
body = await response.json();
} else {
body = await response.text();
}
let newResponse = copyResponse(response);
newResponse.body = body;
return [request, newResponse];
}
return [request, response];
},
async transformRequest(widget, request, response) {
const { body, headers, method } = request;
if (
body &&
(headers['Content-Type'] || headers['content-type']) ===
'application/json' &&
!['GET', 'HEAD'].includes(method)
) {
let newRequest = { ...request, body: JSON.stringify(body) };
return [newRequest, response];
}
return [request, response];
},
};
}
function transformTimeout() {
return {
async transformRequest(widget, request, response) {
let newRequest = { ...request };
if ('timeout' in request) {
const controller = new widget.$dependencies.AbortController();
newRequest.signal = controller.signal;
newRequest.timeoutTimer = setTimeout(() => {
controller.abort();
}, request.timeout);
}
return [newRequest, response];
},
async transformResponse(widget, request, response) {
if ('timeoutTimer' in request) {
clearTimeout(request.timeoutTimer);
}
return [request, response];
},
};
}
function copyResponse(response) {
const {
body,
headers,
ok,
redirected,
status,
statusText,
trailers,
type,
url,
useFinalURL,
} = response;
return {
body: assign({}, body),
headers,
ok,
redirected,
status,
statusText,
trailers,
type,
url,
useFinalURL,
};
}
const PROTECTED_FIELDS = ['__proto__', 'prototype', 'constructor'];
function assign(target, source = {}, parentField = null) {
for (const field of Object.keys(source)) {
if (PROTECTED_FIELDS.includes(field)) {
return;
}
const value = source[field];
const fieldPath = parentField ? parentField + '.' + field : field;
if (value instanceof Array) {
target[field] = value.slice();
} else if (
value instanceof Object &&
!(value instanceof Function) &&
!(value instanceof RegExp)
) {
if (!(target[field] instanceof Object)) {
target[field] = {};
}
assign(target[field], value, fieldPath);
} else {
target[field] = value;
}
}
return target;
}
exports.copyResponse = copyResponse;
exports.getDefaultTransformers = getDefaultTransformers;
exports.httpClientPlugin = httpClientPlugin;
exports.setDefaultConfig = setDefaultConfig;
exports.transformBody = transformBody;
exports.transformQuery = transformQuery;
exports.transformTimeout = transformTimeout;