UNPKG

@vtex/api

Version:
147 lines (146 loc) 8.11 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HttpClient = void 0; const crypto_1 = require("crypto"); const koa_compose_1 = __importDefault(require("koa-compose")); const p_limit_1 = __importDefault(require("p-limit")); const constants_1 = require("../constants"); const binding_1 = require("../utils/binding"); const tenant_1 = require("../utils/tenant"); const cache_1 = require("./middlewares/cache"); const cancellationToken_1 = require("./middlewares/cancellationToken"); const inflight_1 = require("./middlewares/inflight"); const memoization_1 = require("./middlewares/memoization"); const metrics_1 = require("./middlewares/metrics"); const notFound_1 = require("./middlewares/notFound"); const recorder_1 = require("./middlewares/recorder"); const request_1 = require("./middlewares/request"); const tracing_1 = require("./middlewares/tracing"); const DEFAULT_TIMEOUT_MS = 1000; const noTransforms = [(data) => data]; class HttpClient { constructor(opts) { this.get = (url, config = {}) => { const cacheableConfig = this.getConfig(url, config); return this.request(cacheableConfig).then(response => response.data); }; this.getRaw = (url, config = {}) => { const cacheableConfig = this.getConfig(url, config); return this.request(cacheableConfig); }; this.getWithBody = (url, data, config = {}) => { const deterministicReplacer = (_, v) => { try { return typeof v !== 'object' || v === null || Array.isArray(v) ? v : Object.fromEntries(Object.entries(v).sort(([ka], [kb]) => ka < kb ? -1 : ka > kb ? 1 : 0)); } catch (error) { // I don't believe this will ever happen, but just in case // Also, I didn't include error as I am unsure if it would have sensitive information this.logger.warn({ message: 'Error while sorting object for cache key' }); return v; } }; const bodyHash = (0, crypto_1.createHash)('md5').update(JSON.stringify(data, deterministicReplacer)).digest('hex'); const cacheableConfig = this.getConfig(url, { ...config, data, params: { ...config.params, [constants_1.BODY_HASH]: bodyHash, }, }); return this.request(cacheableConfig).then(response => response.data); }; this.getBuffer = (url, config = {}) => { const bufferConfig = { cacheable: cache_1.CacheType.Disk, ...config, url, responseType: 'arraybuffer', transformResponse: noTransforms }; return this.request(bufferConfig); }; this.getStream = (url, config = {}) => { const streamConfig = { ...config, url, responseType: 'stream', transformResponse: noTransforms }; return this.request(streamConfig).then(response => response.data); }; this.put = (url, data, config = {}) => { const putConfig = { ...config, url, data, method: 'put' }; return this.request(putConfig).then(response => response.data); }; this.putRaw = (url, data, config = {}) => { const putConfig = { ...config, url, data, method: 'put' }; return this.request(putConfig); }; this.post = (url, data, config = {}) => { const postConfig = { ...config, url, data, method: 'post' }; return this.request(postConfig).then(response => response.data); }; this.postRaw = (url, data, config = {}) => { const postConfig = { ...config, url, data, method: 'post' }; return this.request(postConfig); }; this.patch = (url, data, config = {}) => { const patchConfig = { ...config, url, data, method: 'patch' }; return this.request(patchConfig).then(response => response.data); }; this.head = (url, config = {}) => { const headConfig = { ...config, url, method: 'head' }; return this.request(headConfig); }; this.delete = (url, config) => { const deleteConfig = { ...config, url, method: 'delete' }; return this.request(deleteConfig); }; this.request = async (config) => { const context = { config }; await this.runMiddlewares(context); return context.response; }; this.getConfig = (url, config = {}) => ({ cacheable: this.cacheableType, memoizable: this.memoizable, ...config, url, }); const { baseURL, authToken, authType, memoryCache, diskCache, memoizable = true, asyncSetCache, locale, name, metrics, product, serverTiming, recorder, userAgent, timeout = DEFAULT_TIMEOUT_MS, segmentToken, sessionToken, retries, concurrency, headers: defaultHeaders, host, params, operationId, tenant, binding, verbose, cancellation, exponentialTimeoutCoefficient, initialBackoffDelay, exponentialBackoffCoefficient, httpsAgent, tracer, logger, cacheableType = cache_1.CacheType.Memory, } = opts; this.name = name || baseURL || 'unknown'; this.logger = logger; this.cacheableType = cacheableType; this.memoizable = memoizable; const limit = concurrency && concurrency > 0 && (0, p_limit_1.default)(concurrency) || undefined; const headers = { ...defaultHeaders, 'Accept-Encoding': 'gzip', 'User-Agent': userAgent, ...host ? { [constants_1.FORWARDED_HOST_HEADER]: host } : null, ...tenant ? { [constants_1.TENANT_HEADER]: (0, tenant_1.formatTenantHeaderValue)(tenant) } : null, ...binding ? { [constants_1.BINDING_HEADER]: (0, binding_1.formatBindingHeaderValue)(binding) } : null, ...locale ? { [constants_1.LOCALE_HEADER]: locale } : null, ...operationId ? { 'x-vtex-operation-id': operationId } : null, ...product ? { [constants_1.PRODUCT_HEADER]: product } : null, ...segmentToken ? { [constants_1.SEGMENT_HEADER]: segmentToken } : null, ...sessionToken ? { [constants_1.SESSION_HEADER]: sessionToken } : null, }; if (authType && authToken) { headers['Authorization'] = `${authType} ${authToken}`; // tslint:disable-line } const memoizedCache = new Map(); this.runMiddlewares = (0, koa_compose_1.default)([ (0, tracing_1.createHttpClientTracingMiddleware)({ tracer, logger, clientName: this.name, hasDiskCacheMiddleware: !!diskCache, hasMemoryCacheMiddleware: !!memoryCache }), ...(opts.middlewares || []), (0, request_1.defaultsMiddleware)({ baseURL, rawHeaders: headers, params, timeout, retries, verbose, exponentialTimeoutCoefficient, initialBackoffDelay, exponentialBackoffCoefficient, httpsAgent }), (0, metrics_1.metricsMiddleware)({ metrics, serverTiming, name }), (0, memoization_1.memoizationMiddleware)({ memoizedCache }), ...recorder ? [(0, recorder_1.recorderMiddleware)(recorder)] : [], (0, cancellationToken_1.cancellationToken)(cancellation), inflight_1.singleFlightMiddleware, notFound_1.acceptNotFoundMiddleware, ...memoryCache ? [(0, cache_1.cacheMiddleware)({ type: cache_1.CacheType.Memory, storage: memoryCache, asyncSet: asyncSetCache })] : [], ...diskCache ? [(0, cache_1.cacheMiddleware)({ type: cache_1.CacheType.Disk, storage: diskCache, asyncSet: asyncSetCache })] : [], notFound_1.notFoundFallbackMiddleware, request_1.routerCacheMiddleware, (0, request_1.requestMiddleware)(limit), ]); } } exports.HttpClient = HttpClient;