UNPKG

zephyr-agent

Version:
264 lines (263 loc) • 11 kB
"use strict"; var __webpack_require__ = {}; (()=>{ __webpack_require__.n = (module)=>{ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module; __webpack_require__.d(getter, { a: getter }); return getter; }; })(); (()=>{ __webpack_require__.d = (exports1, getters, values)=>{ var define = (defs, kind)=>{ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, { enumerable: true, [kind]: defs[key] }); }; define(getters, "get"); define(values, "value"); }; })(); (()=>{ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop); })(); (()=>{ __webpack_require__.r = (exports1)=>{ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, { value: 'Module' }); Object.defineProperty(exports1, '__esModule', { value: true }); }; })(); var __webpack_exports__ = {}; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, { DEFAULT_HTTP_DEADLINE_MS: ()=>DEFAULT_HTTP_DEADLINE_MS, fetchWithRetries: ()=>fetchWithRetries }); const external_axios_namespaceObject = require("axios"); var external_axios_default = /*#__PURE__*/ __webpack_require__.n(external_axios_namespaceObject); const external_https_proxy_agent_namespaceObject = require("https-proxy-agent"); const external_ci_info_namespaceObject = require("ci-info"); const index_js_namespaceObject = require("../errors/index.js"); const redaction_js_namespaceObject = require("../security/redaction.js"); const IPV4_FAMILY = 4; const DEFAULT_HTTP_DEADLINE_MS = 30000; const RETRY_BASE_DELAY_MS = 100; const RETRY_ERROR_CODES = [ 'ETIMEDOUT', 'ECONNABORTED', 'ENETUNREACH', 'ECONNRESET', 'ECONNREFUSED', 'EAI_AGAIN', 'ENOTFOUND', 'EPIPE' ]; const RETRY_SAFE_METHODS = new Set([ 'GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE' ]); const RETRYABLE_STATUS_CODES = new Set([ 408, 425, 429 ]); function hasIdempotencyKey(headers) { if (!headers) return false; if (headers instanceof Headers) return headers.has('idempotency-key'); if (Array.isArray(headers)) return headers.some(([key])=>'idempotency-key' === key.toLowerCase()); return Object.keys(headers).some((key)=>'idempotency-key' === key.toLowerCase()); } function getHttpsProxyUrl() { const envVars = [ 'HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy' ]; for (const envVar of envVars){ const value = process.env[envVar]; if (value) return value; } } function shouldBypassProxy(hostname) { const noProxy = process.env['NO_PROXY'] || process.env['no_proxy']; if (!noProxy) return false; const noProxyList = noProxy.split(',').map((h)=>h.trim().toLowerCase()); const hostLower = hostname.toLowerCase(); return noProxyList.some((noProxyHost)=>{ if ('*' === noProxyHost) return true; if (noProxyHost.startsWith('.')) return hostLower.endsWith(noProxyHost) || hostLower === noProxyHost.slice(1); return hostLower === noProxyHost || hostLower.endsWith(`.${noProxyHost}`); }); } function getHttpsProxyAgent(url) { if (shouldBypassProxy(url.hostname)) return; const httpsProxyUrl = getHttpsProxyUrl(); return httpsProxyUrl ? new external_https_proxy_agent_namespaceObject.HttpsProxyAgent(httpsProxyUrl) : void 0; } function toHttpLikeError(error) { if ('object' == typeof error && null !== error || 'function' == typeof error) return error; return { message: String(error) }; } function isRetryableNetworkError(error) { const candidate = toHttpLikeError(error); if (candidate.response) return false; const message = String(candidate.message ?? '').toLowerCase(); return Boolean(candidate.code && RETRY_ERROR_CODES.includes(candidate.code) || message.includes('network') || message.includes('timeout')); } function isRetryableStatus(status) { return RETRYABLE_STATUS_CODES.has(status) || status >= 500; } async function waitForRetry(retryIndex, deadline, signal, retryAfterMs = 0) { if (signal?.aborted) return false; const remaining = deadline - Date.now(); const delay = Math.min(Math.max(RETRY_BASE_DELAY_MS * 2 ** retryIndex, retryAfterMs), remaining - 1); if (delay <= 0) return false; await new Promise((resolve)=>{ const timer = setTimeout(done, delay); function done() { clearTimeout(timer); signal?.removeEventListener('abort', done); resolve(); } signal?.addEventListener('abort', done, { once: true }); }); return !signal?.aborted && Date.now() < deadline; } function getRetryAfterMs(headers) { const candidate = headers; const value = candidate?.get?.('retry-after') ?? candidate?.['retry-after']; if (null == value) return 0; const seconds = Number(value); if (Number.isFinite(seconds)) return Math.max(0, 1000 * seconds); const retryDate = Date.parse(String(value)); return Number.isNaN(retryDate) ? 0 : Math.max(0, retryDate - Date.now()); } function stringifyUnknown(value) { if ('string' == typeof value) return value; try { return JSON.stringify(value) ?? String(value); } catch { try { return String(value); } catch { return '[unserializable value]'; } } } function toResponse(response) { const headers = Object.entries(response.headers ?? {}).reduce((acc, [key, value])=>{ if (void 0 !== value) acc[key] = 'string' == typeof value ? value : String(value); return acc; }, {}); return { ok: response.status >= 200 && response.status < 300, status: response.status, headers: new Headers(headers), text: async ()=>stringifyUnknown(response.data), json: async ()=>response.data }; } function normalizeRequestError(error, url, method) { if (error instanceof index_js_namespaceObject.ZephyrError) return error; const candidate = toHttpLikeError(error); if (candidate.response) return new index_js_namespaceObject.ZephyrError(index_js_namespaceObject.ZeErrors.ERR_HTTP_ERROR, { status: candidate.response.status, url: (0, redaction_js_namespaceObject.redactUrl)(url), content: 'string' == typeof candidate.response.data ? (0, redaction_js_namespaceObject.redactString)(candidate.response.data) : (0, redaction_js_namespaceObject.safeStringifyForLogging)(candidate.response.data), method }); if ('ENOTFOUND' === candidate.code) { let failedHostname = url.hostname; if ('string' == typeof candidate.hostname) failedHostname = candidate.hostname; else if ('string' == typeof candidate.cause?.hostname) failedHostname = candidate.cause.hostname; return new index_js_namespaceObject.ZephyrError(index_js_namespaceObject.ZeErrors.ERR_HTTP_ERROR, { status: -1, url: (0, redaction_js_namespaceObject.redactUrl)(url), content: `DNS lookup failed for hostname "${(0, redaction_js_namespaceObject.redactString)(failedHostname)}" (ENOTFOUND). Verify the hostname's DNS record.`, method }); } if (isRetryableNetworkError(error) || 'ERR_CANCELED' === candidate.code) return new index_js_namespaceObject.ZephyrError(index_js_namespaceObject.ZeErrors.ERR_HTTP_ERROR, { status: -1, url: (0, redaction_js_namespaceObject.redactUrl)(url), content: 'ERR_CANCELED' === candidate.code ? 'Request canceled' : (0, redaction_js_namespaceObject.redactString)(`Request failed after retries: ${candidate.code ?? candidate.message}`), method }); return new index_js_namespaceObject.ZephyrError(index_js_namespaceObject.ZeErrors.ERR_UNKNOWN, { message: 'Unknown error occurred', cause: (0, redaction_js_namespaceObject.sanitizeForLogging)(error) }); } async function fetchWithRetries(url, options = {}, retries = 3, deadlineMs = DEFAULT_HTTP_DEADLINE_MS) { const method = (options.method ?? 'GET').toUpperCase(); const retrySafe = RETRY_SAFE_METHODS.has(method) || hasIdempotencyKey(options.headers); const maxRetries = retrySafe && Number.isFinite(retries) ? Math.max(0, Math.floor(retries)) : 0; const boundedDeadlineMs = Number.isFinite(deadlineMs) ? Math.max(1, deadlineMs) : DEFAULT_HTTP_DEADLINE_MS; const deadline = Date.now() + boundedDeadlineMs; const httpsAgent = getHttpsProxyAgent(url); const axiosInstance = external_axios_default().create({ family: external_ci_info_namespaceObject.isCI ? IPV4_FAMILY : void 0, httpsAgent, validateStatus: ()=>true }); const axiosConfig = { method, data: options.body, ...options.signal ? { signal: options.signal } : {} }; if (options.headers) if (options.headers instanceof Headers) { const headersRecord = {}; options.headers.forEach((value, key)=>{ headersRecord[key] = value; }); axiosConfig.headers = headersRecord; } else if (Array.isArray(options.headers)) { const headersRecord = {}; options.headers.forEach(([key, value])=>{ headersRecord[key] = value; }); axiosConfig.headers = headersRecord; } else axiosConfig.headers = options.headers; for(let attempt = 0; attempt <= maxRetries; attempt++)try { const response = await axiosInstance(url.toString(), { ...axiosConfig, timeout: Math.max(1, deadline - Date.now()) }); if (attempt < maxRetries && isRetryableStatus(response.status) && await waitForRetry(attempt, deadline, options.signal, getRetryAfterMs(response.headers))) continue; return toResponse(response); } catch (error) { const canRetry = attempt < maxRetries && !options.signal?.aborted && isRetryableNetworkError(error) && await waitForRetry(attempt, deadline, options.signal); if (canRetry) continue; throw normalizeRequestError(error, url, method); } throw new index_js_namespaceObject.ZephyrError(index_js_namespaceObject.ZeErrors.ERR_UNKNOWN, { message: 'HTTP retry loop exited unexpectedly' }); } exports.DEFAULT_HTTP_DEADLINE_MS = __webpack_exports__.DEFAULT_HTTP_DEADLINE_MS; exports.fetchWithRetries = __webpack_exports__.fetchWithRetries; for(var __rspack_i in __webpack_exports__)if (-1 === [ "DEFAULT_HTTP_DEADLINE_MS", "fetchWithRetries" ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i]; Object.defineProperty(exports, '__esModule', { value: true }); //# sourceMappingURL=fetch-with-retries.js.map