83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
export async function api<T>(path: string, init: RequestInit = {}, clientInfo?: { id: string; name: string; api_name?: string } | null): Promise<T> {
|
||
const headers = new Headers(init.headers || {});
|
||
|
||
// Helper: UTF-8 → base64 (ASCII safe for HTTP headers)
|
||
const toBase64 = (s: string) => {
|
||
try {
|
||
// browser-safe utf8 → b64
|
||
return btoa(unescape(encodeURIComponent(s)));
|
||
} catch {
|
||
// Node or unexpected env – best effort fallback
|
||
if (typeof Buffer !== "undefined") return Buffer.from(s, "utf-8").toString("base64");
|
||
return s;
|
||
}
|
||
};
|
||
|
||
// Priorité 1: Utiliser clientInfo passé en paramètre (server-side)
|
||
if (clientInfo) {
|
||
const company = clientInfo.name || "";
|
||
const activeOrg = clientInfo.id || "";
|
||
|
||
if (company) {
|
||
headers.set("x-company-name", company);
|
||
headers.set("x-company-name-b64", toBase64(company));
|
||
}
|
||
if (activeOrg) {
|
||
headers.set("x-active-org-id", activeOrg);
|
||
}
|
||
} else {
|
||
// Fallback: localStorage (client-side seulement si pas de clientInfo)
|
||
let company = "";
|
||
let activeOrg = "";
|
||
if (typeof window !== "undefined") {
|
||
company = localStorage.getItem("company_name") || "";
|
||
activeOrg = localStorage.getItem("active_org_id") || "";
|
||
}
|
||
if (company) {
|
||
headers.set("x-company-name", company);
|
||
headers.set("x-company-name-b64", toBase64(company));
|
||
}
|
||
if (activeOrg && !headers.has("x-active-org-id")) {
|
||
headers.set("x-active-org-id", activeOrg);
|
||
}
|
||
}
|
||
|
||
console.log("📦 headers envoyés :", [...headers.entries()]);
|
||
|
||
// Content negotiation
|
||
headers.set("Accept", "application/json");
|
||
// Only set Content-Type automatically if a body is present and header not already set
|
||
const hasBody = !!(init as RequestInit).body;
|
||
if (hasBody && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
||
|
||
// Route interne pour certaines ressources gérées par Next.js (évite l'upstream AWS)
|
||
// Prefixes served by Next.js API (internal) instead of upstream
|
||
const INTERNAL_PREFIXES = [
|
||
"/access",
|
||
"/salaries", // all salaries endpoints are internal (Supabase)
|
||
"/spectacles", // all spectacles endpoints are internal (Supabase)
|
||
"/informations", // Vos informations: lire depuis Supabase (organization_details)
|
||
"/facturation", // Facturation: lire depuis Supabase (invoices)
|
||
"/cotisations", // Cotisations: lire depuis Supabase (monthly_contributions)
|
||
"/contrats", // Ajouté : toutes les requêtes contrats passent par l'API interne
|
||
"/staff", // Toutes les API staff (staff/facturation, staff/organizations, etc.)
|
||
];
|
||
const isInternal = INTERNAL_PREFIXES.some((p) => path.startsWith(p));
|
||
const base = process.env.NEXT_PUBLIC_API_BASE as string;
|
||
const url = isInternal ? `/api${path}` : `${base}${path}`;
|
||
console.log("🔗 api() -> URL ciblée :", url);
|
||
|
||
const res = await fetch(url, {
|
||
...init,
|
||
credentials: "include", // cookie httpOnly (supabase / session)
|
||
headers,
|
||
cache: "no-store",
|
||
});
|
||
|
||
if (!res.ok) {
|
||
// Try to surface text body for easier debugging
|
||
const text = await res.text().catch(() => "");
|
||
throw new Error(text || `API error ${res.status}`);
|
||
}
|
||
return res.json();
|
||
}
|