UNPKG

askui

Version:

Reliable, automated end-to-end-testing that depends on what is shown on your screen instead of the technology you are running on

192 lines (191 loc) 9.63 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HttpClientGot = void 0; const got_1 = __importStar(require("got")); const tough_cookie_1 = require("tough-cookie"); const lib_1 = require("../../lib"); const credentials_1 = require("./credentials"); const custom_errors_1 = require("./custom-errors"); const unkown_http_client_error_1 = require("./custom-errors/unkown-http-client-error"); function buildRetryLog(requestUrl, errorCode, statusCode, errorMessage, delayInMs, delayReason, attemptCount) { const failureReasons = []; if (statusCode !== undefined && statusCode >= 400) { failureReasons.push(`status code ${statusCode}`); } if (errorCode !== undefined) { failureReasons.push(`error code ${errorCode}`); } if (failureReasons.length === 0) { failureReasons.push('unknown error'); } const requestText = requestUrl ? `Request to ${requestUrl}` : 'Request'; return (`${requestText} failed with ${failureReasons.join(', ')}.` + ` Retrying in ${delayInMs} ms... (based on ${delayReason}; attempt ${attemptCount})` + `\nFull message:\n${errorMessage}`); } class HttpClientGot { constructor(token, customHeaders, cookies = {}, proxyAgents) { this.token = token; this.customHeaders = customHeaders; this.cookies = cookies; this.proxyAgents = proxyAgents; this.headers = {}; this.urlsToRetry = []; this.initHeaders(token, customHeaders); const gotExtendOptions = this.buildGotExtendOptions(proxyAgents); this.askuiGot = got_1.default.extend(gotExtendOptions); } /** * Configures got with retry behavior for transient server errors. * * Got retries requests that fail with retryable status codes * (500, 502, 503, 504, etc.) up to `limit` times using exponential backoff. * * POST requests are only retried if their URL is registered in `urlsToRetry` * (see `shouldRetryOnError`), to avoid retrying non-idempotent calls * to unknown endpoints. */ buildGotExtendOptions(proxyAgents) { const gotExtendOptions = { retry: { calculateDelay: ({ attemptCount, retryOptions, error, retryAfter, }) => { var _a, _b, _c; if (!this.shouldRetryOnError(error)) { return 0; } if (attemptCount > retryOptions.limit) { return 0; } const hasMethod = retryOptions.methods.includes(error.options.method); const hasErrorCode = retryOptions.errorCodes.includes(error.code); const hasStatusCode = error.response && retryOptions.statusCodes.includes(error.response.statusCode); if (!hasMethod || (!hasErrorCode && !hasStatusCode)) { return 0; } if (error.response) { if (retryAfter) { if (retryOptions.maxRetryAfter === undefined || retryAfter > retryOptions.maxRetryAfter) { return 0; } lib_1.logger.debug(buildRetryLog((_a = error.request) === null || _a === void 0 ? void 0 : _a.requestUrl, error.code, error.response.statusCode, error.message, retryAfter, 'retry-after header', attemptCount)); return retryAfter; } if (error.response.statusCode === 413) { return 0; } } const baseDelayInMs = 1000; const noiseToPreventCollisions = Math.random() * 100; const delayInMs = Math.min(Math.pow(2, (attemptCount - 1)) * baseDelayInMs + noiseToPreventCollisions, Number.MAX_SAFE_INTEGER); lib_1.logger.debug(buildRetryLog((_b = error.request) === null || _b === void 0 ? void 0 : _b.requestUrl, error.code, (_c = error.response) === null || _c === void 0 ? void 0 : _c.statusCode, error.message, delayInMs, 'retry-after header', attemptCount)); return delayInMs; }, limit: 5, maxRetryAfter: 10 * 60, methods: ['POST', 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'], }, }; if (proxyAgents) { gotExtendOptions.agent = proxyAgents; } return gotExtendOptions; } /** * Only retry POST requests if the URL is explicitly registered in `urlsToRetry` * (populated by InferenceClient with inference endpoint URLs). * Non-POST requests (GET, PUT, etc.) are always retried. */ shouldRetryOnError(error) { var _a; return (((_a = error.request) === null || _a === void 0 ? void 0 : _a.options.method) !== 'POST' || this.shouldRetryPostRequest(error.request)); } shouldRetryPostRequest(request) { return (request !== undefined && this.urlsToRetry.includes(request.requestUrl)); } initHeaders(token, customHeaders = {}) { const credentials = token ? new credentials_1.Credentials(token) : undefined; this.headers = Object.assign(Object.assign({}, (credentials ? { Authorization: `Basic ${credentials === null || credentials === void 0 ? void 0 : credentials.base64Encoded}` } : {})), customHeaders); } injectHeadersAndCookies(url, options) { const cookieJar = new tough_cookie_1.CookieJar(); Object.keys(this.cookies) .map((key) => `${key}=${this.cookies[key]}`) .forEach((cookie) => { cookieJar.setCookieSync(cookie, url); }); return Object.assign(Object.assign({}, options), { cookieJar, headers: this.headers }); } post(url, data) { return __awaiter(this, void 0, void 0, function* () { // Note: We intentionally do NOT set `throwHttpErrors: false` here. // Got must throw on non-2xx responses so that its built-in retry mechanism // (configured in buildGotExtendOptions) can kick in for transient server errors // (500, 502, 503, 504). After all retries are exhausted, got throws an HTTPError // which we catch below and convert into our custom error hierarchy. const options = this.injectHeadersAndCookies(url, { json: data, responseType: 'json', }); try { const { body, headers } = yield this.askuiGot.post(url, options); if (headers['deprecation'] !== undefined) { lib_1.logger.warn(headers['deprecation']); } return { body, headers }; } catch (error) { // After got exhausts all retries, it throws HTTPError. // Convert it to our custom error types (ServerHttpClientError, // AuthenticationHttpClientError, etc.) for consistent error handling. if (error instanceof got_1.HTTPError) { throw (0, custom_errors_1.httpClientErrorHandler)(error.response.statusCode, error.response.body); } throw new unkown_http_client_error_1.UnkownHttpClientError(error instanceof Error ? error.message : String(error)); } }); } get(url_1) { return __awaiter(this, arguments, void 0, function* (url, options = { responseType: 'json' }) { const response = yield this.askuiGot.get(url, this.injectHeadersAndCookies(url, options)); return response.body; }); } } exports.HttpClientGot = HttpClientGot;