diff --git a/VERSION b/VERSION index 860487c..37c2961 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.1 +2.7.2 diff --git a/play-life-web/src/components/auth/AuthContext.jsx b/play-life-web/src/components/auth/AuthContext.jsx index cc5d99e..f43af7d 100644 --- a/play-life-web/src/components/auth/AuthContext.jsx +++ b/play-life-web/src/components/auth/AuthContext.jsx @@ -11,6 +11,63 @@ export function AuthProvider({ children }) { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const logout = useCallback(async () => { + const token = localStorage.getItem(TOKEN_KEY) + + if (token) { + try { + await fetch('/api/auth/logout', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }) + } catch (err) { + console.error('Logout error:', err) + } + } + + localStorage.removeItem(TOKEN_KEY) + localStorage.removeItem(REFRESH_TOKEN_KEY) + localStorage.removeItem(USER_KEY) + setUser(null) + }, []) + + const refreshToken = useCallback(async () => { + const refresh = localStorage.getItem(REFRESH_TOKEN_KEY) + + if (!refresh) { + return false + } + + try { + const response = await fetch('/api/auth/refresh', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ refresh_token: refresh }) + }) + + if (!response.ok) { + return false + } + + const data = await response.json() + + localStorage.setItem(TOKEN_KEY, data.access_token) + localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token) + localStorage.setItem(USER_KEY, JSON.stringify(data.user)) + setUser(data.user) + + return true + } catch (err) { + console.error('Refresh token error:', err) + return false + } + }, []) + // Initialize from localStorage useEffect(() => { const initAuth = async () => { @@ -20,14 +77,20 @@ export function AuthProvider({ children }) { if (token && savedUser) { try { setUser(JSON.parse(savedUser)) - // Verify token is still valid + // Verify token is still valid with timeout + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout + const response = await fetch('/api/auth/me', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' - } + }, + signal: controller.signal }) + clearTimeout(timeoutId) + if (response.ok) { const data = await response.json() setUser(data.user) @@ -38,17 +101,32 @@ export function AuthProvider({ children }) { if (!refreshed) { logout() } + } else { + // For other errors (like 503, 502, network errors), don't clear auth + // Just log the error and keep the user logged in + console.warn('Auth check failed with status:', response.status, 'but keeping session') } } catch (err) { - console.error('Auth init error:', err) - logout() + // Network errors (e.g., backend not ready) should not clear auth + // Only clear if it's a real auth error + if (err.name === 'AbortError') { + // Timeout - backend might be starting up, keep auth state + console.warn('Auth check timeout, backend might be starting up. Keeping session.') + } else if (err.name === 'TypeError' && (err.message.includes('fetch') || err.message.includes('Failed to fetch'))) { + // Network error - backend might be starting up, keep auth state + console.warn('Network error during auth check, keeping session:', err.message) + } else { + // Other errors - might be auth related + console.error('Auth init error:', err) + // Don't automatically logout on unknown errors + } } } setLoading(false) } initAuth() - }, []) + }, [refreshToken, logout]) const login = useCallback(async (email, password) => { setError(null) @@ -108,63 +186,6 @@ export function AuthProvider({ children }) { } }, []) - const logout = useCallback(async () => { - const token = localStorage.getItem(TOKEN_KEY) - - if (token) { - try { - await fetch('/api/auth/logout', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }) - } catch (err) { - console.error('Logout error:', err) - } - } - - localStorage.removeItem(TOKEN_KEY) - localStorage.removeItem(REFRESH_TOKEN_KEY) - localStorage.removeItem(USER_KEY) - setUser(null) - }, []) - - const refreshToken = useCallback(async () => { - const refresh = localStorage.getItem(REFRESH_TOKEN_KEY) - - if (!refresh) { - return false - } - - try { - const response = await fetch('/api/auth/refresh', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ refresh_token: refresh }) - }) - - if (!response.ok) { - return false - } - - const data = await response.json() - - localStorage.setItem(TOKEN_KEY, data.access_token) - localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token) - localStorage.setItem(USER_KEY, JSON.stringify(data.user)) - setUser(data.user) - - return true - } catch (err) { - console.error('Refresh token error:', err) - return false - } - }, []) - const getToken = useCallback(() => { return localStorage.getItem(TOKEN_KEY) }, []) @@ -182,21 +203,28 @@ export function AuthProvider({ children }) { headers['Authorization'] = `Bearer ${token}` } - let response = await fetch(url, { ...options, headers }) - - // If 401, try to refresh token and retry - if (response.status === 401) { - const refreshed = await refreshToken() - if (refreshed) { - const newToken = localStorage.getItem(TOKEN_KEY) - headers['Authorization'] = `Bearer ${newToken}` - response = await fetch(url, { ...options, headers }) - } else { - logout() + try { + let response = await fetch(url, { ...options, headers }) + + // If 401, try to refresh token and retry + if (response.status === 401) { + const refreshed = await refreshToken() + if (refreshed) { + const newToken = localStorage.getItem(TOKEN_KEY) + headers['Authorization'] = `Bearer ${newToken}` + response = await fetch(url, { ...options, headers }) + } else { + // Only logout if refresh failed (real auth error) + logout() + } } + + return response + } catch (err) { + // Network errors should not trigger logout + // Let the caller handle the error + throw err } - - return response }, [refreshToken, logout]) const value = {