Documentation Index
Fetch the complete documentation index at: https://mintlify.com/No-Country-simulation/S02-26-Equipo-33-Web-App/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Horse Trust platform uses Socket.io for real-time bidirectional communication, primarily for the chat system and live notifications.
Socket.io Setup
Configured in index.ts with CORS and authentication:
import { Server as SocketServer, Socket } from "socket.io";
import http from "http";
import app from "./app";
const httpServer = http.createServer(app);
const io = new SocketServer(httpServer, {
cors: {
origin: (process.env.CORS_ORIGINS || "http://localhost:5173").split(","),
credentials: true,
},
});
Reference: index.ts:11-20
Authentication Middleware
Socket connections require JWT authentication:
import jwt, { JwtPayload } from "jsonwebtoken";
io.use((socket: Socket, next) => {
const token = socket.handshake.auth?.token as string | undefined;
if (!token) {
return next(new Error("Authentication error: no token"));
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as JwtPayload;
(socket as Socket & { user: JwtPayload }).user = decoded;
next();
} catch {
next(new Error("Authentication error: invalid token"));
}
});
Reference: index.ts:22-35
Client Connection Example:
import { io } from "socket.io-client";
const socket = io("http://localhost:3000", {
auth: {
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
});
Connection Handler
When a user connects, they automatically join their personal room:
io.on("connection", (socket: Socket) => {
const user = (socket as Socket & { user: JwtPayload }).user;
console.log(`🔌 Socket connected: userId=${user.userId}`);
// Join a room per user (so we can target recipients)
socket.join(`user:${user.userId}`);
// Event handlers...
socket.on("disconnect", () => {
console.log(`🔌 Socket disconnected: userId=${user.userId}`);
});
});
Reference: index.ts:38-123
Event Handlers
join_conversation
Join a conversation room to receive messages:
socket.on("join_conversation", (conversationId: string) => {
socket.join(`conv:${conversationId}`);
console.log(` User ${user.userId} joined conv:${conversationId}`);
});
Reference: index.ts:46-49
Client Example:
socket.emit("join_conversation", "conversation_id_123");
send_message
Send a message in a conversation:
socket.on(
"send_message",
async (data: { conversation_id: string; text: string }, ack?: (res: unknown) => void) => {
try {
// Verify user is participant
const conversation = await Conversation.findOne({
_id: data.conversation_id,
participants: user.userId,
});
if (!conversation) {
if (ack) ack({ success: false, message: "Conversation not found" });
return;
}
// Persist to DB
const message = await Message.create({
conversation_id: data.conversation_id,
sender_id: user.userId,
text: data.text,
is_read: false,
});
// Update last_message snapshot
await Conversation.findByIdAndUpdate(data.conversation_id, {
last_message: {
text: data.text,
sender_id: user.userId,
sent_at: message.sent_at,
is_read: false,
},
updated_at: new Date(),
});
const populated = await message.populate("sender_id", "full_name profile_picture_url");
// Emit to all in the conversation room
io.to(`conv:${data.conversation_id}`).emit("new_message", populated);
// Also notify recipient's personal room (for badge/notification)
const recipientId = conversation.participants.find(
(p) => p.toString() !== user.userId
);
if (recipientId) {
io.to(`user:${recipientId}`).emit("message_notification", {
conversation_id: data.conversation_id,
sender: user.userId,
preview: data.text.substring(0, 60),
});
}
if (ack) ack({ success: true, data: populated });
} catch (err) {
console.error("Socket send_message error:", err);
if (ack) ack({ success: false, message: "Error sending message" });
}
}
);
Reference: index.ts:52-109
Client Example with Acknowledgment:
socket.emit(
"send_message",
{
conversation_id: "conv_123",
text: "Hello, is this horse still available?"
},
(response) => {
if (response.success) {
console.log("Message sent:", response.data);
} else {
console.error("Error:", response.message);
}
}
);
typing
Indicates the user is typing:
socket.on("typing", (conversationId: string) => {
socket.to(`conv:${conversationId}`).emit("user_typing", { userId: user.userId });
});
Reference: index.ts:112-114
Client Example:
// User starts typing
socket.emit("typing", "conversation_id_123");
// Listen for typing indicators
socket.on("user_typing", (data) => {
console.log(`User ${data.userId} is typing...`);
});
stop_typing
Indicates the user stopped typing:
socket.on("stop_typing", (conversationId: string) => {
socket.to(`conv:${conversationId}`).emit("user_stop_typing", { userId: user.userId });
});
Reference: index.ts:116-118
Client Example:
// User stops typing
socket.emit("stop_typing", "conversation_id_123");
// Listen for stop typing
socket.on("user_stop_typing", (data) => {
console.log(`User ${data.userId} stopped typing`);
});
Room Structure
The socket implementation uses two types of rooms:
User Rooms
Format: user:{userId}
- Users automatically join their personal room on connection
- Used for personal notifications and badges
- Example:
user:507f1f77bcf86cd799439011
Conversation Rooms
Format: conv:{conversationId}
- Users join when opening a conversation
- Used for broadcasting messages to all participants
- Example:
conv:507f191e810c19729de860ea
Client Events to Listen For
new_message
Received when a new message is sent in a joined conversation:
socket.on("new_message", (message) => {
console.log("New message:", message);
// message structure:
// {
// _id: "msg_id",
// conversation_id: "conv_id",
// sender_id: { _id: "user_id", full_name: "John Doe", profile_picture_url: "..." },
// text: "Message text",
// is_read: false,
// sent_at: "2024-01-15T10:30:00.000Z"
// }
});
Reference: index.ts:89
message_notification
Received in the user’s personal room when a message is sent to them:
socket.on("message_notification", (notification) => {
console.log("New message notification:", notification);
// notification structure:
// {
// conversation_id: "conv_id",
// sender: "user_id",
// preview: "Hello, is this horse still available?"
// }
// Show badge or notification UI
});
Reference: index.ts:96-100
user_typing
Received when another user in the conversation starts typing:
socket.on("user_typing", (data) => {
console.log(`User ${data.userId} is typing...`);
// Show typing indicator in UI
});
Reference: index.ts:113
user_stop_typing
Received when another user stops typing:
socket.on("user_stop_typing", (data) => {
console.log(`User ${data.userId} stopped typing`);
// Hide typing indicator in UI
});
Reference: index.ts:117
Complete Client Integration Example
import { io } from "socket.io-client";
const token = localStorage.getItem("jwt_token");
const socket = io("http://localhost:3000", {
auth: { token }
});
// Handle connection
socket.on("connect", () => {
console.log("Connected to server");
// Join conversation room
socket.emit("join_conversation", conversationId);
});
// Listen for new messages
socket.on("new_message", (message) => {
addMessageToUI(message);
});
// Listen for notifications
socket.on("message_notification", (notification) => {
showNotificationBadge(notification.conversation_id);
});
// Listen for typing indicators
socket.on("user_typing", (data) => {
showTypingIndicator(data.userId);
});
socket.on("user_stop_typing", (data) => {
hideTypingIndicator(data.userId);
});
// Send message
function sendMessage(text: string) {
socket.emit(
"send_message",
{ conversation_id: conversationId, text },
(response) => {
if (response.success) {
console.log("Message sent successfully");
} else {
console.error("Failed to send message:", response.message);
}
}
);
}
// Handle typing
let typingTimeout: NodeJS.Timeout;
function handleTyping() {
socket.emit("typing", conversationId);
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
socket.emit("stop_typing", conversationId);
}, 1000);
}
// Handle disconnection
socket.on("disconnect", () => {
console.log("Disconnected from server");
});
// Handle errors
socket.on("connect_error", (error) => {
console.error("Connection error:", error.message);
});
Error Handling
Socket authentication errors:
socket.on("connect_error", (error) => {
if (error.message === "Authentication error: no token") {
// Redirect to login
} else if (error.message === "Authentication error: invalid token") {
// Token expired, refresh or redirect to login
}
});