activity
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { getDB, getSession, getUser, deleteSession as deleteSessionFromDB } from './mongodb.js';
|
||||
import { getDB, getSession, getUser, deleteSession as deleteSessionFromDB, updateSessionActivity } from './mongodb.js';
|
||||
|
||||
let wss = null;
|
||||
|
||||
// Duración de la sesión en milisegundos (24 horas)
|
||||
const SESSION_DURATION = 24 * 60 * 60 * 1000;
|
||||
|
||||
// Tiempo de inactividad para marcar como inactivo (5 minutos)
|
||||
const INACTIVE_TIMEOUT = 5 * 60 * 1000;
|
||||
|
||||
// Intervalo para limpiar conexiones inactivas (1 minuto)
|
||||
const CLEANUP_INTERVAL = 60 * 1000;
|
||||
|
||||
// Inicializar WebSocket Server
|
||||
export function initWebSocket(server) {
|
||||
wss = new WebSocketServer({ server, path: '/ws' });
|
||||
@@ -73,18 +79,90 @@ export function initWebSocket(server) {
|
||||
role: user.role || 'user',
|
||||
token: token
|
||||
};
|
||||
ws.isAlive = true;
|
||||
ws.lastActivity = new Date();
|
||||
|
||||
// Actualizar estado de conexión en la base de datos
|
||||
await updateSessionActivity(token, true);
|
||||
|
||||
console.log(`Cliente WebSocket conectado: ${session.username} (${user.role || 'user'})`);
|
||||
|
||||
// Enviar confirmación de conexión
|
||||
ws.send(JSON.stringify({
|
||||
type: 'connection',
|
||||
status: 'connected',
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
|
||||
// Broadcast a otros clientes que este usuario se conectó
|
||||
broadcastUserStatus(session.username, 'online');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error validando token WebSocket:', error);
|
||||
ws.close(1011, 'Error de autenticación');
|
||||
return;
|
||||
}
|
||||
|
||||
ws.on('close', () => {
|
||||
// Manejar mensajes del cliente
|
||||
ws.on('message', async (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
// Manejar diferentes tipos de mensajes
|
||||
switch (message.type) {
|
||||
case 'ping':
|
||||
case 'heartbeat':
|
||||
// Actualizar actividad
|
||||
ws.isAlive = true;
|
||||
ws.lastActivity = new Date();
|
||||
|
||||
// Actualizar en base de datos
|
||||
if (ws.user && ws.user.token) {
|
||||
await updateSessionActivity(ws.user.token, true);
|
||||
}
|
||||
|
||||
// Responder con pong
|
||||
ws.send(JSON.stringify({
|
||||
type: 'pong',
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'activity':
|
||||
// El usuario realizó alguna actividad (click, scroll, etc.)
|
||||
ws.isAlive = true;
|
||||
ws.lastActivity = new Date();
|
||||
|
||||
if (ws.user && ws.user.token) {
|
||||
await updateSessionActivity(ws.user.token, true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`Mensaje WebSocket no manejado: ${message.type}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error procesando mensaje WebSocket:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Manejar pong nativo de WebSocket
|
||||
ws.on('pong', () => {
|
||||
ws.isAlive = true;
|
||||
ws.lastActivity = new Date();
|
||||
});
|
||||
|
||||
ws.on('close', async () => {
|
||||
if (ws.user) {
|
||||
console.log(`Cliente WebSocket desconectado: ${ws.user.username}`);
|
||||
|
||||
// Actualizar estado en base de datos
|
||||
if (ws.user.token) {
|
||||
await updateSessionActivity(ws.user.token, false);
|
||||
}
|
||||
|
||||
// Broadcast a otros clientes que este usuario se desconectó
|
||||
broadcastUserStatus(ws.user.username, 'offline');
|
||||
} else {
|
||||
console.log('Cliente WebSocket desconectado');
|
||||
}
|
||||
@@ -95,9 +173,81 @@ export function initWebSocket(server) {
|
||||
});
|
||||
});
|
||||
|
||||
// Iniciar limpieza periódica de conexiones inactivas
|
||||
startCleanupInterval();
|
||||
|
||||
return wss;
|
||||
}
|
||||
|
||||
// Limpiar conexiones inactivas periódicamente
|
||||
let cleanupIntervalId = null;
|
||||
|
||||
function startCleanupInterval() {
|
||||
if (cleanupIntervalId) {
|
||||
clearInterval(cleanupIntervalId);
|
||||
}
|
||||
|
||||
cleanupIntervalId = setInterval(() => {
|
||||
if (!wss) return;
|
||||
|
||||
const now = new Date();
|
||||
|
||||
wss.clients.forEach(async (ws) => {
|
||||
// Si el cliente no ha respondido, marcarlo como inactivo
|
||||
if (ws.isAlive === false) {
|
||||
console.log(`Cerrando conexión inactiva: ${ws.user?.username || 'desconocido'}`);
|
||||
|
||||
// Actualizar estado en base de datos antes de cerrar
|
||||
if (ws.user && ws.user.token) {
|
||||
await updateSessionActivity(ws.user.token, false);
|
||||
}
|
||||
|
||||
return ws.terminate();
|
||||
}
|
||||
|
||||
// Verificar si ha pasado el timeout de inactividad
|
||||
if (ws.lastActivity && (now - ws.lastActivity) > INACTIVE_TIMEOUT) {
|
||||
console.log(`Usuario inactivo detectado: ${ws.user?.username || 'desconocido'}`);
|
||||
|
||||
// Actualizar estado en base de datos pero mantener conexión
|
||||
if (ws.user && ws.user.token) {
|
||||
await updateSessionActivity(ws.user.token, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Marcar como no vivo y enviar ping
|
||||
ws.isAlive = false;
|
||||
ws.ping();
|
||||
});
|
||||
}, CLEANUP_INTERVAL);
|
||||
}
|
||||
|
||||
// Detener limpieza
|
||||
function stopCleanupInterval() {
|
||||
if (cleanupIntervalId) {
|
||||
clearInterval(cleanupIntervalId);
|
||||
cleanupIntervalId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast del estado de un usuario específico
|
||||
function broadcastUserStatus(username, status) {
|
||||
if (!wss) return;
|
||||
|
||||
const message = JSON.stringify({
|
||||
type: 'user_status',
|
||||
username: username,
|
||||
status: status, // 'online' | 'offline' | 'inactive'
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
wss.clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // WebSocket.OPEN
|
||||
client.send(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Broadcast a todos los clientes WebSocket
|
||||
export function broadcast(data) {
|
||||
if (!wss) return;
|
||||
@@ -114,3 +264,51 @@ export function getWebSocketServer() {
|
||||
return wss;
|
||||
}
|
||||
|
||||
// Obtener usuarios activos conectados
|
||||
export function getActiveUsers() {
|
||||
if (!wss) return [];
|
||||
|
||||
const activeUsers = [];
|
||||
const now = new Date();
|
||||
|
||||
wss.clients.forEach((ws) => {
|
||||
if (ws.user && ws.readyState === 1) { // WebSocket.OPEN
|
||||
const isActive = ws.lastActivity && (now - ws.lastActivity) <= INACTIVE_TIMEOUT;
|
||||
|
||||
activeUsers.push({
|
||||
username: ws.user.username,
|
||||
role: ws.user.role,
|
||||
status: isActive ? 'active' : 'inactive',
|
||||
lastActivity: ws.lastActivity?.toISOString() || null,
|
||||
connectedAt: ws.lastActivity?.toISOString() || null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Eliminar duplicados (un usuario puede tener múltiples conexiones)
|
||||
const uniqueUsers = [];
|
||||
const seenUsernames = new Set();
|
||||
|
||||
for (const user of activeUsers) {
|
||||
if (!seenUsernames.has(user.username)) {
|
||||
seenUsernames.add(user.username);
|
||||
uniqueUsers.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueUsers;
|
||||
}
|
||||
|
||||
// Cerrar todas las conexiones y limpiar
|
||||
export function closeWebSocket() {
|
||||
stopCleanupInterval();
|
||||
|
||||
if (wss) {
|
||||
wss.clients.forEach((ws) => {
|
||||
ws.close(1001, 'Servidor cerrando');
|
||||
});
|
||||
wss.close();
|
||||
wss = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user