UNPKG

backend-mcp

Version:

Generador automático de backends con Node.js, Express, Prisma y módulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.

775 lines (615 loc) 16.6 kB
# 📦 Módulo websockets **Versión:** 1.0.0 **Categoría:** realtime **Descripción:** Sistema completo de WebSockets para comunicación en tiempo real ## 📊 Estado del Módulo | Componente | Estado | |------------|--------| | Script de inicialización | ✅ Disponible | | Templates | ❌ Faltante | | Ejemplos | ❌ Faltante | ## 🔗 Dependencias ### Requeridas - `database` ### Opcionales - `auth` - `logging` - `redis` ## 🤖 Triggers para IA Este módulo se activa automáticamente cuando se detectan las siguientes palabras clave: - **user_wants_realtime**: true - **needs_websockets**: true - **requires_chat**: true - **has_notifications**: true - **undefined**: undefined ## ✨ Características - real-time-messaging - room-management - user-presence - message-broadcasting - private-messaging - typing-indicators - connection-management - event-handling - middleware-support - authentication-integration - redis-adapter - scalable-architecture ## 📖 Documentación Completa # Módulo WebSockets Módulo para implementar comunicación en tiempo real mediante WebSockets en aplicaciones backend. ## Características - ✅ Servidor WebSocket con Socket.IO - ✅ Autenticación y autorización de conexiones - ✅ Salas (rooms) y namespaces - ✅ Broadcasting y mensajería dirigida - ✅ Middleware personalizable - ✅ Rate limiting para conexiones - ✅ Reconexión automática del cliente - ✅ Compresión de mensajes - ✅ Logs y métricas de conexiones - ✅ Clustering y escalabilidad horizontal ## Configuración ### Requisitos - Node.js 16+ - Express.js o servidor HTTP compatible - Redis (para clustering, opcional) ### Variables de Entorno ```bash # Configuración del servidor WebSocket WS_PORT=3001 WS_PATH=/socket.io WS_CORS_ORIGIN=http://localhost:3000 WS_MAX_CONNECTIONS=1000 # Configuración de autenticación WS_AUTH_ENABLED=true WS_JWT_SECRET=your_jwt_secret_here # Configuración de Redis (para clustering) WS_REDIS_ENABLED=false WS_REDIS_HOST=localhost WS_REDIS_PORT=6379 WS_REDIS_PASSWORD= # Configuración de rate limiting WS_RATE_LIMIT_ENABLED=true WS_RATE_LIMIT_MAX=100 WS_RATE_LIMIT_WINDOW=60000 ``` ## Uso ### Configuración Básica ```javascript const { initWebSockets } = require('./modules/websockets'); const express = require('express'); const http = require('http'); const app = express(); const server = http.createServer(app); // Inicializar WebSockets const io = await initWebSockets(server, { cors: { origin: process.env.WS_CORS_ORIGIN, methods: ['GET', 'POST'] }, path: process.env.WS_PATH }); server.listen(3000, () => { console.log('Servidor iniciado en puerto 3000'); }); ``` ### Manejo de Conexiones ```javascript // Manejar nuevas conexiones io.on('connection', (socket) => { console.log(`Usuario conectado: ${socket.id}`); // Unir a una sala socket.join('general'); // Manejar mensajes socket.on('message', (data) => { console.log('Mensaje recibido:', data); // Enviar a todos en la sala io.to('general').emit('message', { id: socket.id, message: data.message, timestamp: new Date() }); }); // Manejar desconexión socket.on('disconnect', () => { console.log(`Usuario desconectado: ${socket.id}`); }); }); ``` ### Autenticación ```javascript // Configurar autenticación con JWT const io = await initWebSockets(server, { auth: { enabled: true, jwtSecret: process.env.WS_JWT_SECRET, verify: async (token, socket) => { try { const decoded = jwt.verify(token, process.env.WS_JWT_SECRET); const user = await getUserById(decoded.userId); if (!user) { throw new Error('Usuario no encontrado'); } socket.userId = user.id; socket.userRole = user.role; return true; } catch (error) { return false; } } } }); // Cliente con autenticación const socket = io('http://localhost:3000', { auth: { token: 'your_jwt_token_here' } }); ``` ### Salas y Namespaces ```javascript // Crear namespace para chat const chatNamespace = io.of('/chat'); chatNamespace.on('connection', (socket) => { // Unir a sala específica socket.on('join-room', (roomId) => { socket.join(`room-${roomId}`); socket.emit('joined-room', roomId); // Notificar a otros en la sala socket.to(`room-${roomId}`).emit('user-joined', { userId: socket.userId, roomId }); }); // Enviar mensaje a sala socket.on('room-message', (data) => { chatNamespace.to(`room-${data.roomId}`).emit('message', { userId: socket.userId, message: data.message, roomId: data.roomId, timestamp: new Date() }); }); // Salir de sala socket.on('leave-room', (roomId) => { socket.leave(`room-${roomId}`); socket.to(`room-${roomId}`).emit('user-left', { userId: socket.userId, roomId }); }); }); ``` ## Middleware ### Middleware de Autenticación ```javascript // Middleware personalizado io.use(async (socket, next) => { try { const token = socket.handshake.auth.token; if (!token) { throw new Error('Token requerido'); } const user = await authenticateToken(token); socket.user = user; next(); } catch (error) { next(new Error('Autenticación fallida')); } }); ``` ### Middleware de Rate Limiting ```javascript // Rate limiting por usuario const rateLimiter = new Map(); io.use((socket, next) => { const userId = socket.user?.id || socket.handshake.address; const now = Date.now(); const windowStart = now - 60000; // 1 minuto if (!rateLimiter.has(userId)) { rateLimiter.set(userId, []); } const requests = rateLimiter.get(userId); const recentRequests = requests.filter(time => time > windowStart); if (recentRequests.length >= 100) { next(new Error('Rate limit excedido')); return; } recentRequests.push(now); rateLimiter.set(userId, recentRequests); next(); }); ``` ## Broadcasting ### Envío Masivo ```javascript // Enviar a todos los clientes conectados io.emit('global-announcement', { message: 'Mantenimiento programado en 10 minutos', type: 'warning', timestamp: new Date() }); // Enviar a usuarios específicos const sendToUsers = (userIds, event, data) => { userIds.forEach(userId => { const sockets = getUserSockets(userId); sockets.forEach(socket => { socket.emit(event, data); }); }); }; // Enviar a usuarios con rol específico const sendToRole = (role, event, data) => { io.sockets.sockets.forEach(socket => { if (socket.userRole === role) { socket.emit(event, data); } }); }; ``` ### Notificaciones en Tiempo Real ```javascript // Sistema de notificaciones class NotificationService { static async sendNotification(userId, notification) { // Guardar en base de datos await saveNotification(userId, notification); // Enviar por WebSocket si está conectado const userSockets = getUserSockets(userId); userSockets.forEach(socket => { socket.emit('notification', notification); }); } static async sendBulkNotifications(notifications) { for (const { userId, notification } of notifications) { await this.sendNotification(userId, notification); } } } // Uso del servicio await NotificationService.sendNotification('user123', { id: 'notif_456', title: 'Nuevo mensaje', message: 'Tienes un nuevo mensaje de Juan', type: 'message', timestamp: new Date(), read: false }); ``` ## Clustering y Escalabilidad ### Configuración con Redis ```javascript const { createAdapter } = require('@socket.io/redis-adapter'); const { createClient } = require('redis'); // Configurar adaptador Redis para clustering const pubClient = createClient({ host: process.env.WS_REDIS_HOST, port: process.env.WS_REDIS_PORT, password: process.env.WS_REDIS_PASSWORD }); const subClient = pubClient.duplicate(); await pubClient.connect(); await subClient.connect(); io.adapter(createAdapter(pubClient, subClient)); // Ahora el servidor puede escalar horizontalmente console.log('Clustering habilitado con Redis'); ``` ### Balanceador de Carga ```javascript // Configuración para sticky sessions const io = await initWebSockets(server, { transports: ['websocket', 'polling'], allowEIO3: true, cors: { origin: process.env.WS_CORS_ORIGIN, credentials: true } }); // Configuración de nginx para sticky sessions /* upstream socketio { ip_hash; server 127.0.0.1:3001; server 127.0.0.1:3002; server 127.0.0.1:3003; } server { listen 80; location /socket.io/ { proxy_pass http://socketio; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } */ ``` ## Cliente JavaScript ### Configuración del Cliente ```javascript // Cliente básico const socket = io('http://localhost:3000', { autoConnect: true, reconnection: true, reconnectionDelay: 1000, reconnectionAttempts: 5, timeout: 20000 }); // Manejar eventos de conexión socket.on('connect', () => { console.log('Conectado al servidor'); }); socket.on('disconnect', (reason) => { console.log('Desconectado:', reason); }); socket.on('connect_error', (error) => { console.error('Error de conexión:', error); }); ``` ### Chat en Tiempo Real ```javascript // Sistema de chat class ChatClient { constructor(serverUrl, token) { this.socket = io(serverUrl, { auth: { token } }); this.setupEventListeners(); } setupEventListeners() { this.socket.on('message', (data) => { this.displayMessage(data); }); this.socket.on('user-joined', (data) => { this.showUserJoined(data.userId); }); this.socket.on('user-left', (data) => { this.showUserLeft(data.userId); }); } joinRoom(roomId) { this.socket.emit('join-room', roomId); } sendMessage(roomId, message) { this.socket.emit('room-message', { roomId, message }); } displayMessage(data) { const messageElement = document.createElement('div'); messageElement.innerHTML = ` <strong>${data.userId}:</strong> ${data.message} <small>${new Date(data.timestamp).toLocaleTimeString()}</small> `; document.getElementById('messages').appendChild(messageElement); } } // Uso del cliente const chat = new ChatClient('http://localhost:3000', userToken); chat.joinRoom('general'); ``` ## Monitoreo y Métricas ### Dashboard de Conexiones ```javascript // Métricas en tiempo real class WebSocketMetrics { constructor(io) { this.io = io; this.metrics = { totalConnections: 0, activeConnections: 0, messagesSent: 0, messagesReceived: 0, errors: 0 }; this.setupMetrics(); } setupMetrics() { this.io.on('connection', (socket) => { this.metrics.totalConnections++; this.metrics.activeConnections++; socket.on('disconnect', () => { this.metrics.activeConnections--; }); socket.onAny(() => { this.metrics.messagesReceived++; }); socket.onAnyOutgoing(() => { this.metrics.messagesSent++; }); }); } getMetrics() { return { ...this.metrics, rooms: this.io.sockets.adapter.rooms.size, namespaces: Object.keys(this.io._nsps).length }; } } // Endpoint para métricas app.get('/metrics/websockets', (req, res) => { const metrics = wsMetrics.getMetrics(); res.json(metrics); }); ``` ### Logs Estructurados ```javascript // Logger para WebSockets const winston = require('winston'); const wsLogger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.File({ filename: 'websockets.log' }), new winston.transports.Console() ] }); // Middleware de logging io.use((socket, next) => { wsLogger.info('Nueva conexión', { socketId: socket.id, userId: socket.user?.id, ip: socket.handshake.address, userAgent: socket.handshake.headers['user-agent'] }); next(); }); // Log de eventos socket.onAny((eventName, ...args) => { wsLogger.debug('Evento recibido', { socketId: socket.id, event: eventName, data: args }); }); ``` ## Testing ### Tests de Integración ```javascript const { createServer } = require('http'); const { Server } = require('socket.io'); const Client = require('socket.io-client'); describe('WebSocket Server', () => { let io, serverSocket, clientSocket; beforeAll((done) => { const httpServer = createServer(); io = new Server(httpServer); httpServer.listen(() => { const port = httpServer.address().port; clientSocket = new Client(`http://localhost:${port}`); io.on('connection', (socket) => { serverSocket = socket; }); clientSocket.on('connect', done); }); }); afterAll(() => { io.close(); clientSocket.close(); }); test('should communicate', (done) => { clientSocket.on('hello', (arg) => { expect(arg).toBe('world'); done(); }); serverSocket.emit('hello', 'world'); }); test('should handle room joining', (done) => { serverSocket.on('join-room', (roomId) => { expect(roomId).toBe('test-room'); done(); }); clientSocket.emit('join-room', 'test-room'); }); }); ``` ### Tests de Carga ```javascript // Test de carga con Artillery /* config: target: 'http://localhost:3000' socketio: transports: ['websocket'] phases: - duration: 60 arrivalRate: 10 name: "Warm up" - duration: 120 arrivalRate: 50 name: "Load test" scenarios: - name: "WebSocket connection test" engine: socketio flow: - emit: channel: "join-room" data: "test-room" - think: 1 - emit: channel: "message" data: message: "Hello from Artillery" - think: 5 */ ``` ## Troubleshooting ### Errores Comunes 1. **Conexión rechazada** - Verificar CORS configuration - Comprobar firewall y puertos - Validar autenticación 2. **Desconexiones frecuentes** - Ajustar timeout settings - Verificar estabilidad de red - Revisar rate limiting 3. **Mensajes perdidos** - Implementar acknowledgments - Usar persistent storage - Verificar order de eventos ### Debugging ```javascript // Habilitar debug mode process.env.DEBUG = 'socket.io:*'; // Logger detallado io.engine.on('connection_error', (err) => { console.log('Error de conexión:', err.req); console.log('Código de error:', err.code); console.log('Mensaje:', err.message); console.log('Contexto:', err.context); }); // Monitorear memoria setInterval(() => { const used = process.memoryUsage(); console.log('Memoria:', { rss: Math.round(used.rss / 1024 / 1024 * 100) / 100, heapTotal: Math.round(used.heapTotal / 1024 / 1024 * 100) / 100, heapUsed: Math.round(used.heapUsed / 1024 / 1024 * 100) / 100, connections: io.engine.clientsCount }); }, 30000); ``` ## API Reference ### Server API #### `io.emit(event, data)` Envía evento a todos los clientes conectados. #### `io.to(room).emit(event, data)` Envía evento a todos los clientes en una sala específica. #### `socket.join(room)` Une el socket a una sala. #### `socket.leave(room)` Remueve el socket de una sala. #### `socket.broadcast.emit(event, data)` Envía evento a todos excepto al socket actual. ### Client API #### `socket.emit(event, data, callback)` Envía evento al servidor con callback opcional. #### `socket.on(event, handler)` Escucha eventos del servidor. #### `socket.disconnect()` Desconecta del servidor. #### `socket.connect()` Reconecta al servidor. ## Contribuir Para contribuir a este módulo: 1. Fork el repositorio 2. Crea una rama para tu feature 3. Añade tests para nuevas funcionalidades 4. Ejecuta `npm test` para verificar 5. Crea un Pull Request ## Licencia MIT License - ver archivo LICENSE para más detalles. ## 🔗 Enlaces - [Volver al índice de módulos](./README.md) - [Documentación principal](../README.md) - [Código fuente](../../modules/websockets/)