Amba-Agent-App/lib/api/client.ts
2026-01-16 00:22:35 +03:00

97 lines
2.6 KiB
TypeScript

import { getBaseUrl, type BaseUrlKey } from "./baseUrls";
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
export interface EndpointConfig {
base: BaseUrlKey; // e.g. "events"
path: string; // e.g. "/api/event/all"
method: HttpMethod;
}
export const createEndpoint = (
base: BaseUrlKey,
path: string,
method: HttpMethod = "GET"
): EndpointConfig => ({ base, path, method });
interface CallEndpointOptions<TBody> {
body?: TBody;
query?: Record<string, string | number | boolean | undefined>;
headers?: Record<string, string>;
}
export async function callEndpoint<TResponse = unknown, TBody = unknown>(
endpoint: EndpointConfig,
options: CallEndpointOptions<TBody> = {}
): Promise<TResponse> {
const { body, query, headers } = options;
const baseUrl = getBaseUrl(endpoint.base);
const url = buildUrl(baseUrl, endpoint.path, query);
const init: RequestInit = {
method: endpoint.method,
headers: {
"Content-Type": "application/json",
...(headers || {}),
},
};
if (body && endpoint.method !== "GET") {
// @ts-ignore body is allowed on RequestInit for fetch
init.body = JSON.stringify(body);
}
// Debug log of what we're calling (without dumping full token)
const authHeader = headers?.Authorization || "";
const shortToken = authHeader.startsWith("Bearer ")
? authHeader.slice(7, 27) + "..."
: undefined;
// eslint-disable-next-line no-console
console.log("[api] request", {
url,
method: endpoint.method,
hasAuth: !!authHeader,
tokenPrefix: shortToken,
});
const res = await fetch(url, init as any);
if (!res.ok) {
const text = await res.text().catch(() => "");
// eslint-disable-next-line no-console
console.log("[api] error response", {
url,
status: res.status,
body: text,
});
throw new Error(text || `Request failed with status ${res.status}`);
}
const contentType = res.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
return (await res.json()) as TResponse;
}
return (await res.text()) as unknown as TResponse;
}
function buildUrl(
baseUrl: string,
path: string,
query?: Record<string, string | number | boolean | undefined>
): string {
const trimmedBase = baseUrl.replace(/\/$/, "");
const trimmedPath = path.startsWith("/") ? path : `/${path}`;
const url = new URL(trimmedBase + trimmedPath);
if (query) {
Object.entries(query).forEach(([key, value]) => {
if (value === undefined) return;
url.searchParams.set(key, String(value));
});
}
return url.toString();
}