Presence & Online Status
Menampilkan siapa yang online — green dot di Slack, "last seen" di WhatsApp. Terlihat sederhana tapi surprisingly complex di skala besar.
Basic Implementation
// Server: track connected users
const onlineUsers = new Map(); // userId → Set of socketIds
io.on("connection", (socket) => {
const userId = socket.data.userId;
// Mark online
if (!onlineUsers.has(userId)) onlineUsers.set(userId, new Set());
onlineUsers.get(userId).add(socket.id);
// Broadcast status change
io.emit("presence:online", { userId });
socket.on("disconnect", () => {
onlineUsers.get(userId)?.delete(socket.id);
// Only mark offline if no more connections
if (onlineUsers.get(userId)?.size === 0) {
onlineUsers.delete(userId);
io.emit("presence:offline", { userId });
}
});
});
// API: get online users
app.get("/api/presence", (req, res) => {
res.json([...onlineUsers.keys()]);
});
Heartbeat Pattern
// Client sends heartbeat setiap 30 detik
setInterval(() => {
socket.emit("heartbeat");
}, 30000);
// Server: mark user as "away" jika no heartbeat > 60s
// Status: online → away → offline
Scalable Presence (Redis)
// Single server: Map works
// Multiple servers: use Redis
// On connect
await redis.sadd("online_users", userId);
await redis.set(`presence:${userId}`, JSON.stringify({
status: "online",
lastSeen: Date.now(),
device: socket.handshake.headers["user-agent"],
}));
// On disconnect
await redis.srem("online_users", userId);
await redis.set(`presence:${userId}`, JSON.stringify({
status: "offline",
lastSeen: Date.now(),
}));
// Check status
const isOnline = await redis.sismember("online_users", friendId);
Privacy Considerations
- Let users control visibility ("Invisible mode")
- Show "last seen" only to contacts/friends
- Aggregate presence for large channels ("23 members online")