86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
interface ApiConfig {
|
|
baseUrl: string;
|
|
defaultHeaders?: Record<string, string | Promise<string | null>>;
|
|
maxRetries?: number;
|
|
initialRetryDelay?: number;
|
|
}
|
|
|
|
interface FetchOptions extends RequestInit {
|
|
retries?: number;
|
|
}
|
|
|
|
export class Api {
|
|
private baseUrl: string;
|
|
private headers: Record<string, string | Promise<string | null>>;
|
|
private maxRetries: number;
|
|
private initialRetryDelay: number;
|
|
|
|
constructor(config: ApiConfig) {
|
|
this.baseUrl = config.baseUrl;
|
|
this.headers = {
|
|
'Content-Type': 'application/json',
|
|
...config.defaultHeaders,
|
|
};
|
|
this.maxRetries = config.maxRetries ?? 3;
|
|
this.initialRetryDelay = config.initialRetryDelay ?? 500;
|
|
}
|
|
|
|
private async resolveHeaders(): Promise<Record<string, string>> {
|
|
const resolvedHeaders: Record<string, string> = {};
|
|
for (const [key, value] of Object.entries(this.headers)) {
|
|
const resolvedValue = await value;
|
|
if (resolvedValue !== null) {
|
|
resolvedHeaders[key] = resolvedValue;
|
|
}
|
|
}
|
|
return resolvedHeaders;
|
|
}
|
|
|
|
public addHeader(key: string, value: string | Promise<string | null>) {
|
|
this.headers[key] = value;
|
|
}
|
|
|
|
private async post<ReqBody, ResBody>(
|
|
url: string,
|
|
data: ReqBody,
|
|
options: FetchOptions,
|
|
attempt: number,
|
|
): Promise<ResBody | null> {
|
|
try {
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: await this.resolveHeaders(),
|
|
body: data ? JSON.stringify(data ?? {}) : undefined,
|
|
keepalive: true,
|
|
...options,
|
|
});
|
|
|
|
if (response.status === 401) return null;
|
|
|
|
if (response.status !== 200 && response.status !== 202) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseText = await response.text();
|
|
return responseText ? JSON.parse(responseText) : null;
|
|
} catch (error) {
|
|
if (attempt < this.maxRetries) {
|
|
const delay = this.initialRetryDelay * 2 ** attempt;
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
return this.post<ReqBody, ResBody>(url, data, options, attempt + 1);
|
|
}
|
|
console.error('Max retries reached:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async fetch<ReqBody, ResBody>(
|
|
path: string,
|
|
data: ReqBody,
|
|
options: FetchOptions = {},
|
|
): Promise<ResBody | null> {
|
|
const url = `${this.baseUrl}${path}`;
|
|
return this.post<ReqBody, ResBody>(url, data, options, 0);
|
|
}
|
|
}
|