import { ref, computed } from 'vue' // Estado global compartido para auth const authChecked = ref(true) // Iniciamos como chequeado const isAuthenticated = ref(true) // Asumimos autenticado al inicio (si la página cargó, es porque pasamos Authentik) const isCheckingAuth = ref(false) const lastCheckTime = ref(0) // Listener para cambios de autenticación desde otras tabs/ventanas let visibilityChangeListener: (() => void) | null = null let focusListener: (() => void) | null = null export const useAuth = () => { const checkAuth = async (force = false) => { // Si ya sabemos que NO estamos autenticados, no hacer más requests // (evita loops de CORS con Authentik redirects) if (!isAuthenticated.value && !force) { console.log('[Auth] Skipping check - already known to be unauthenticated') return } // Evitar chequeos duplicados (cache de 30 segundos) const now = Date.now() if (!force && isCheckingAuth.value) return if (!force && authChecked.value && now - lastCheckTime.value < 30000) { return } isCheckingAuth.value = true try { // Intenta hacer fetch a un endpoint protegido const response = await fetch('/api/music', { method: 'HEAD', // Solo headers, no body credentials: 'include', // Include cookies for auth cache: 'no-store', // No cachear la respuesta redirect: 'error', // No seguir redirects (error si Authentik intenta redirigir) signal: AbortSignal.timeout(5000) // 5s timeout }) const wasAuthenticated = isAuthenticated.value isAuthenticated.value = response.ok authChecked.value = true lastCheckTime.value = now // Log cambios de estado if (wasAuthenticated !== isAuthenticated.value) { console.log('[Auth] Status changed:', isAuthenticated.value ? 'authenticated' : 'unauthenticated') } } catch (error: any) { // Con redirect: 'error', cualquier redirect (302) genera "Failed to fetch" // Authentik redirige a login cuando no estás autenticado = 302 console.warn('[Auth] Check failed:', error.message) const wasAuthenticated = isAuthenticated.value // "Failed to fetch" con redirect: 'error' configurado = sesión expirada // Authentik intenta redirigir (302) pero fetch lo rechaza y genera error if (error.message && error.message.includes('Failed to fetch')) { console.log('[Auth] Failed to fetch detected - marking as unauthenticated') isAuthenticated.value = false authChecked.value = true if (wasAuthenticated) { console.log('[Auth] Status changed: authenticated → unauthenticated (Authentik redirect detected)') } else { console.log('[Auth] Confirmed unauthenticated state') } } else if (error.name === 'TypeError' && error.message.includes('fetch')) { // Otro tipo de TypeError fetch = también probablemente redirect console.log('[Auth] TypeError fetch detected - marking as unauthenticated') isAuthenticated.value = false authChecked.value = true if (wasAuthenticated) { console.log('[Auth] Status changed: authenticated → unauthenticated (fetch error)') } else { console.log('[Auth] Confirmed unauthenticated state') } } else { // Otro tipo de error (timeout, etc) - mantener estado actual console.log('[Auth] Network error (not auth related), maintaining current state:', error.name) } lastCheckTime.value = now } finally { isCheckingAuth.value = false } } const triggerAuth = () => { console.log('[Auth] Triggering authentication flow - forcing page reload...') // Simplemente recargamos la página completamente // Si no estamos autenticados, Authentik interceptará y redirigirá al login // Si ya estamos autenticados, la página se recarga normalmente window.location.reload() } const markUnauthenticated = () => { // Helper para marcar como no autenticado (útil cuando detectamos 401/403) console.log('[Auth] Marking as unauthenticated') isAuthenticated.value = false authChecked.value = true lastCheckTime.value = Date.now() } const setupVisibilityListener = () => { // Re-chequea auth cuando la pestaña vuelve a ser visible // (útil si el usuario se autentica en otra pestaña) if (typeof document !== 'undefined' && !visibilityChangeListener) { visibilityChangeListener = () => { if (!document.hidden && isAuthenticated.value) { // Solo chequear si creemos estar autenticados // (evita spam de requests fallidas) console.log('[Auth] Tab visible again, checking auth...') checkAuth(false) } } document.addEventListener('visibilitychange', visibilityChangeListener) } // También chequea cuando la ventana recibe foco if (typeof window !== 'undefined' && !focusListener) { focusListener = () => { if (isAuthenticated.value) { // Solo chequear si creemos estar autenticados console.log('[Auth] Window focused, checking auth...') checkAuth(false) } } window.addEventListener('focus', focusListener) } } const cleanupListeners = () => { if (visibilityChangeListener) { document.removeEventListener('visibilitychange', visibilityChangeListener) visibilityChangeListener = null } if (focusListener) { window.removeEventListener('focus', focusListener) focusListener = null } } const authStatus = computed(() => { if (!authChecked.value) return 'unknown' return isAuthenticated.value ? 'authenticated' : 'unauthenticated' }) return { isAuthenticated, authChecked, isCheckingAuth, authStatus, checkAuth, triggerAuth, markUnauthenticated, setupVisibilityListener, cleanupListeners } }