From ad00e3b65d612c788521a2246defe564ab9e02a8 Mon Sep 17 00:00:00 2001 From: Marcos Reifonas Date: Thu, 13 Aug 2026 21:11:57 +0000 Subject: [PATCH] fix(auth): adiciona heartbeat global e previne multi-sessoes acumluadas no painel --- src/hooks/useAuth.tsx | 28 +++++++++++++++++++++++----- src/hooks/useSessionLogs.tsx | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index 3606367..e939cf1 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -140,6 +140,13 @@ export function AuthProvider({ children }: { children: ReactNode }) { const shouldSkipLogging = await isAdminOrDeveloper(userId); if (shouldSkipLogging) return; + // Desativar qualquer sessão antiga que ficou presa (fechar abas sem logout) + await supabase + .from('user_session_logs') + .update({ is_active: false, session_end: new Date().toISOString() }) + .eq('user_id', userId) + .eq('is_active', true); + const { data, error } = await supabase .from('user_session_logs') .insert({ @@ -225,10 +232,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (mounted) { setAuthInitialized(true); setUserOnline(session.user.id); - const existingSessionId = localStorage.getItem('currentSessionId'); - if (!existingSessionId) { - startSessionLog(session.user.id); - } + // Sempre cria uma nova sessão, resetando contadores anteriores + startSessionLog(session.user.id); } }, 100); } else if (mounted) { @@ -282,12 +287,25 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (user && authInitialized) { cleanupInterval = setInterval(async () => { + // Enviar Heartbeat global da sessão (mantém o usuário online de fato) + const sessionId = localStorage.getItem('currentSessionId'); + if (sessionId) { + try { + await supabase + .from('user_session_logs') + .update({ updated_at: new Date().toISOString() }) + .eq('id', sessionId); + } catch (e) { + console.error('Falha no heartbeat da sessão', e); + } + } + try { await supabase.rpc('cleanup_offline_users'); } catch { // Ignora se RPC não existir } - }, 60000); + }, 30000); // 30 segundos const handleBeforeUnload = () => { if (user) { diff --git a/src/hooks/useSessionLogs.tsx b/src/hooks/useSessionLogs.tsx index 8f66742..e645329 100644 --- a/src/hooks/useSessionLogs.tsx +++ b/src/hooks/useSessionLogs.tsx @@ -296,12 +296,24 @@ export const useSessionLogs = () => { } }; - // Atualizar automaticamente usuários online + // Atualizar automaticamente usuários online e fazer heartbeat da sessão useEffect(() => { fetchOnlineUsers(); - const interval = setInterval(fetchOnlineUsers, 30000); // Atualizar a cada 30 segundos + const interval = setInterval(() => { + fetchOnlineUsers(); + + const sessionId = currentSessionId || localStorage.getItem('currentSessionId'); + if (sessionId) { + // Heartbeat pings database every 30s to keep session alive + supabase + .from('user_session_logs') + .update({ updated_at: new Date().toISOString() }) + .eq('id', sessionId) + .catch(console.error); + } + }, 30000); // Atualizar a cada 30 segundos return () => clearInterval(interval); - }, []); + }, [currentSessionId]); // Gerenciar sessão baseado no estado de autenticação useEffect(() => {