diff --git a/src/index.ts b/src/index.ts index 64d5965..6ea6430 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import { Configuration, DefaultApi as InfisicalApi } from "./infisicalapi_client import { DefaultApiApiV1DynamicSecretsLeasesPostRequest } from "./infisicalapi_client"; import SecretsClient from "./custom/secrets"; import AuthClient from "./custom/auth"; -import { RawAxiosRequestConfig } from "axios"; +import axios, { AxiosError, AxiosInstance, RawAxiosRequestConfig } from "axios"; import DynamicSecretsClient from "./custom/dynamic-secrets"; import * as ApiClient from "./infisicalapi_client"; @@ -10,6 +10,12 @@ import EnvironmentsClient from "./custom/environments"; import ProjectsClient from "./custom/projects"; import FoldersClient from "./custom/folders"; +declare module "axios" { + interface AxiosRequestConfig { + _retryCount?: number; + } +} + const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosRequestConfig) => { return { // Add more as we go @@ -18,6 +24,42 @@ const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosReque }; }; +const setupAxiosRetry = () => { + const axiosInstance = axios.create(); + const maxRetries = 4; + + const initialRetryDelay = 1000; + const backoffFactor = 2; + + axiosInstance.interceptors.response.use(null, (error: AxiosError) => { + const config = error?.config; + + if (!config) { + return Promise.reject(error); + } + + if (!config._retryCount) config._retryCount = 0; + + // handle rate limits and network errors + if ((error.response?.status === 429 || error.response?.status === undefined) && config && config._retryCount! < maxRetries) { + config._retryCount!++; + const baseDelay = initialRetryDelay * Math.pow(backoffFactor, config._retryCount! - 1); + const jitter = baseDelay * 0.2; // 20% +/- jitter + const exponentialDelay = baseDelay + (Math.random() * 2 - 1) * jitter; + + return new Promise(resolve => { + setTimeout(() => { + resolve(axiosInstance(config)); + }, exponentialDelay); + }); + } + + return Promise.reject(error); + }); + + return axiosInstance; +}; + // We need to do bind(this) because the authenticate method is a private method, and usually you can't call private methods from outside the class. type InfisicalSDKOptions = { siteUrl?: string; @@ -34,14 +76,18 @@ class InfisicalSDK { #foldersClient: FoldersClient; #authClient: AuthClient; #basePath: string; + axiosInstance: AxiosInstance; constructor(options?: InfisicalSDKOptions) { this.#basePath = options?.siteUrl || "https://app.infisical.com"; + this.axiosInstance = setupAxiosRetry(); this.#apiInstance = new InfisicalApi( new Configuration({ basePath: this.#basePath - }) + }), + undefined, + this.axiosInstance ); this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance); @@ -58,7 +104,9 @@ class InfisicalSDK { new Configuration({ basePath: this.#basePath, accessToken - }) + }), + undefined, + this.axiosInstance ); this.#requestOptions = {