Implement Private Messaging Using Socket IDs

Socket.IO automatically creates a private room for each connected socket using its socket ID.

This means you can send an event to one specific connection using:

io.to(socketId).emit('private:message', payload);

However, socket IDs are temporary connection identifiers. They can change after reconnection and should not be treated as permanent user IDs.

For a basic demonstration, we can maintain a mapping between usernames and socket IDs.

In a production application, use authenticated user IDs and support multiple active connections per user.

Important Production Limitation

Do not trust a client-provided username as an authenticated identity. Use a verified user ID from server-side authentication. Also support multiple sockets per user because a user may have multiple browser tabs or devices connected.

Basic Private Messaging
const onlineUsers = new Map();

io.on('connection', (socket) => {
  socket.on('user:register', (username) => {
    if (
      typeof username !== 'string' ||
      username.trim().length === 0 ||
      username.length > 30
    ) return;

    const normalizedUsername = username.trim().toLowerCase();
    onlineUsers.set(normalizedUsername, socket.id);

    socket.data.username = normalizedUsername;
  });

  socket.on('private:message', (payload, acknowledge) => {
    const recipient = payload?.recipient;
    const message = payload?.message;

    if (
      typeof recipient !== 'string' ||
      typeof message !== 'string' ||
      message.trim().length === 0 ||
      message.length > 1000
    ) {
      acknowledge?.({ success: false, error: 'Invalid message' });
      return;
    }

    const recipientSocketId = onlineUsers.get(recipient.toLowerCase());

    if (!recipientSocketId) {
      acknowledge?.({ success: false, error: 'User is offline' });
      return;
    }

    io.to(recipientSocketId).emit('private:message', {
      sender: socket.data.username,
      message: message.trim(),
      timestamp: new Date().toISOString()
    });

    acknowledge?.({ success: true });
  });

  socket.on('disconnect', () => {
    if (socket.data.username) {
      onlineUsers.delete(socket.data.username);
    }
  });
});

This demonstration maintains a username-to-socket mapping and uses the recipient socket ID to deliver private messages. It supports only one connection per username and is not a production identity system.

Key Takeaways
  • Socket IDs can be used to target individual connections.

  • Socket IDs are not permanent user identities.

  • Production messaging should use authenticated user IDs.