fix(auth): adiciona heartbeat global e previne multi-sessoes acumluadas no painel

This commit is contained in:
2026-08-13 21:11:57 +00:00
parent 938f22a423
commit ad00e3b65d
2 changed files with 38 additions and 8 deletions
+23 -5
View File
@@ -140,6 +140,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const shouldSkipLogging = await isAdminOrDeveloper(userId); const shouldSkipLogging = await isAdminOrDeveloper(userId);
if (shouldSkipLogging) return; 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 const { data, error } = await supabase
.from('user_session_logs') .from('user_session_logs')
.insert({ .insert({
@@ -225,10 +232,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
if (mounted) { if (mounted) {
setAuthInitialized(true); setAuthInitialized(true);
setUserOnline(session.user.id); setUserOnline(session.user.id);
const existingSessionId = localStorage.getItem('currentSessionId'); // Sempre cria uma nova sessão, resetando contadores anteriores
if (!existingSessionId) { startSessionLog(session.user.id);
startSessionLog(session.user.id);
}
} }
}, 100); }, 100);
} else if (mounted) { } else if (mounted) {
@@ -282,12 +287,25 @@ export function AuthProvider({ children }: { children: ReactNode }) {
if (user && authInitialized) { if (user && authInitialized) {
cleanupInterval = setInterval(async () => { 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 { try {
await supabase.rpc('cleanup_offline_users'); await supabase.rpc('cleanup_offline_users');
} catch { } catch {
// Ignora se RPC não existir // Ignora se RPC não existir
} }
}, 60000); }, 30000); // 30 segundos
const handleBeforeUnload = () => { const handleBeforeUnload = () => {
if (user) { if (user) {
+15 -3
View File
@@ -296,12 +296,24 @@ export const useSessionLogs = () => {
} }
}; };
// Atualizar automaticamente usuários online // Atualizar automaticamente usuários online e fazer heartbeat da sessão
useEffect(() => { useEffect(() => {
fetchOnlineUsers(); 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); return () => clearInterval(interval);
}, []); }, [currentSessionId]);
// Gerenciar sessão baseado no estado de autenticação // Gerenciar sessão baseado no estado de autenticação
useEffect(() => { useEffect(() => {