167 lines
4.7 KiB
Svelte
167 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import { useConvexClient } from 'convex-svelte';
|
|
import { useQuery } from 'convex-svelte';
|
|
import { api } from '@sgse-app/backend/convex/_generated/api';
|
|
import { onMount } from 'svelte';
|
|
|
|
const client = useConvexClient();
|
|
|
|
// Verificar se o usuário está autenticado antes de gerenciar presença
|
|
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
|
const usuarioAutenticado = $derived(
|
|
currentUser?.data !== null && currentUser?.data !== undefined
|
|
);
|
|
|
|
// Token é passado automaticamente via interceptadores em +layout.svelte
|
|
|
|
let heartbeatInterval: ReturnType<typeof setInterval> | null = null;
|
|
let inactivityTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
let lastActivity = Date.now();
|
|
let lastStatusUpdate = 0;
|
|
let pendingStatusUpdate: ReturnType<typeof setTimeout> | null = null;
|
|
const STATUS_UPDATE_THROTTLE = 5000; // 5 segundos entre atualizações
|
|
|
|
// Função auxiliar para atualizar status com throttle e tratamento de erro
|
|
async function atualizarStatusPresencaSeguro(status: 'online' | 'offline' | 'ausente' | 'externo' | 'em_reuniao') {
|
|
if (!usuarioAutenticado) return;
|
|
|
|
const now = Date.now();
|
|
// Throttle: só atualizar se passou tempo suficiente desde a última atualização
|
|
if (now - lastStatusUpdate < STATUS_UPDATE_THROTTLE) {
|
|
// Cancelar atualização pendente se houver
|
|
if (pendingStatusUpdate) {
|
|
clearTimeout(pendingStatusUpdate);
|
|
}
|
|
// Agendar atualização para depois do throttle
|
|
pendingStatusUpdate = setTimeout(() => {
|
|
atualizarStatusPresencaSeguro(status);
|
|
}, STATUS_UPDATE_THROTTLE - (now - lastStatusUpdate));
|
|
return;
|
|
}
|
|
|
|
// Limpar atualização pendente se houver
|
|
if (pendingStatusUpdate) {
|
|
clearTimeout(pendingStatusUpdate);
|
|
pendingStatusUpdate = null;
|
|
}
|
|
|
|
lastStatusUpdate = now;
|
|
|
|
try {
|
|
await client.mutation(api.chat.atualizarStatusPresenca, { status });
|
|
} catch (error) {
|
|
// Silenciar erros de timeout - não são críticos para a funcionalidade
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
const isTimeout = errorMessage.includes('timed out') || errorMessage.includes('timeout');
|
|
if (!isTimeout) {
|
|
console.error('Erro ao atualizar status de presença:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detectar atividade do usuário
|
|
function handleActivity() {
|
|
if (!usuarioAutenticado) return;
|
|
|
|
lastActivity = Date.now();
|
|
|
|
// Limpar timeout de inatividade anterior
|
|
if (inactivityTimeout) {
|
|
clearTimeout(inactivityTimeout);
|
|
}
|
|
|
|
// Configurar novo timeout (5 minutos)
|
|
inactivityTimeout = setTimeout(
|
|
() => {
|
|
if (usuarioAutenticado) {
|
|
atualizarStatusPresencaSeguro('ausente');
|
|
}
|
|
},
|
|
5 * 60 * 1000
|
|
);
|
|
}
|
|
|
|
onMount(() => {
|
|
// Só configurar presença se usuário estiver autenticado
|
|
if (!usuarioAutenticado) return;
|
|
|
|
// Configurar como online ao montar (apenas se autenticado)
|
|
atualizarStatusPresencaSeguro('online');
|
|
|
|
// Heartbeat a cada 30 segundos (apenas se autenticado)
|
|
heartbeatInterval = setInterval(() => {
|
|
if (!usuarioAutenticado) {
|
|
if (heartbeatInterval) {
|
|
clearInterval(heartbeatInterval);
|
|
heartbeatInterval = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const timeSinceLastActivity = Date.now() - lastActivity;
|
|
|
|
// Se houve atividade nos últimos 5 minutos, manter online
|
|
if (timeSinceLastActivity < 5 * 60 * 1000) {
|
|
atualizarStatusPresencaSeguro('online');
|
|
}
|
|
}, 30 * 1000);
|
|
|
|
// Listeners para detectar atividade
|
|
const events = ['mousedown', 'keydown', 'scroll', 'touchstart'];
|
|
events.forEach((event) => {
|
|
window.addEventListener(event, handleActivity);
|
|
});
|
|
|
|
// Configurar timeout inicial de inatividade
|
|
if (usuarioAutenticado) {
|
|
handleActivity();
|
|
}
|
|
|
|
// Detectar quando a aba fica inativa/ativa
|
|
function handleVisibilityChange() {
|
|
if (!usuarioAutenticado) return;
|
|
|
|
if (document.hidden) {
|
|
// Aba ficou inativa
|
|
atualizarStatusPresencaSeguro('ausente');
|
|
} else {
|
|
// Aba ficou ativa
|
|
atualizarStatusPresencaSeguro('online');
|
|
handleActivity();
|
|
}
|
|
}
|
|
|
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
|
|
// Cleanup
|
|
return () => {
|
|
// Limpar atualização pendente
|
|
if (pendingStatusUpdate) {
|
|
clearTimeout(pendingStatusUpdate);
|
|
pendingStatusUpdate = null;
|
|
}
|
|
|
|
// Marcar como offline ao desmontar (apenas se autenticado)
|
|
if (usuarioAutenticado) {
|
|
atualizarStatusPresencaSeguro('offline');
|
|
}
|
|
|
|
if (heartbeatInterval) {
|
|
clearInterval(heartbeatInterval);
|
|
}
|
|
|
|
if (inactivityTimeout) {
|
|
clearTimeout(inactivityTimeout);
|
|
}
|
|
|
|
events.forEach((event) => {
|
|
window.removeEventListener(event, handleActivity);
|
|
});
|
|
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<!-- Componente invisível - apenas lógica -->
|