From 65d868a7ddbb810688b363401dc22c8a8c3af684 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 13 Oct 2025 07:00:54 +0000 Subject: [PATCH] fix: AI chat authentication on iOS Safari by using global auth state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: - Floating chat had its own separate authentication check using localStorage - iOS Safari has restrictions on localStorage access, especially with tracking prevention - This caused logged-in users to still see login prompt in AI chat Solution: - Replace local auth state management with global useAuth hook from AuthProvider - Remove redundant checkAuthStatus() function - Update all authentication checks to use isAuthenticated from useAuth - Update token retrieval to get directly from localStorage only when needed Benefits: - Single source of truth for authentication across the app - More reliable authentication state management - Better compatibility with iOS Safari's privacy features - Automatic auth state synchronization when user logs in/out Changes: - Use useAuth hook instead of local isAuthenticated/authToken state - Remove checkAuthStatus() function - Update loadConversations to read token from localStorage directly - Update loadConversation to read token from localStorage directly - Update handleSendMessage to read token from localStorage directly - Simplify handleAuthSuccess to rely on global auth state updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- components/chat/floating-chat.tsx | 70 ++++++++----------------------- 1 file changed, 17 insertions(+), 53 deletions(-) diff --git a/components/chat/floating-chat.tsx b/components/chat/floating-chat.tsx index 17bf5b6..da1c565 100644 --- a/components/chat/floating-chat.tsx +++ b/components/chat/floating-chat.tsx @@ -46,6 +46,7 @@ import { useState, useRef, useEffect, useCallback } from 'react' import { useTranslations, useLocale } from 'next-intl' import ReactMarkdown from 'react-markdown' import { AuthModal } from '@/components/auth/auth-modal' +import { useAuth } from '@/hooks/use-auth' // Random Bible-related loading messages const LOADING_MESSAGES = [ @@ -77,6 +78,7 @@ export default function FloatingChat() { const theme = useTheme() const t = useTranslations('chat') const locale = useLocale() + const { user, isAuthenticated } = useAuth() // Use global auth state const [isOpen, setIsOpen] = useState(false) const [isMinimized, setIsMinimized] = useState(false) const [isFullscreen, setIsFullscreen] = useState(false) @@ -98,8 +100,6 @@ export default function FloatingChat() { // Conversation management state const [conversations, setConversations] = useState([]) const [activeConversationId, setActiveConversationId] = useState(null) - const [isAuthenticated, setIsAuthenticated] = useState(false) - const [authToken, setAuthToken] = useState(null) const [isLoadingConversations, setIsLoadingConversations] = useState(false) const [menuAnchorEl, setMenuAnchorEl] = useState(null) const [selectedConversationId, setSelectedConversationId] = useState(null) @@ -144,56 +144,22 @@ export default function FloatingChat() { return () => window.removeEventListener('auth:sign-in-required', handler as EventListener) }, []) - // Check authentication status - useEffect(() => { - checkAuthStatus() - }, []) - // Load conversations when authenticated useEffect(() => { - if (isAuthenticated && authToken) { + if (isAuthenticated) { loadConversations() } - }, [isAuthenticated, authToken, locale]) - - const checkAuthStatus = useCallback(async () => { - try { - const token = localStorage.getItem('authToken') - if (token) { - // Verify token with the server - const response = await fetch('/api/auth/me', { - headers: { - 'Authorization': `Bearer ${token}` - } - }) - - if (response.ok) { - setAuthToken(token) - setIsAuthenticated(true) - } else { - localStorage.removeItem('authToken') - setIsAuthenticated(false) - setAuthToken(null) - } - } else { - setIsAuthenticated(false) - setAuthToken(null) - } - } catch (error) { - console.error('Chat - Auth check failed:', error) - setIsAuthenticated(false) - setAuthToken(null) - } - }, []) + }, [isAuthenticated, locale]) const loadConversations = useCallback(async () => { - if (!authToken) return + const token = localStorage.getItem('authToken') + if (!token) return setIsLoadingConversations(true) try { const response = await fetch(`/api/chat/conversations?language=${locale}&limit=20`, { headers: { - 'Authorization': `Bearer ${authToken}` + 'Authorization': `Bearer ${token}` } }) @@ -208,15 +174,16 @@ export default function FloatingChat() { } finally { setIsLoadingConversations(false) } - }, [authToken, locale]) + }, [locale]) const loadConversation = useCallback(async (conversationId: string) => { - if (!authToken) return + const token = localStorage.getItem('authToken') + if (!token) return try { const response = await fetch(`/api/chat/conversations/${conversationId}`, { headers: { - 'Authorization': `Bearer ${authToken}` + 'Authorization': `Bearer ${token}` } }) @@ -239,7 +206,7 @@ export default function FloatingChat() { } catch (error) { console.error('Error loading conversation:', error) } - }, [authToken]) + }, []) const createNewConversation = useCallback(() => { // Reset to a new conversation @@ -369,12 +336,9 @@ export default function FloatingChat() { } // Add authentication if available - console.log('Chat - authToken value:', authToken ? 'present' : 'null') - if (authToken) { - headers['Authorization'] = `Bearer ${authToken}` - console.log('Chat - Authorization header added') - } else { - console.log('Chat - No authToken, skipping Authorization header') + const token = localStorage.getItem('authToken') + if (token) { + headers['Authorization'] = `Bearer ${token}` } const response = await fetch('/api/chat', { @@ -456,8 +420,8 @@ export default function FloatingChat() { const handleAuthSuccess = () => { setAuthModalOpen(false) - // Re-check auth status to update the UI - checkAuthStatus() + // Auth state will be updated automatically by the global useAuth hook + // Load conversations will trigger automatically via useEffect when isAuthenticated changes } return (