Merge pull request #18 from Infisical/daniel/retry-on-error

feat: retry network errors and rate limits
This commit is contained in:
Daniel Hougaard
2025-04-17 00:21:49 +04:00
committed by GitHub

View File

@@ -2,7 +2,7 @@ import { Configuration, DefaultApi as InfisicalApi } from "./infisicalapi_client
import { DefaultApiApiV1DynamicSecretsLeasesPostRequest } from "./infisicalapi_client"; import { DefaultApiApiV1DynamicSecretsLeasesPostRequest } from "./infisicalapi_client";
import SecretsClient from "./custom/secrets"; import SecretsClient from "./custom/secrets";
import AuthClient from "./custom/auth"; import AuthClient from "./custom/auth";
import { RawAxiosRequestConfig } from "axios"; import axios, { AxiosError, AxiosInstance, RawAxiosRequestConfig } from "axios";
import DynamicSecretsClient from "./custom/dynamic-secrets"; import DynamicSecretsClient from "./custom/dynamic-secrets";
import * as ApiClient from "./infisicalapi_client"; import * as ApiClient from "./infisicalapi_client";
@@ -10,6 +10,12 @@ import EnvironmentsClient from "./custom/environments";
import ProjectsClient from "./custom/projects"; import ProjectsClient from "./custom/projects";
import FoldersClient from "./custom/folders"; import FoldersClient from "./custom/folders";
declare module "axios" {
interface AxiosRequestConfig {
_retryCount?: number;
}
}
const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosRequestConfig) => { const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosRequestConfig) => {
return { return {
// Add more as we go // 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. // 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 = { type InfisicalSDKOptions = {
siteUrl?: string; siteUrl?: string;
@@ -34,14 +76,18 @@ class InfisicalSDK {
#foldersClient: FoldersClient; #foldersClient: FoldersClient;
#authClient: AuthClient; #authClient: AuthClient;
#basePath: string; #basePath: string;
axiosInstance: AxiosInstance;
constructor(options?: InfisicalSDKOptions) { constructor(options?: InfisicalSDKOptions) {
this.#basePath = options?.siteUrl || "https://app.infisical.com"; this.#basePath = options?.siteUrl || "https://app.infisical.com";
this.axiosInstance = setupAxiosRetry();
this.#apiInstance = new InfisicalApi( this.#apiInstance = new InfisicalApi(
new Configuration({ new Configuration({
basePath: this.#basePath basePath: this.#basePath
}) }),
undefined,
this.axiosInstance
); );
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance); this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance);
@@ -58,7 +104,9 @@ class InfisicalSDK {
new Configuration({ new Configuration({
basePath: this.#basePath, basePath: this.#basePath,
accessToken accessToken
}) }),
undefined,
this.axiosInstance
); );
this.#requestOptions = { this.#requestOptions = {