const API_BASE = '/api' // Global callback for 401 responses (set by app component to trigger auto-logout) let onUnauthorized = null export function setOnUnauthorized(callback) { onUnauthorized = callback } function getToken() { return localStorage.getItem('token') } function authHeaders() { const token = getToken() return token ? { Authorization: `Bearer ${token}` } : {} } async function request(method, path, body) { const opts = { method, headers: { 'Content-Type': 'application/json', ...authHeaders(), }, } if (body) { opts.body = JSON.stringify(body) } const res = await fetch(`${API_BASE}${path}`, opts) if (!res.ok) { // Auto-logout on 401 for any authenticated request (not login/register) if (res.status === 401 && path !== '/auth/login' && path !== '/auth/register') { if (onUnauthorized) onUnauthorized() throw new Error('Session expired — please log in again') } const text = await res.text() const err = new Error(text || `HTTP ${res.status}`) err.status = res.status throw err } if (res.status === 204 || res.headers.get('content-length') === '0') { return null } return res.json() } export const api = { // Auth register: (data) => request('POST', '/auth/register', data), login: (data) => request('POST', '/auth/login', data), me: () => request('GET', '/auth/me'), // Profile updateProfile: (data) => request('PUT', '/auth/profile', data), uploadAvatar: async (file) => { const formData = new FormData() formData.append('avatar', file) const res = await fetch(`${API_BASE}/auth/avatar`, { method: 'POST', headers: authHeaders(), body: formData, }) if (!res.ok) { const text = await res.text() throw new Error(text || `HTTP ${res.status}`) } return res.json() }, deleteAvatar: () => request('DELETE', '/auth/avatar'), // Rooms listRooms: () => request('GET', '/rooms'), createRoom: (data) => request('POST', '/rooms', data), getRoom: (roomId) => request('GET', `/rooms/${roomId}`), getMessages: (roomId, limit = 50, before) => { const params = new URLSearchParams({ limit: String(limit) }) if (before) params.set('before', before) return request('GET', `/rooms/${roomId}/messages?${params}`) }, joinRoom: (roomId) => request('POST', `/rooms/${roomId}/join`), deleteRoom: (roomId) => request('DELETE', `/rooms/${roomId}`), clearRoom: (roomId) => request('POST', `/rooms/${roomId}/clear`), // Models listModels: () => request('GET', '/models'), // Upload (chat images) uploadChatImage: async (file) => { const formData = new FormData() formData.append('image', file) const res = await fetch(`${API_BASE}/upload`, { method: 'POST', headers: authHeaders(), body: formData, }) if (!res.ok) { const text = await res.text() throw new Error(text || `HTTP ${res.status}`) } return res.json() }, // Invites createInvite: (data) => request('POST', '/invites', data), acceptInvite: (token) => request('POST', `/invites/${token}/accept`), } export function saveAuth(token, user) { localStorage.setItem('token', token) localStorage.setItem('user', JSON.stringify(user)) } export function getUser() { const raw = localStorage.getItem('user') return raw ? JSON.parse(raw) : null } export function clearAuth() { localStorage.removeItem('token') localStorage.removeItem('user') } export function isAuthenticated() { return !!getToken() }