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
Markdown
# 📦 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/)