Files
stats/packages/sdks/sdk/src/api.ts
Carl-Gerhard Lindesvärd 32415d31d9 fix: fetch device id
2025-11-25 10:24:11 +01:00

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);
}
}