zephyr-agent
Version:
Zephyr plugin agent
264 lines (263 loc) • 11 kB
JavaScript
;
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