import { getDeviceId } from '../lib/device'; function getCsrfToken(): string | null { // Method 1: Meta tag (preferred for SPA) const metaToken = document.querySelector('meta[name="csrf-token"]'); if (metaToken) { return (metaToken as HTMLMetaElement).getAttribute('content') || null; } // Method 2: XSRF-TOKEN cookie (Sanctum fallback) const name = 'XSRF-TOKEN='; const decodedCookie = decodeURIComponent(document.cookie); const ca = decodedCookie.split(';'); for (let i = 0; i < ca.length; i++) { let c = ca[i].trimStart(); if (c.startsWith(name)) { const token = c.substring(name.length); try { // Decode base64 if needed return decodeURIComponent(atob(token)); } catch { return token; } } } console.warn('No CSRF token found - API requests may fail'); return null; } function getCsrfHeaders(): Record { const token = getCsrfToken(); const headers: Record = { 'X-Device-Id': getDeviceId(), 'Accept': 'application/json', }; if (token) { headers['X-CSRF-TOKEN'] = token; headers['X-XSRF-TOKEN'] = token; } return headers; } export async function likePhoto(id: number): Promise { const headers = getCsrfHeaders(); const res = await fetch(`/api/v1/photos/${id}/like`, { method: 'POST', credentials: 'include', headers: { ...headers, 'Content-Type': 'application/json', }, }); if (!res.ok) { const errorText = await res.text(); if (res.status === 419) { throw new Error('CSRF Token mismatch. This usually means:\n\n' + '1. The page needs to be refreshed\n' + '2. Check if is present in HTML source\n' + '3. API routes might need CSRF exemption in VerifyCsrfToken middleware'); } throw new Error(`Like failed: ${res.status} - ${errorText}`); } const json = await res.json(); return json.likes_count ?? json.data?.likes_count ?? 0; } export async function uploadPhoto(slug: string, file: File, taskId?: number, emotionSlug?: string): Promise { const formData = new FormData(); formData.append('photo', file, `photo-${Date.now()}.jpg`); if (taskId) formData.append('task_id', taskId.toString()); if (emotionSlug) formData.append('emotion_slug', emotionSlug); formData.append('device_id', getDeviceId()); const res = await fetch(`/api/v1/events/${slug}/upload`, { method: 'POST', credentials: 'include', body: formData, // Don't set Content-Type for FormData - let browser handle it with boundary }); if (!res.ok) { const errorText = await res.text(); if (res.status === 419) { throw new Error('CSRF Token mismatch during upload.\n\n' + 'This usually means:\n' + '1. API routes need CSRF exemption in VerifyCsrfToken middleware\n' + '2. Check if is present in page source\n' + '3. The page might need to be refreshed'); } throw new Error(`Upload failed: ${res.status} - ${errorText}`); } const json = await res.json(); return json.photo_id ?? json.id ?? json.data?.id ?? 0; }