Add Server-Sent Events (SSE) support for real-time synchronization
Despliegue Automático / desplegar (push) Successful in 1m38s
Despliegue Automático / desplegar (push) Successful in 1m38s
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const syncService = require('../services/syncService');
|
||||
const sseManager = require('../services/sseManager');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
|
||||
@@ -36,4 +37,35 @@ const sync = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { sync };
|
||||
const streamEvents = async (req, res) => {
|
||||
try {
|
||||
// Validar token (usa tu lógica existente o un middleware)
|
||||
const token = req.headers.authorization.slice(7).trim();
|
||||
const payload = jwt.verify(token, JWT_SECRET);
|
||||
const userId = payload.id;
|
||||
|
||||
// Configurar cabeceras obligatorias para SSE
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
res.flushHeaders(); // Enviar cabeceras inmediatamente
|
||||
|
||||
// Registrar este dispositivo en el manager
|
||||
sseManager.addClient(userId, res);
|
||||
|
||||
// Latido (Heartbeat) para mantener la conexión viva a través de firewalls
|
||||
const interval = setInterval(() => res.write(':keepalive\n\n'), 30000);
|
||||
|
||||
// Cuando el cliente (móvil/PC) cierre la app, limpiar la memoria
|
||||
req.on('close', () => {
|
||||
clearInterval(interval);
|
||||
sseManager.removeClient(userId, res);
|
||||
res.end();
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(401).json({ error: 'No autorizado' });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { sync, streamEvents };
|
||||
@@ -4,4 +4,6 @@ const syncController = require('../controllers/syncController');
|
||||
|
||||
router.post('/sync', syncController.sync);
|
||||
|
||||
router.get('/stream', syncController.streamEvents);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,39 @@
|
||||
class SseManager {
|
||||
constructor() {
|
||||
// Mapa para guardar los clientes conectados: { userId: [res1, res2, ...] }
|
||||
this.clients = new Map();
|
||||
}
|
||||
|
||||
addClient(userId, res) {
|
||||
if (!this.clients.has(userId)) {
|
||||
this.clients.set(userId, []);
|
||||
}
|
||||
this.clients.get(userId).push(res);
|
||||
}
|
||||
|
||||
removeClient(userId, res) {
|
||||
if (this.clients.has(userId)) {
|
||||
const userClients = this.clients.get(userId);
|
||||
const index = userClients.indexOf(res);
|
||||
if (index !== -1) userClients.splice(index, 1);
|
||||
|
||||
// Limpiar el mapa si el usuario ya no tiene dispositivos conectados
|
||||
if (userClients.length === 0) {
|
||||
this.clients.delete(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// El "timbre": Avisar a todos los dispositivos de un usuario (excepto al que originó el cambio)
|
||||
notifyUser(userId) {
|
||||
if (this.clients.has(userId)) {
|
||||
const userClients = this.clients.get(userId);
|
||||
userClients.forEach(res => {
|
||||
// Formato obligatorio de SSE: "data: {JSON}\n\n"
|
||||
res.write(`data: ${JSON.stringify({ event: 'sync_needed' })}\n\n`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new SseManager();
|
||||
@@ -2,7 +2,7 @@ const { Op } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
const Note = require('../models/Note');
|
||||
const Category = require('../models/Category');
|
||||
const { randomUUID } = require('crypto');
|
||||
const sseManager = require('./sseManager');
|
||||
|
||||
const toIso = (value) => (value ? new Date(value).toISOString() : null);
|
||||
|
||||
@@ -217,6 +217,12 @@ class SyncService {
|
||||
})
|
||||
]);
|
||||
|
||||
if(incomingNotes && incomingNotes.length > 0 ||
|
||||
incomingCategories && incomingCategories.length > 0
|
||||
) {
|
||||
sseManager.notifyUser(userId);
|
||||
}
|
||||
|
||||
return {
|
||||
serverTimestamp: new Date().toISOString(),
|
||||
synced: true,
|
||||
|
||||
Reference in New Issue
Block a user