Documentation Index
Fetch the complete documentation index at: https://mintlify.com/abdelhafid37/talkbox/llms.txt
Use this file to discover all available pages before exploring further.
TalkBox tracks which users are currently connected by maintaining a server-side Map that pairs each user’s MongoDB _id with their active socket ID. Whenever a user joins or disconnects, the server broadcasts the updated list of online user IDs to every connected client so the sidebar can immediately reflect who is available.
How presence works
The server module socket.js declares a module-level Map that lives for the lifetime of the Node.js process:
const onlineUsers = new Map();
Each entry maps a userId string (the MongoDB _id converted via .toString()) to the corresponding socketId. Because one user maps to exactly one socket, only one active session per user is tracked at a time.
The join event
After the socket connection is established, AuthProvider immediately emits a "join" event. The server handler adds the user to onlineUsers and broadcasts the refreshed list to every client:
socket.on("join", () => {
console.log(
`[SOCKET.IO] [JOIN] ${socket.user.username} joined with socket ${socket.id}.`
);
onlineUsers.set(socket.user._id.toString(), socket.id);
emitOnlineUsers();
});
emitOnlineUsers converts the Map’s keys (all connected user IDs) into a plain array and broadcasts it on the "onlineUsers" event:
function emitOnlineUsers() {
io.emit("onlineUsers", [...onlineUsers.keys()]);
}
Using io.emit (rather than socket.emit) ensures every connected client receives the update simultaneously — including the user who just joined.
The disconnect event
When a socket closes — whether from a browser tab close, logout, or network interruption — the server iterates the Map to find and remove the matching entry, then broadcasts the updated list:
socket.on("disconnect", () => {
console.log(`[SOCKET.IO] [DISCONNECT] ${socket.id} disconnect.`);
for (const [userId, socketId] of onlineUsers) {
if (socketId === socket.id) {
onlineUsers.delete(userId);
emitOnlineUsers();
break;
}
}
});
Iterating by socketId rather than looking up by userId is important: the socket.user object is not reliably accessible inside a disconnect handler, so the reverse lookup by socket ID is the safe approach.
Client-side usage
Receive the online users list
AuthProvider listens for the "onlineUsers" event and stores the received array in React state. The array contains MongoDB _id strings for every user who is currently connected:socket.on("onlineUsers", (users) => {
setOnlineUsers(users);
});
The onlineUsers value is exposed through AuthContext and is accessible anywhere in the tree via the useAuth hook:const { onlineUsers } = useAuth();
Show online indicators
ChatSidebar reads onlineUsers from the context and checks whether each user’s _id is present in the array. If it is, an online indicator dot is rendered next to their name.A typical check looks like:const isOnline = onlineUsers.includes(user._id);
Clear presence on logout
When the user logs out, AuthProvider calls socket.disconnect() (which triggers the server-side disconnect handler) and clears the local onlineUsers state:function logout() {
localStorage.removeItem("token");
setTokenState(null);
setUser(null);
setOnlineUsers([]);
}
Full server-side socket code
Below is the complete presence-related portion of socket.js for reference:
const onlineUsers = new Map();
function initializeSocket(server) {
const io = new Server(server, {
cors: {
origin: process.env.CLIENT_URL,
},
});
// Socket.IO auth middleware — verifies JWT on every new connection
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth?.token;
if (!token) return next(new Error("Unauthorized."));
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId);
if (!user) return next(new Error("Unauthorized."));
socket.user = user;
next();
} catch (error) {
next(new Error("Unauthorized."));
}
});
function emitOnlineUsers() {
io.emit("onlineUsers", [...onlineUsers.keys()]);
}
io.on("connection", (socket) => {
socket.on("join", () => {
onlineUsers.set(socket.user._id.toString(), socket.id);
emitOnlineUsers();
});
socket.on("disconnect", () => {
for (const [userId, socketId] of onlineUsers) {
if (socketId === socket.id) {
onlineUsers.delete(userId);
emitOnlineUsers();
break;
}
}
});
});
}
Limitations and scaling considerations
The onlineUsers Map is held entirely in memory inside the Node.js process. If the server restarts, all presence data is lost and clients must reconnect and re-emit "join" to repopulate it. More critically, if you run multiple server instances behind a load balancer, each instance maintains its own isolated Map — users connected to different instances will appear offline to each other.
For horizontal scaling, replace the in-memory Map with a shared store using the Socket.IO Redis Adapter. The adapter synchronises events across all server instances so presence broadcasts reach every connected client regardless of which instance they landed on.npm install @socket.io/redis-adapter redis
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));