UNPKG

@merkur/plugin-http-client

Version:

Merkur event emitter plugin.

279 lines (236 loc) 6.63 kB
'use strict'; 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;