import { useState, useEffect, useCallback } from 'react' import { useLocation } from 'react-router-dom' import { useAuth } from '@app/auth/UseSession' import { signInAnonymously, supabase } from '@app/auth/supabase' import { isAuthRoute, isHomeRoute, isToolRoute } from '@app/utils/pathUtils' interface AutoAnonymousAuthState { isAutoAuthenticating: boolean autoAuthError: string | null shouldTriggerAutoAuth: boolean } /** * Automatically signs in users anonymously on direct tool access when not authenticated. */ export function useAutoAnonymousAuth() { const { session, loading } = useAuth() const location = useLocation() const [state, setState] = useState({ isAutoAuthenticating: false, autoAuthError: null, shouldTriggerAutoAuth: false }) const shouldAutoAuthenticate = useCallback((): boolean => { const currentPath = location.pathname if (isAuthRoute(currentPath)) return false if (isHomeRoute(currentPath)) return false return isToolRoute(currentPath) }, [location.pathname]) const waitForToken = useCallback(async (timeoutMs = 7000) => { // Prefer event-driven approach; fallback to polling if needed let resolved = false const done = (ok: boolean) => { resolved = true return ok } const hasToken = async () => { const { data: { session } } = await supabase.auth.getSession() return !!session?.access_token } if (await hasToken()) return true const unsub = supabase.auth.onAuthStateChange(async (_evt, _session) => { if (!resolved && (await hasToken())) { unsub.data.subscription.unsubscribe() done(true) } }) const started = Date.now() while (!resolved && Date.now() - started < timeoutMs) { if (await hasToken()) { unsub.data.subscription.unsubscribe() return done(true) } // gentle backoff await new Promise(r => setTimeout(r, 120)) } try { unsub.data.subscription.unsubscribe() } catch (err) { // Ignore unsubscribe errors during cleanup console.debug('[useAutoAnonymousAuth] Unsubscribe cleanup error:', err) } return done(false) }, []) const triggerAnonymousAuth = useCallback(async () => { if (state.isAutoAuthenticating) return setState(prev => ({ ...prev, isAutoAuthenticating: true, autoAuthError: null })) try { console.log('[useAutoAnonymousAuth] anonymous auth starting') const { error } = await signInAnonymously() if (error) throw error // Wait for a usable token so first API calls won't 401/redirect const ok = await waitForToken(7000) if (!ok) { throw new Error('Timed out waiting for anonymous session token') } console.log('[useAutoAnonymousAuth] anonymous auth complete') setState(prev => ({ ...prev, isAutoAuthenticating: false, shouldTriggerAutoAuth: false })) } catch (e) { console.error('[useAutoAnonymousAuth] anonymous auth failed', e) setState(prev => ({ ...prev, isAutoAuthenticating: false, autoAuthError: e instanceof Error ? e.message : 'Anonymous authentication failed' })) } }, [state.isAutoAuthenticating, waitForToken]) // Decide whether to auto-auth on mount & whenever location/auth changes useEffect(() => { if (loading) return if (session) return if (state.isAutoAuthenticating) return const shouldAuth = shouldAutoAuthenticate() if (state.shouldTriggerAutoAuth !== shouldAuth) { setState(prev => ({ ...prev, shouldTriggerAutoAuth: shouldAuth })) } if (shouldAuth) { console.log('[useAutoAnonymousAuth] tool route detected, auto-auth') triggerAnonymousAuth() } }, [loading, session, state.isAutoAuthenticating, state.shouldTriggerAutoAuth, shouldAutoAuthenticate, triggerAnonymousAuth]) // Clear error if route is no longer a tool route, or once authenticated useEffect(() => { if (session || !shouldAutoAuthenticate()) { setState(prev => ({ ...prev, autoAuthError: null, shouldTriggerAutoAuth: false })) } }, [session, shouldAutoAuthenticate]) return { isAutoAuthenticating: state.isAutoAuthenticating, autoAuthError: state.autoAuthError, shouldTriggerAutoAuth: state.shouldTriggerAutoAuth, triggerAnonymousAuth } }