const AUTH_STATE_COOKIE = 'magent_logged_in' export const getApiBase = () => process.env.NEXT_PUBLIC_API_BASE ?? '/api' const setCookie = (name: string, value: string, maxAgeSeconds: number) => { if (typeof document === 'undefined') return document.cookie = `${name}=${value}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax` } const clearCookie = (name: string) => { if (typeof document === 'undefined') return document.cookie = `${name}=; Max-Age=0; Path=/; SameSite=Lax` } export const getToken = () => { if (typeof document === 'undefined') return null const cookies = document.cookie.split(';').map((entry) => entry.trim()) const marker = cookies.find((entry) => entry.startsWith(`${AUTH_STATE_COOKIE}=`)) if (!marker) return null const [, value] = marker.split('=', 2) return value || null } export const setToken = (_token: string) => { setCookie(AUTH_STATE_COOKIE, '1', 60 * 60 * 12) } export const clearToken = () => { clearCookie(AUTH_STATE_COOKIE) if (typeof window === 'undefined') return const baseUrl = getApiBase() void fetch(`${baseUrl}/auth/logout`, { method: 'POST', credentials: 'include', keepalive: true, }).catch(() => undefined) } export const logout = async () => { const baseUrl = getApiBase() clearCookie(AUTH_STATE_COOKIE) await fetch(`${baseUrl}/auth/logout`, { method: 'POST', credentials: 'include', }) } export const authFetch = (input: RequestInfo | URL, init?: RequestInit) => { const headers = new Headers(init?.headers || {}) return fetch(input, { ...init, headers, credentials: 'include' }) } export const getEventStreamToken = async () => { const baseUrl = getApiBase() const response = await authFetch(`${baseUrl}/auth/stream-token`) if (!response.ok) { const text = await response.text() throw new Error(text || `Stream token request failed: ${response.status}`) } const data = await response.json() const token = typeof data?.stream_token === 'string' ? data.stream_token : '' if (!token) { throw new Error('Stream token not returned') } return token } export class UnauthorizedError extends Error { constructor() { super('Unauthorized') this.name = 'UnauthorizedError' } } export class ForbiddenError extends Error { constructor() { super('Forbidden') this.name = 'ForbiddenError' } } export const authFetchOrThrow = async (input: RequestInfo | URL, init?: RequestInit) => { const response = await authFetch(input, init) if (response.status === 401) { clearToken() throw new UnauthorizedError() } if (response.status === 403) { throw new ForbiddenError() } return response } export const readResponseText = async (response: Response) => { try { return (await response.text()).trim() } catch { return '' } }