@jamx-framework/realtime
v2.0.0
Published
JAMX Framework — WebSockets and Server-Sent Events
Maintainers
Readme
@jamx-framework/realtime
Descripción
Módulo de comunicación en tiempo real para JAMX Framework. Proporciona capacidades de WebSocket, Server-Sent Events (SSE) y presencia (presence) para construir aplicaciones con actualizaciones en tiempo real, chat, notificaciones push, y colaboración en vivo. Soporta tanto servidor como cliente, con manejo automático de reconexión, canales (rooms) y broadcast.
Cómo funciona
El módulo implementa un sistema de eventos en tiempo real con múltiples transportes:
- WebSocketServer/Client: Comunicación bidireccional full-duplex
- SSEServer: Server-Sent Events para push unidireccional desde servidor
- EventEmitter: Emisión y suscripción a eventos con canales
- Presence: Sistema de presencia para rastrear usuarios conectados y su estado
Componentes principales
- src/websocket-server.ts: Servidor WebSocket que maneja conexiones y broadcast
- src/websocket-client.ts: Cliente WebSocket para navegadores/Node
- src/sse-server.ts: Servidor SSE para eventos server → client
- src/event-emitter.ts: Emisor de eventos con canales (rooms)
- src/presence.ts: Gestor de presencia (usuarios en línea, estados)
- src/types.ts: Tipos compartidos (Message, Channel, Presence, etc.)
- src/index.ts: Punto de exportación
Uso básico
import { WebSocketServer, EventEmitter } from '@jamx-framework/realtime';
// Crear servidor WebSocket
const wss = new WebSocketServer({ port: 8080 });
// Emisor de eventos
const emitter = new EventEmitter();
// Suscribirse a canal
emitter.on('chat:general', (message) => {
console.log('Mensaje en canal general:', message);
});
// Publicar en canal
emitter.emit('chat:general', { user: 'Juan', text: '¡Hola!' });
// Con WebSocket, broadcast a todos los conectados
wss.onConnection((ws) => {
ws.onMessage((data) => {
const msg = JSON.parse(data);
// Broadcast a todos los clientes
wss.broadcast(JSON.stringify(msg));
});
});Ejemplos
Servidor WebSocket completo
import { WebSocketServer, EventEmitter } from '@jamx-framework/realtime';
const wss = new WebSocketServer({ port: 8080 });
const emitter = new EventEmitter();
// Manejar conexiones
wss.onConnection((ws, req) => {
const userId = req.headers['x-user-id'];
// Unir a canal de chat
ws.join('chat:general');
// Escuchar mensajes
ws.onMessage((data) => {
const message = JSON.parse(data);
// Validar y procesar
if (message.type === 'chat') {
// Broadcast a todos en el canal
wss.broadcastTo('chat:general', JSON.stringify({
type: 'chat',
user: userId,
text: message.text,
timestamp: Date.now(),
}));
}
});
// Manejar disconnect
ws.onClose(() => {
wss.leave('chat:general', ws);
});
});
console.log('WebSocket server listening on ws://localhost:8080');Cliente WebSocket
import { WebSocketClient } from '@jamx-framework/realtime';
const client = new WebSocketClient('ws://localhost:8080');
// Conectar
await client.connect();
// Unirse a canal
client.subscribe('chat:general');
// Escuchar mensajes
client.onMessage((data) => {
const msg = JSON.parse(data);
console.log('Mensaje recibido:', msg);
});
// Enviar mensaje
client.send(JSON.stringify({
type: 'chat',
text: '¡Hola a todos!',
}));
// Desconectar
client.disconnect();Server-Sent Events (SSE)
import { SSEServer } from '@jamx-framework/realtime';
const sse = new SSEServer();
// Cliente se conecta a /events
sse.onConnection((req, res) => {
const userId = req.headers['x-user-id'];
// Unir a canal de notificaciones
sse.join(`user:${userId}:notifications`);
// Enviar evento inicial
sse.send(`user:${userId}:notifications`, {
type: 'connected',
message: 'Conectado a notificaciones',
});
// Mantener conexión viva
const interval = setInterval(() => {
sse.send(`user:${userId}:notifications`, { type: 'ping' });
}, 30000);
// Limpiar al desconectar
req.on('close', () => {
clearInterval(interval);
sse.leave(`user:${userId}:notifications`);
});
});
// Desde cualquier parte, enviar notificación a usuario
sse.send(`user:123:notifications`, {
type: 'notification',
title: 'Nuevo mensaje',
body: 'Tienes un mensaje nuevo',
});Sistema de presencia
import { Presence } from '@jamx-framework/realtime';
const presence = new Presence();
// Usuario se conecta
presence.userConnected('user-123', {
status: 'online',
lastSeen: Date.now(),
metadata: { device: 'mobile', location: 'US' },
});
// Usuario se desconecta
presence.userDisconnected('user-123');
// Obtener usuarios en línea
const onlineUsers = await presence.getOnlineUsers();
console.log('Usuarios en línea:', onlineUsers);
// Obtener estado de usuario específico
const status = await presence.getUserStatus('user-123');
console.log('Estado:', status); // 'online' | 'offline' | 'away'
// Escuchar cambios de presencia
presence.onPresenceChange((userId, status) => {
console.log(`Usuario ${userId} cambió a ${status}`);
// Actualizar UI, notificar a otros, etc.
});
// Establecer estado personalizado
await presence.setStatus('user-123', {
status: 'away',
lastSeen: Date.now(),
metadata: { reason: 'inactive' },
});Canales (rooms) y broadcast selectivo
import { EventEmitter } from '@jamx-framework/realtime';
const emitter = new EventEmitter();
// Crear canales
const channel1 = 'room:123';
const channel2 = 'room:456';
// Suscribirse a múltiples canales
emitter.subscribe(['room:123', 'room:456']);
// Escuchar eventos en canal específico
emitter.on('room:123', (data) => {
console.log('Mensaje para room 123:', data);
});
// Broadcast a un canal específico
emitter.broadcastTo('room:123', { message: 'Hello room 123!' });
// Broadcast a todos los canales
emitter.broadcastAll({ message: 'Global announcement' });
// Unirse/irse de canales dinámicamente
emitter.subscribe('dynamic-channel');
emitter.leave('dynamic-channel');Integración con JAMX Server
import { Server } from '@jamx-framework/server';
import { WebSocketServer } from '@jamx-framework/realtime';
const server = new Server();
const wss = new WebSocketServer();
// Middleware que inyecta WebSocket en request
server.use(async (req, res, next) => {
const ws = wss.getConnectionForRequest(req);
if (ws) {
req.locals.ws = ws;
}
next();
});
// Handler que envía notificación via WebSocket
server.post('/notify', async (req, res) => {
const { userId, message } = req.body;
// Enviar notificación en tiempo real
wss.sendToUser(userId, JSON.stringify({
type: 'notification',
message,
}));
res.ok({ sent: true });
});Presencia con WebSocket
import { WebSocketServer, Presence } from '@jamx-framework/realtime';
const wss = new WebSocketServer();
const presence = new Presence();
wss.onConnection((ws, req) => {
const userId = req.headers['x-user-id'];
// Marcar usuario como en línea
presence.userConnected(userId, {
connectedAt: Date.now(),
ip: req.ip,
userAgent: req.headers['user-agent'],
});
// Enviar lista de usuarios en línea
const online = await presence.getOnlineUsers();
ws.send(JSON.stringify({ type: 'presence', users: online }));
ws.onClose(() => {
presence.userDisconnected(userId);
});
});Flujo interno
- Conexión: Cliente se conecta via WebSocket o SSE
- Handshake: Servidor acepta y asigna ID de conexión
- Suscripción: Cliente se suscribe a canales (rooms)
- Emisión: Cualquier parte emite eventos a canales
- Broadcast: Servidor envía mensajes a todos los suscriptores del canal
- Presencia: Sistema rastrea quién está conectado y a qué canales
- Reconexión: Clientes pueden reconectarse automáticamente
- Cleanup: Al desconectar, se limpian suscripciones y presencia
API Reference (Resumen)
WebSocketServer
constructor(options: WebSocketServerOptions)onConnection(handler: (ws: WebSocket, req: Request) => void): voidbroadcast(message: string, exclude?: WebSocket[]): voidbroadcastTo(channel: string, message: string): voidsendToUser(userId: string, message: string): voidgetConnections(): WebSocket[]close(): Promise<void>
WebSocketClient
constructor(url: string, options?: ClientOptions)connect(): Promise<void>disconnect(): voidsubscribe(channel: string): voidunsubscribe(channel: string): voidsend(data: string | object): voidonMessage(handler: (data: any) => void): voidonClose(handler: () => void): voidonError(handler: (err: Error) => void): void
SSEServer
constructor()onConnection(handler: (req: Request, res: Response) => void): voidjoin(channel: string, res: Response): voidleave(channel: string, res: Response): voidsend(channel: string, data: any): voidbroadcast(channel: string, data: any): void
EventEmitter
subscribe(channels: string | string[]): voidunsubscribe(channels: string | string[]): voidon(channel: string, handler: (data: any) => void): voidoff(channel: string, handler?: Function): voidemit(channel: string, data: any): voidbroadcastTo(channel: string, data: any): voidbroadcastAll(data: any): void
Presence
userConnected(userId: string, metadata?: any): voiduserDisconnected(userId: string): voidsetStatus(userId: string, status: PresenceStatus): voidgetUserStatus(userId: string): Promise<PresenceStatus>getOnlineUsers(): Promise<string[]>onPresenceChange(handler: (userId: string, status: string) => void): void
Performance Considerations
- Connection pooling: Los servidores manejan miles de conexiones concurrentes
- Broadcast optimization: Los mensajes a canales se envían solo a suscriptores
- Memory usage: Cada conexión consume memoria; limitar canales por conexión
- Heartbeat: Ping/pong automático para detectar conexiones muertas
- Compression: Soporte para permessage-deflate en WebSocket
Configuration Options
// WebSocketServer
const wss = new WebSocketServer({
port: 8080,
maxConnections: 10000,
pingInterval: 30000, // ms entre pings
pingTimeout: 5000, // timeout de pong
verifyClient: (info) => Promise.resolve(true), // auth custom
channels: ['chat', 'notifications', 'presence'],
});
// WebSocketClient
const client = new WebSocketClient('ws://localhost:8080', {
reconnect: true,
reconnectInterval: 3000,
maxReconnectAttempts: 10,
protocols: ['json', 'binary'],
});Testing
Tests en packages/realtime/tests/unit/:
pnpm testCubre:
- Conexión/desconexión WebSocket
- Suscripción a canales
- Broadcast de mensajes
- Sistema de presencia
- SSE server
- Reconexión automática
Compatibility
- Compatible con Node.js 18+
- Navegadores modernos (WebSocket API, EventSource)
- Funciona con cualquier WebSocket client (ws, socket.io, etc.)
- SSE funciona en IE con polyfill
CLI Integration
jamx realtime:server: Inicia servidor WebSocket/SSEjamx realtime:clients: Muestra clientes conectadosjamx realtime:channels: Lista canales activos y suscriptoresjamx realtime:presence: Muestra usuarios en líneajamx realtime:broadcast <channel> <message>: Envía mensaje a canal
Security Considerations
- Authentication: Verificar identidad en handshake (JWT, session)
- Authorization: Validar permisos para unirse a canales
- Input validation: Sanitizar mensajes entrantes
- Rate limiting: Limitar mensajes por conexión
- Origin check: Validar
Originheader para evitar CSRF - TLS: Usar wss:// en producción
Best Practices
- Canales con nombres jerárquicos:
room:123,user:456:notifications - Mensajes pequeños: No enviar payloads grandes; usar referencias
- Heartbeat: Configurar ping/pong para detectar conexiones caídas
- Graceful shutdown: Cerrar conexiones limpiamente al apagar servidor
- Monitor connections: Rastrear número de conexiones por canal
- Backpressure: Manejar cuando el cliente no puede seguir el ritmo
- Message ordering: Los mensajes en un canal mantienen orden
Troubleshooting
Conexiones se caen
Verificar firewall, proxies, y que el puerto esté abierto.
Mensajes no llegan
Verificar que el cliente esté suscrito al canal correcto.
Alta latencia
Reducir tamaño de mensajes, usar compresión, optimizar broadcast.
Memory leak
Asegurarse de limpiar suscripciones al desconectar.
This realtime module provides a powerful, scalable solution for real-time communication in JAMX applications, enabling features like live chat, notifications, presence, and collaborative editing with minimal code.
