Construyendo un Servidor WebSocket Universal para 7u.pl
Nota: La versión en polaco es la original. Esta traducción ha sido generada por IA y puede contener errores menores.

A medida que añadimos más microherramientas interactivas al ecosistema 7u.pl, surgió la necesidad de sincronizar el estado en tiempo real entre dispositivos completamente separados.
Por ejemplo: nuestro proyecto virtual-keyboard debe enviar eventos ("botón Q presionado") desde un teléfono inteligente a un navegador en una Mac, directamente a una Raspberry Pi física, eludiendo las restricciones de la red local en ambos lados.
En lugar de construir la lógica de los WebSockets por separado para cada aplicación (lo que rápidamente llevaría a la duplicación de infraestructura), decidimos crear un servidor de comunicación único, independiente y muy "tonto" que hace solo una cosa, pero la hace sin problemas: escucha en salas (rooms) y transmite (broadcast) mensajes entre los clientes.
La Arquitectura de un Servidor "Tonto"
La clave de la universalidad es la falta de lógica empresarial en el lado del servidor. El servidor no sabe qué es un "teclado", qué es un "ratón" ni cómo es la estructura del JSON enviado a través de la red.
Elección de Tecnología
Nos decidimos por Node.js combinado con Socket.IO. Aunque los WebSockets nativos (ws) son más ligeros, Socket.IO tiene un excelente mecanismo incorporado para organizar conexiones en las llamadas Rooms (Salas), lo que se adapta perfectamente a nuestras necesidades de aislar diferentes aplicaciones. Además, nos libera del problema de las reconexiones automáticas en dispositivos móviles con una conexión inestable.
Código Fuente en Resumen
El núcleo de todo el servidor está contenido en solo un par de detectores de eventos (listeners) en la instancia de io:
io.on('connection', (socket) => {
// Une al cliente a una sala virtual
socket.on('join-room', (roomName) => {
socket.join(roomName);
});
// Transmite un mensaje a otros en la sala (ignorando al remitente)
socket.on('broadcast', ({ roomName, eventName, payload }) => {
socket.to(roomName).emit(eventName, payload);
});
// Transmite un mensaje a todos en la sala (incluyendo al remitente)
socket.on('broadcast-all', ({ roomName, eventName, payload }) => {
io.to(roomName).emit(eventName, payload);
});
});Eso es literalmente todo. El servidor acepta cualquier objeto (payload), encuentra una sala (roomName), lo envuelve en un evento especificado por el desarrollador (eventName) y lo envía al resto de los clientes.
Seguridad a través de CORS
Aunque el servidor no sabe nada sobre los datos transmitidos en sí, queríamos asegurarnos de que nuestro endpoint público no se convirtiera en un servidor proxy gratuito para la mitad de Internet.
La implementación requirió una regla estricta para validar el encabezado Origin, verificando la conexión en la etapa más temprana del llamado apretón de manos (handshake):
const io = new Server(httpServer, {
cors: {
origin: (origin, callback) => {
// Permitir la ausencia de Origin (dispositivos IoT/scripts)
if (!origin) return callback(null, true);
// Permitidos: *.7u.pl y entornos locales
const allowedOrigins = [
/^https?:\/\/((.*)\.)?7u\.pl$/,
/^http:\/\/localhost(:\d+)?$/,
/^http:\/\/127\.0\.0\.1(:\d+)?$/
];
if (allowedOrigins.some((regex) => regex.test(origin))) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}
});Gracias al uso de expresiones regulares (Regex) en lugar de cadenas rígidas, dominios como sklep.7u.pl, así como puertos de desarrollo (por ejemplo, localhost:5173 utilizado por el ecosistema Vite) están autorizados dinámicamente, sin la necesidad de reiniciar y reconfigurar el servidor para cada proyecto nuevo.
Implementación y Mantenimiento (Docker)
El servidor pesa solo unos pocos megabytes. La imagen de Docker se construyó en base al entorno node:20-alpine.
FROM node:20-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . .
EXPOSE 80
CMD ["npm", "start"]En la máquina de producción, la imagen se oculta detrás de un balanceador de carga (NGINX/Cloudflare), que maneja la terminación definitiva del certificado SSL (wss://sock.7u.pl). Dicha configuración significa que la nueva infraestructura se ha vuelto completamente invisible ("transparente") para futuras modificaciones.
Resumen y Beneficios
Mover el punto de relevo de los WebSockets ("relay") a un repositorio separado nos liberó de:
- Tener backends obligados a escuchar en puertos solo para que los clientes "solo front-end" pudieran verse entre sí.
- Recrear sistemas de autenticación desde cero.
- Mantener deuda técnica innecesaria en los proyectos.
A partir de ahora, cada próximo proyecto interactivo bajo el dominio *.7u.pl recibirá la funcionalidad en tiempo real tras añadir solo cinco líneas de importación. Y de eso se trata precisamente una buena arquitectura.