Skip to content

Commit 75ce3ef

Browse files
Merge pull request #18 from Infisical/daniel/retry-on-error
feat: retry network errors and rate limits
2 parents 5261fbb + 64fde98 commit 75ce3ef

File tree

1 file changed

+51
-3
lines changed

1 file changed

+51
-3
lines changed

src/index.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@ import { Configuration, DefaultApi as InfisicalApi } from "./infisicalapi_client
22
import { DefaultApiApiV1DynamicSecretsLeasesPostRequest } from "./infisicalapi_client";
33
import SecretsClient from "./custom/secrets";
44
import AuthClient from "./custom/auth";
5-
import { RawAxiosRequestConfig } from "axios";
5+
import axios, { AxiosError, AxiosInstance, RawAxiosRequestConfig } from "axios";
66
import DynamicSecretsClient from "./custom/dynamic-secrets";
77

88
import * as ApiClient from "./infisicalapi_client";
99
import EnvironmentsClient from "./custom/environments";
1010
import ProjectsClient from "./custom/projects";
1111
import FoldersClient from "./custom/folders";
1212

13+
declare module "axios" {
14+
interface AxiosRequestConfig {
15+
_retryCount?: number;
16+
}
17+
}
18+
1319
const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosRequestConfig) => {
1420
return {
1521
// Add more as we go
@@ -18,6 +24,42 @@ const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosReque
1824
};
1925
};
2026

27+
const setupAxiosRetry = () => {
28+
const axiosInstance = axios.create();
29+
const maxRetries = 4;
30+
31+
const initialRetryDelay = 1000;
32+
const backoffFactor = 2;
33+
34+
axiosInstance.interceptors.response.use(null, (error: AxiosError) => {
35+
const config = error?.config;
36+
37+
if (!config) {
38+
return Promise.reject(error);
39+
}
40+
41+
if (!config._retryCount) config._retryCount = 0;
42+
43+
// handle rate limits and network errors
44+
if ((error.response?.status === 429 || error.response?.status === undefined) && config && config._retryCount! < maxRetries) {
45+
config._retryCount!++;
46+
const baseDelay = initialRetryDelay * Math.pow(backoffFactor, config._retryCount! - 1);
47+
const jitter = baseDelay * 0.2; // 20% +/- jitter
48+
const exponentialDelay = baseDelay + (Math.random() * 2 - 1) * jitter;
49+
50+
return new Promise(resolve => {
51+
setTimeout(() => {
52+
resolve(axiosInstance(config));
53+
}, exponentialDelay);
54+
});
55+
}
56+
57+
return Promise.reject(error);
58+
});
59+
60+
return axiosInstance;
61+
};
62+
2163
// 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.
2264
type InfisicalSDKOptions = {
2365
siteUrl?: string;
@@ -34,14 +76,18 @@ class InfisicalSDK {
3476
#foldersClient: FoldersClient;
3577
#authClient: AuthClient;
3678
#basePath: string;
79+
axiosInstance: AxiosInstance;
3780

3881
constructor(options?: InfisicalSDKOptions) {
3982
this.#basePath = options?.siteUrl || "https://app.infisical.com";
83+
this.axiosInstance = setupAxiosRetry();
4084

4185
this.#apiInstance = new InfisicalApi(
4286
new Configuration({
4387
basePath: this.#basePath
44-
})
88+
}),
89+
undefined,
90+
this.axiosInstance
4591
);
4692

4793
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance);
@@ -58,7 +104,9 @@ class InfisicalSDK {
58104
new Configuration({
59105
basePath: this.#basePath,
60106
accessToken
61-
})
107+
}),
108+
undefined,
109+
this.axiosInstance
62110
);
63111

64112
this.#requestOptions = {

0 commit comments

Comments
 (0)