import { UppzyApiError, isRetryableStatus } from "./uppzy-errors.js";
const BASE_URL = process.env.UPPZY_BASE_URL || "https://api.uppzy.com/api/v1";
const API_KEY = process.env.UPPZY_API_KEY;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function backoffDelay(attempt) {
const baseMs = 300;
const maxMs = 4000;
const jitterMs = Math.floor(Math.random() * 250);
return Math.min(maxMs, baseMs * 2 ** attempt) + jitterMs;
}
export async function uppzyRequest(path, {
method = "GET",
body,
timeoutMs = 15000,
maxRetries = 2,
} = {}) {
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(`${BASE_URL}${path}`, {
method,
signal: controller.signal,
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
const text = await response.text();
const data = text ? JSON.parse(text) : null;
if (response.ok) {
return data;
}
const retryable = isRetryableStatus(response.status);
if (!retryable || attempt === maxRetries) {
throw new UppzyApiError(`Uppzy API returned HTTP ${response.status}`, {
status: response.status,
endpoint: path,
details: data,
retryable,
});
}
await sleep(backoffDelay(attempt));
} catch (error) {
if (error instanceof UppzyApiError) {
throw error;
}
const isAbort = error.name === "AbortError";
const canRetry = isAbort && attempt < maxRetries;
if (!canRetry) {
throw new UppzyApiError(isAbort ? "Uppzy API request timed out" : "Uppzy API request failed", {
status: null,
endpoint: path,
details: null,
retryable: isAbort,
});
}
await sleep(backoffDelay(attempt));
} finally {
clearTimeout(timeout);
}
}
}