"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StandardRetryStrategy = void 0; const protocol_http_1 = require("@aws-sdk/protocol-http"); const service_error_classification_1 = require("@aws-sdk/service-error-classification"); const uuid_1 = require("uuid"); const config_1 = require("./config"); const constants_1 = require("./constants"); const defaultRetryQuota_1 = require("./defaultRetryQuota"); const delayDecider_1 = require("./delayDecider"); const retryDecider_1 = require("./retryDecider"); class StandardRetryStrategy { constructor(maxAttemptsProvider, options) { var _a, _b, _c; this.maxAttemptsProvider = maxAttemptsProvider; this.mode = config_1.RETRY_MODES.STANDARD; this.retryDecider = (_a = options === null || options === void 0 ? void 0 : options.retryDecider) !== null && _a !== void 0 ? _a : retryDecider_1.defaultRetryDecider; this.delayDecider = (_b = options === null || options === void 0 ? void 0 : options.delayDecider) !== null && _b !== void 0 ? _b : delayDecider_1.defaultDelayDecider; this.retryQuota = (_c = options === null || options === void 0 ? void 0 : options.retryQuota) !== null && _c !== void 0 ? _c : (0, defaultRetryQuota_1.getDefaultRetryQuota)(constants_1.INITIAL_RETRY_TOKENS); } shouldRetry(error, attempts, maxAttempts) { return attempts < maxAttempts && this.retryDecider(error) && this.retryQuota.hasRetryTokens(error); } async getMaxAttempts() { let maxAttempts; try { maxAttempts = await this.maxAttemptsProvider(); } catch (error) { maxAttempts = config_1.DEFAULT_MAX_ATTEMPTS; } return maxAttempts; } async retry(next, args, options) { let retryTokenAmount; let attempts = 0; let totalDelay = 0; const maxAttempts = await this.getMaxAttempts(); const { request } = args; if (protocol_http_1.HttpRequest.isInstance(request)) { request.headers[constants_1.INVOCATION_ID_HEADER] = (0, uuid_1.v4)(); } while (true) { try { if (protocol_http_1.HttpRequest.isInstance(request)) { request.headers[constants_1.REQUEST_HEADER] = `attempt=${attempts + 1}; max=${maxAttempts}`; } if (options === null || options === void 0 ? void 0 : options.beforeRequest) { await options.beforeRequest(); } const { response, output } = await next(args); if (options === null || options === void 0 ? void 0 : options.afterRequest) { options.afterRequest(response); } this.retryQuota.releaseRetryTokens(retryTokenAmount); output.$metadata.attempts = attempts + 1; output.$metadata.totalRetryDelay = totalDelay; return { response, output }; } catch (e) { const err = asSdkError(e); attempts++; if (this.shouldRetry(err, attempts, maxAttempts)) { retryTokenAmount = this.retryQuota.retrieveRetryTokens(err); const delayFromDecider = this.delayDecider((0, service_error_classification_1.isThrottlingError)(err) ? constants_1.THROTTLING_RETRY_DELAY_BASE : constants_1.DEFAULT_RETRY_DELAY_BASE, attempts); const delayFromResponse = getDelayFromRetryAfterHeader(err.$response); const delay = Math.max(delayFromResponse || 0, delayFromDecider); totalDelay += delay; await new Promise((resolve) => setTimeout(resolve, delay)); continue; } if (!err.$metadata) { err.$metadata = {}; } err.$metadata.attempts = attempts; err.$metadata.totalRetryDelay = totalDelay; throw err; } } } } exports.StandardRetryStrategy = StandardRetryStrategy; const getDelayFromRetryAfterHeader = (response) => { if (!protocol_http_1.HttpResponse.isInstance(response)) return; const retryAfterHeaderName = Object.keys(response.headers).find((key) => key.toLowerCase() === "retry-after"); if (!retryAfterHeaderName) return; const retryAfter = response.headers[retryAfterHeaderName]; const retryAfterSeconds = Number(retryAfter); if (!Number.isNaN(retryAfterSeconds)) return retryAfterSeconds * 1000; const retryAfterDate = new Date(retryAfter); return retryAfterDate.getTime() - Date.now(); }; const asSdkError = (error) => { if (error instanceof Error) return error; if (error instanceof Object) return Object.assign(new Error(), error); if (typeof error === "string") return new Error(error); return new Error(`AWS SDK error wrapper for ${error}`); };