const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001/api/v1'; export interface AccessTokenResponse { accessToken: string; } export async function loginRequest(email: string, password: string): Promise { const response = await fetch(`${API_URL}/auth/login`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); if (!response.ok) { throw new Error('Login fehlgeschlagen. Bitte E-Mail und Passwort prüfen.'); } return response.json() as Promise; } // The refresh token is single-use and rotates on every call. If two callers // (e.g. React StrictMode's dev double-invoke, or two components hitting a 401 // at once) both call refresh at the same time, the second one always fails // because the first already rotated the cookie. Deduplicating into one // shared in-flight request makes concurrent callers share the same result // instead of racing each other. let inFlightRefresh: Promise | null = null; async function performRefresh(): Promise { const response = await fetch(`${API_URL}/auth/refresh`, { method: 'POST', credentials: 'include', }); if (!response.ok) { return null; } return response.json() as Promise; } export function refreshRequest(): Promise { if (!inFlightRefresh) { inFlightRefresh = performRefresh().finally(() => { inFlightRefresh = null; }); } return inFlightRefresh; } export async function logoutRequest(): Promise { await fetch(`${API_URL}/auth/logout`, { method: 'POST', credentials: 'include' }); } export interface CurrentUserProfile { id: string; email: string; name: string; roleKey: string; roleName: string; permissions: string[]; } export async function getCurrentUserRequest(accessToken: string): Promise { const response = await fetch(`${API_URL}/auth/me`, { credentials: 'include', headers: { Authorization: `Bearer ${accessToken}` }, }); if (!response.ok) { throw new Error('Profil konnte nicht geladen werden.'); } return response.json() as Promise; }