UNPKG

@sectester/core

Version:

The core package can be used to obtain a config including credentials from different sources, and provide a simplified abstraction to handle events and commands.

99 lines 4.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FetchApiClient = void 0; const ApiError_1 = require("../exceptions/ApiError"); const RateLimitError_1 = require("../exceptions/RateLimitError"); const RateLimiter_1 = require("./RateLimiter"); const RetryHandler_1 = require("./RetryHandler"); const node_util_1 = require("node:util"); const node_crypto_1 = require("node:crypto"); class FetchApiClient { constructor(config) { this.config = config; this.rateLimiter = new RateLimiter_1.RateLimiter(); this.retryHandler = new RetryHandler_1.RetryHandler({ maxRetries: 3, baseDelay: 1000, maxDelay: 30000, jitterFactor: 0.3, ...config.retry }); } request(path, options) { var _a, _b; const url = new URL(path, this.config.baseUrl); const requestOptions = { redirect: 'follow', keepalive: true, ...options, headers: this.createHeaders(options === null || options === void 0 ? void 0 : options.headers), method: ((_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET').toUpperCase(), handle409Redirects: (_b = options === null || options === void 0 ? void 0 : options.handle409Redirects) !== null && _b !== void 0 ? _b : true }; const idempotent = FetchApiClient.IDEMPOTENT_METHODS.has(requestOptions.method); return this.retryHandler.executeWithRetry(() => this.makeRequest(url, requestOptions), { idempotent, signal: requestOptions.signal }); } async makeRequest(url, options) { var _a, _b; const { handle409Redirects = true, ...requestOptions } = options !== null && options !== void 0 ? options : {}; const signal = (_a = requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.signal) !== null && _a !== void 0 ? _a : AbortSignal.timeout((_b = this.config.timeout) !== null && _b !== void 0 ? _b : 10000); const response = await fetch(url, { ...requestOptions, signal }); return this.handleResponse(response, handle409Redirects); } // eslint-disable-next-line complexity async handleResponse(response, handle409Redirects = true) { var _a; if (!response.ok) { if (response.status === 409 && response.headers.has('location') && handle409Redirects) { const locationPath = response.headers.get('location'); // eslint-disable-next-line max-depth if (locationPath) { // Handle both absolute and relative URLs const locationUrl = new URL(locationPath, this.config.baseUrl); return this.request(locationUrl.toString()); } } const rateLimitInfo = this.rateLimiter.extractRateLimitInfo(response); const contentType = response.headers.get('content-type'); const mimeType = contentType ? new node_util_1.MIMEType(contentType) : undefined; const responseBody = (mimeType === null || mimeType === void 0 ? void 0 : mimeType.type) === 'text' ? await response.clone().text() : undefined; if (response.status === 429) { const retryAfter = parseInt((_a = response.headers.get('retry-after')) !== null && _a !== void 0 ? _a : rateLimitInfo.reset.toString(), 10); throw new RateLimitError_1.RateLimitError(response, retryAfter, responseBody); } throw new ApiError_1.ApiError(response, responseBody); } return response; } createHeaders(headersInit = {}) { var _a; const headers = new Headers({ ...headersInit, 'idempotency-key': (0, node_crypto_1.randomUUID)(), ...(this.config.userAgent ? { 'user-agent': this.config.userAgent } : {}) }); if (this.config.apiKey) { const prefix = (_a = this.config.apiKeyPrefix) !== null && _a !== void 0 ? _a : 'Api-Key'; headers.set('authorization', `${prefix} ${this.config.apiKey}`); } return headers; } } exports.FetchApiClient = FetchApiClient; FetchApiClient.IDEMPOTENT_METHODS = new Set([ 'GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE' ]); //# sourceMappingURL=FetchApiClient.js.map