import { UppzyApiError, isRetryableStatus } from "./errors";
type RequestOptions = {
method?: string;
body?: unknown;
timeoutMs?: number;
maxRetries?: number;
};
export class UppzyClient {
constructor(
private readonly baseUrl: string,
private readonly apiKey: string,
private readonly siteId: string,
) {}
private async request<T>(path: string, {
method = "GET",
body,
timeoutMs = 15000,
maxRetries = 2,
}: RequestOptions = {}): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(`${this.baseUrl}${path}`, {
method,
signal: controller.signal,
headers: {
"X-API-Key": this.apiKey,
"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 as T;
}
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 this.sleep(this.backoff(attempt));
} catch (error: any) {
if (error instanceof UppzyApiError) {
throw error;
}
const isAbort = error?.name === "AbortError";
if (!isAbort || attempt === maxRetries) {
throw new UppzyApiError(isAbort ? "Uppzy API request timed out" : "Uppzy API request failed", {
status: null,
endpoint: path,
details: null,
retryable: isAbort,
});
}
await this.sleep(this.backoff(attempt));
} finally {
clearTimeout(timeout);
}
}
throw new Error("Unexpected Uppzy client state");
}
private backoff(attempt: number): number {
const jitter = Math.floor(Math.random() * 250);
return Math.min(4000, 300 * 2 ** attempt) + jitter;
}
private sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
sendSyncChat(payload: {
session_id: string;
message: string;
email?: string;
ui_locale?: string;
response_language?: string;
}) {
return this.request<UppzyChatResponse>(`/m2m/sites/${this.siteId}/chat`, {
method: "POST",
body: payload,
});
}
sendAsyncChat(payload: {
session_id: string;
message: string;
response_language?: string;
}) {
return this.request<UppzyAsyncResponse>(`/m2m/sites/${this.siteId}/chat/async`, {
method: "POST",
body: payload,
});
}
getAsyncChatStatus(requestId: string) {
return this.request<UppzyAsyncStatusResponse>(`/m2m/sites/${this.siteId}/chat/requests/${requestId}`);
}
createTextDocument(payload: {
title: string;
content: string;
category?: string;
}) {
return this.request(`/m2m/sites/${this.siteId}/documents/text`, {
method: "POST",
body: payload,
});
}
createQaDocument(payload: {
title: string;
question: string;
answer: string;
category?: string;
}) {
return this.request(`/m2m/sites/${this.siteId}/documents/qa`, {
method: "POST",
body: payload,
});
}
submitFeedback(payload: {
session_id: string;
request_id: string;
feedback: "good" | "bad";
}) {
return this.request(`/m2m/sites/${this.siteId}/chat/feedback`, {
method: "POST",
body: payload,
});
}
closeSession(sessionId: string) {
return this.request(`/m2m/sites/${this.siteId}/chat/session/close`, {
method: "POST",
body: { session_id: sessionId },
});
}
getStatistics() {
return this.request(`/m2m/sites/${this.siteId}/statistics`);
}
getSessions() {
return this.request(`/m2m/sites/${this.siteId}/sessions`);
}
}