// CSRF Token Management let csrfToken = null; let tokenPromise = null; // Get CSRF token from server (force refresh if needed) async function getCsrfToken(forceRefresh = false) { // If forcing refresh, clear existing token if (forceRefresh) { csrfToken = null; tokenPromise = null; } // Return existing token if available and not forcing refresh if (csrfToken && !forceRefresh) { return csrfToken; } // If a request is already in progress, wait for it if (tokenPromise) { return tokenPromise; } // Fetch new token tokenPromise = (async () => { try { const response = await fetch('/api/csrf-token', { credentials: 'include' // Include cookies }); const data = await response.json(); if (data.success) { csrfToken = data.csrfToken; tokenPromise = null; return csrfToken; } } catch (error) { console.error('Error fetching CSRF token:', error); tokenPromise = null; } return null; })(); return tokenPromise; } // Refresh CSRF token (alias for force refresh) async function refreshCsrfToken() { return await getCsrfToken(true); } // Add CSRF token to fetch request async function fetchWithCsrf(url, options = {}) { const method = options.method || 'GET'; const stateChangingMethods = ['POST', 'PUT', 'PATCH', 'DELETE']; // For state-changing requests, always get a fresh token first if (stateChangingMethods.includes(method.toUpperCase())) { // Always refresh token before state-changing requests // This ensures we have a valid token even if the previous one was invalidated await refreshCsrfToken(); } else if (!csrfToken) { // For GET requests, only fetch if we don't have a token await getCsrfToken(); } // Add CSRF token to headers const headers = { ...options.headers, 'X-CSRF-Token': csrfToken || '' }; const response = await fetch(url, { ...options, headers, credentials: 'include' // Include cookies }); // After state-changing requests, always refresh the token // The server invalidates the used token, so we need a new one for the next request if (stateChangingMethods.includes(method.toUpperCase())) { // Try to get new token from response header (server sends it) const newToken = response.headers.get('X-New-CSRF-Token'); if (newToken) { csrfToken = newToken; } else { // If no header (or header not accessible), refresh token immediately // This ensures we always have a fresh token for the next request try { await refreshCsrfToken(); } catch (err) { console.error('Error refreshing CSRF token:', err); } } } return response; } // Initialize CSRF token on page load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', async () => { await getCsrfToken(); }); } else { // DOM already loaded getCsrfToken(); } // Make functions globally available window.getCsrfToken = getCsrfToken; window.refreshCsrfToken = refreshCsrfToken; window.fetchWithCsrf = fetchWithCsrf;