WebSocket Events¶
Relevant source files * src/sockets/socketHandler.js * views/soporte.ejs
Purpose and Scope¶
This document provides a complete reference for all Socket.IO events used in the real-time communication system. It documents the event names, payload structures, expected behaviors, and the flow of messages between clients and the server.
For information about HTTP API endpoints used to retrieve message history, see HTTP Endpoints. For architectural details about the real-time communication system, including room management and authentication flow, see Real-time Communication System.
Sources: src/sockets/socketHandler.js L1-L71
Authentication¶
All WebSocket connections must be authenticated before any events can be exchanged. Authentication occurs automatically when a client establishes a connection, using JWT tokens stored in HTTP-only cookies.
Authentication Flow¶
sequenceDiagram
participant Socket.IO Client
participant io.use() Middleware
participant jwt.verify()
participant Connection Handler
Socket.IO Client->>io.use() Middleware: "Connect with cookies"
io.use() Middleware->>io.use() Middleware: "Extract token from cookie"
loop [No cookies]
io.use() Middleware-->>Socket.IO Client: "Error: No autenticado"
io.use() Middleware-->>Socket.IO Client: "Error: Token no proporcionado"
io.use() Middleware->>jwt.verify(): "verify(token, JWT_SECRET)"
jwt.verify()-->>io.use() Middleware: "throw Error"
io.use() Middleware-->>Socket.IO Client: "Error: Token inválido"
end
jwt.verify()-->>io.use() Middleware: "decoded user data"
io.use() Middleware->>io.use() Middleware: "Attach user to socket.request.user"
io.use() Middleware->>Connection Handler: "next()"
Connection Handler->>Connection Handler: "socket.join() rooms"
Connection Handler-->>Socket.IO Client: "Connection established"
The middleware extracts the JWT from the token cookie using a regex pattern: /token=([^;]+)/. The decoded token contains user, name, and rol fields, which are attached to socket.request.user for use in subsequent event handlers.
Sources: src/sockets/socketHandler.js L6-L32
Connection Event¶
The connection event fires when a client successfully authenticates and establishes a WebSocket connection.
Server-Side Handler¶
Event Name: connection (Socket.IO reserved event)
Payload: Socket object with authenticated user data in socket.request.user
Behavior:
- Extracts user information from
socket.request.user:user(username),name,rol(role) - Logs the connection with user and role information
- Joins the socket to rooms based on user identity and role: * Personal room:
user:{username}- Every user gets their own room * Admin room:admins- Only users withrol === "admin"join this room
Room Assignment Logic:
| User Role | Personal Room | Admin Room |
|---|---|---|
user |
user:{username} |
No |
admin |
user:{username} |
admins |
Code Reference: src/sockets/socketHandler.js L36-L43
Sources: src/sockets/socketHandler.js L36-L43
Client-to-Server Events¶
mensaje_privado¶
The client emits this event to send a message to another user.
Event Name: mensaje_privado
Direction: Client → Server
Payload Structure:
{
para: string, // Recipient username
mensaje: string // Message content
}
Payload Fields:
| Field | Type | Required | Description |
|---|---|---|---|
para |
string | Yes | Username of the intended recipient |
mensaje |
string | Yes | The message text to send |
Server Behavior:
- Extracts sender username from authenticated user:
const de = user; - Emits
mensaje_recibidoto recipient's personal room:io.to('user:${para}').emit(...) - If sender is NOT an admin (
rol !== "admin"), also broadcasts to all admins:io.to("admins").emit(...) - Persists message to database using INSERT query to
mensajestable with fields(de_usuario, para_usuario, mensaje)
Message Routing Logic:
flowchart TD
Client["Client emits<br>mensaje_privado"]
CheckRole["Sender<br>role?"]
SendToRecipient["Emit to<br>user:{para}"]
SendToAdmins["Emit to<br>admins room"]
SaveDB["INSERT INTO mensajes"]
Client --> CheckRole
CheckRole --> SendToRecipient
CheckRole --> SendToRecipient
CheckRole --> SendToAdmins
SendToRecipient --> SaveDB
SendToAdmins --> SaveDB
Client Implementation Examples:
Admin sending a message:
// From views/soporte.ejs:156
socket.emit("mensaje_privado", { para, mensaje });
Regular user sending to admin:
// From views/soporte.ejs:184
socket.emit("mensaje_privado", { para: "admin", mensaje });
Sources: src/sockets/socketHandler.js L45-L63
Server-to-Client Events¶
mensaje_recibido¶
The server emits this event to deliver a message to connected clients.
Event Name: mensaje_recibido
Direction: Server → Client
Payload Structure:
{
de: string, // Sender username
mensaje: string // Message content
}
Payload Fields:
| Field | Type | Description |
|---|---|---|
de |
string | Username of the message sender |
mensaje |
string | The message text |
Emitted To:
This event is emitted to specific Socket.IO rooms depending on the sender's role:
- Always: Recipient's personal room
user:{para} - Conditionally:
adminsroom (only if sender is not an admin)
Client-Side Handlers:
Admin Client:
// From views/soporte.ejs:128-147
socket.on("mensaje_recibido", ({ de, mensaje }) => {
const fecha = new Date();
conversaciones[de] = conversaciones[de] || [];
conversaciones[de].push({ de, mensaje, fecha });
agregarUsuarioLista(de);
// Visual notification if not active conversation
if (usuarioActual.value !== de) {
const li = document.getElementById("user-" + de);
if (li && li.dataset.nuevo !== "true") {
li.classList.remove("text-success");
li.classList.add("fw-bold", "text-danger");
li.dataset.nuevo = "true";
}
} else {
renderConversacion(de); // Already viewing, mark as seen
}
});
Regular User Client:
// From views/soporte.ejs:174-177
socket.on("mensaje_recibido", ({ de, mensaje }) => {
const fecha = new Date();
agregarMensaje(de, mensaje, fecha);
});
Behavior Differences by Role:
| Client Role | Behavior |
|---|---|
admin |
Stores message in conversation map, adds sender to user list if needed, shows notification badge if not viewing that conversation |
user |
Appends message directly to chat display |
Sources: src/sockets/socketHandler.js L48-L52
Disconnect Event¶
The disconnect event fires when a client's WebSocket connection is closed.
Event Name: disconnect (Socket.IO reserved event)
Direction: Client → Server (automatic)
Payload: None
Server Behavior:
Logs the disconnection with the username of the disconnected user. No additional cleanup is performed as Socket.IO automatically removes the socket from all rooms.
Code Reference: src/sockets/socketHandler.js L65-L67
Sources: src/sockets/socketHandler.js L65-L67
Complete Event Flow¶
User-to-Admin Message Flow¶
sequenceDiagram
participant User Client
participant (role=user)
participant Socket.IO Server
participant socketHandler.js
participant MySQL
participant mensajes table
participant Admin Client
participant (role=admin)
User Client->>Socket.IO Server: "emit('mensaje_privado',
Socket.IO Server->>Socket.IO Server: {para:'admin', mensaje})"
Socket.IO Server->>Admin Client: "Extract de from
Socket.IO Server->>Admin Client: socket.request.user"
Socket.IO Server->>MySQL: "to('user:admin').emit(
MySQL-->>Socket.IO Server: 'mensaje_recibido', {de, mensaje})"
Admin Client->>Admin Client: "to('admins').emit(
Admin Client->>Admin Client: 'mensaje_recibido', {de, mensaje})"
Admin-to-User Message Flow¶
sequenceDiagram
participant Admin Client
participant (role=admin)
participant Socket.IO Server
participant socketHandler.js
participant MySQL
participant mensajes table
participant User Client
participant (role=user)
Admin Client->>Socket.IO Server: "emit('mensaje_privado',
Socket.IO Server->>Socket.IO Server: {para:username, mensaje})"
Socket.IO Server->>User Client: "Extract de from
note over Socket.IO Server,(role=user): "No broadcast to admins
Socket.IO Server->>MySQL: socket.request.user"
MySQL-->>Socket.IO Server: "to('user:username').emit(
User Client->>User Client: 'mensaje_recibido', {de, mensaje})"
Sources: src/sockets/socketHandler.js L45-L63
Event Summary Table¶
| Event Name | Direction | Emitter | Handler Location | Persisted to DB |
|---|---|---|---|---|
connection |
Server | Socket.IO | socketHandler.js L36 | No |
mensaje_privado |
Client→Server | Client code | socketHandler.js L45 | Yes |
mensaje_recibido |
Server→Client | Server code | soporte.ejs L128-L174 | No (payload only) |
disconnect |
Client→Server | Socket.IO | socketHandler.js L65 | No |
Sources: src/sockets/socketHandler.js L1-L71
Code Entity Reference¶
Server-Side Entities¶
| Entity | Type | Location | Purpose |
|---|---|---|---|
setupSocket |
Function | socketHandler.js L4 | Initializes Socket.IO server and event handlers |
io.use() |
Middleware | socketHandler.js L6 | Authenticates WebSocket connections via JWT |
io.on("connection") |
Event Handler | socketHandler.js L36 | Handles new client connections and room assignments |
socket.on("mensaje_privado") |
Event Handler | socketHandler.js L45 | Processes incoming messages and routes to recipients |
socket.on("disconnect") |
Event Handler | socketHandler.js L65 | Handles client disconnections |
socket.join() |
Method | socketHandler.js L42-L43 | Adds socket to room(s) |
io.to() |
Method | socketHandler.js L48-L51 | Targets message emission to specific room |
Client-Side Entities¶
| Entity | Type | Location | Purpose |
|---|---|---|---|
io() |
Constructor | soporte.ejs L39 | Creates Socket.IO client instance |
socket.emit() |
Method | soporte.ejs L156-L184 | Sends events to server |
socket.on() |
Method | soporte.ejs L128-L174 | Registers event listeners |
agregarMensaje() |
Function | soporte.ejs L54 | Renders message in chat UI |
renderConversacion() |
Function | soporte.ejs L69 | Displays full conversation for selected user (admin only) |
conversaciones |
Object | soporte.ejs L65 | Stores message history by user (admin only) |
Sources: src/sockets/socketHandler.js L1-L71
Database Persistence¶
Messages sent via mensaje_privado are persisted to the mensajes table using the following SQL query:
INSERT INTO mensajes (de_usuario, para_usuario, mensaje) VALUES (?, ?, ?)
Parameters:
de_usuario- Sender username (fromsocket.request.user.user)para_usuario- Recipient username (from event payloadparafield)mensaje- Message content (from event payloadmensajefield)
The fecha column is automatically set to the current timestamp via database defaults. Error handling logs failures but does not notify the client.
For retrieving message history, clients use HTTP GET endpoints documented in HTTP Endpoints.
Sources: src/sockets/socketHandler.js L55-L62