fix(auth): adiciona heartbeat global e previne multi-sessoes acumluadas no painel
This commit is contained in:
+23
-5
@@ -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) {
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user