WebSocket en TypeScript: Construyendo aplicaciones avanzadas en tiempo real
\n\nLas aplicaciones en tiempo real se han convertido en una parte esencial de la web moderna. Desde chats y notificaciones hasta juegos en línea y plataformas de trading, la tecnología WebSocket proporciona una conexión bidireccional permanente entre el cliente y el servidor. A nivel avanzado, el desarrollo de estos sistemas requiere no solo comprender el protocolo, sino también saber construir arquitecturas tolerantes a fallos y escalables. En este artículo, analizaremos cómo crear aplicaciones WebSocket de grado de producción en TypeScript, utilizando las mejores prácticas y patrones.
\n\n1. Arquitectura y clustering del servidor WebSocket
\n\nUn único servidor WebSocket no soportará la carga de un proyecto real. La tarea clave es organizar el clustering y el intercambio de mensajes entre instancias. Consideremos la arquitectura utilizando la biblioteca ws y el bus de eventos Redis.
Instalemos las dependencias:
\nnpm install ws redis ioredis @types/ws @types/ioredis\n\nCreemos un servidor clusterizado básico:
\n\nimport { WebSocketServer, WebSocket } from 'ws';\nimport Redis from 'ioredis';\n\nconst redisPub = new Redis();\nconst redisSub = new Redis();\n\nconst wss = new WebSocketServer({ port: 8080 });\n\n// Canal para comunicación entre servidores\nconst CHANNEL = 'ws:messages';\n\nredisSub.subscribe(CHANNEL, (err, count) => {\n if (err) console.error('Error de suscripción:', err);\n console.log(`Suscrito a ${count} canales`);\n});\n\n// Recibir mensajes de otras instancias\nredisSub.on('message', (channel, message) => {\n const parsed = JSON.parse(message);\n wss.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(parsed.data);\n }\n });\n});\n\nwss.on('connection', (ws) => {\n console.log('Nueva conexión');\n\n ws.on('message', (data) => {\n const message = data.toString();\n // Publicar en Redis para todas las instancias\n redisPub.publish(CHANNEL, JSON.stringify({ data: message }));\n });\n});\n\nVentajas:
\n- \n
- Escalabilidad horizontal: agregue tantos servidores como desee \n
- Tolerancia a fallos: si una instancia falla, las demás continúan funcionando \n
- Bus de mensajes unificado a través de Redis Pub/Sub \n
2. Autenticación y gestión de sesiones
\n\nEn aplicaciones de producción, no se puede confiar en conexiones no autenticadas. Implementemos la verificación de tokens mediante JWT al establecer la conexión.
\n\nInstalemos jsonwebtoken:
npm install jsonwebtoken @types/jsonwebtoken\n\nCreemos un middleware para la autenticación WebSocket:
\n\nimport { IncomingMessage } from 'http';\nimport jwt from 'jsonwebtoken';\n\nconst JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';\n\ninterface AuthenticatedSocket extends WebSocket {\n userId?: string;\n}\n\nfunction authenticate(ws: AuthenticatedSocket, req: IncomingMessage): boolean {\n const url = new URL(req.url || '', 'http://localhost');\n const token = url.searchParams.get('token');\n\n if (!token) {\n ws.close(4001, 'Token no proporcionado');\n return false;\n }\n\n try {\n const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };\n ws.userId = decoded.userId;\n console.log(`Usuario ${decoded.userId} autenticado`);\n return true;\n } catch (err) {\n ws.close(4002, 'Token inválido');\n return false;\n }\n}\n\n// Uso\nwss.on('connection', (ws: AuthenticatedSocket, req) => {\n if (!authenticate(ws, req)) return;\n\n ws.on('message', (data) => {\n // Ahora conocemos el userId\n console.log(`Mensaje de ${ws.userId}: ${data}`);\n });\n});\n\nRecomendaciones:
\n- \n
- Transmita el token mediante un parámetro de consulta o encabezados al establecer la conexión \n
- Utilice tokens de acceso de corta duración y tokens de actualización \n
- Almacene las sesiones en Redis \n