Skip to main content

Overview

Voxora’s real-time chat system is built on Socket.IO, providing bidirectional, event-based communication between clients and servers. This enables instant message delivery, typing indicators, presence updates, and more.

Architecture

Connection Flow

1. Client Connection

Clients connect to the Socket.IO server with authentication:
import io from 'socket.io-client';

const socket = io('http://localhost:3002', {
  auth: {
    token: 'jwt-token-here'
  },
  transports: ['websocket', 'polling']
});

socket.on('connect', () => {
  console.log('Connected:', socket.id);
});

2. Authentication

1

Token Validation

Server validates JWT token from handshake
2

User Loading

Load user data from database
3

Room Assignment

Join user to appropriate rooms (conversations, departments)
4

Presence Update

Update user online status in Redis

Core Events

Message Events

Client → Server: Send a new message
socket.emit('send_message', {
  conversationId: '123',
  content: 'Hello!',
  type: 'text' // or 'file', 'image'
});

Typing Indicators

// User starts typing
socket.emit('typing_start', {
  conversationId: '123'
});

// User stops typing
socket.emit('typing_stop', {
  conversationId: '123'
});

// Listen for others typing
socket.on('user_typing', ({ userId, userName }) => {
  showTypingIndicator(userName);
});

Presence Events

Track when users come online or go offline:
socket.on('user_online', ({ userId }) => {
  updateUserStatus(userId, 'online');
});

socket.on('user_offline', ({ userId }) => {
  updateUserStatus(userId, 'offline');
});

Rooms & Namespaces

Conversation Rooms

Each conversation has its own room for message broadcasting:
// Join a conversation
socket.on('join_conversation', async ({ conversationId }) => {
  await socket.join(conversationId);
  
  // Send only to this room
  io.to(conversationId).emit('user_joined', {
    userId: socket.userId
  });
});

// Leave a conversation
socket.on('leave_conversation', ({ conversationId }) => {
  socket.leave(conversationId);
});

Department Namespaces

Organize agents by department:
const departmentNamespace = io.of('/department');

departmentNamespace.on('connection', (socket) => {
  const { departmentId } = socket.handshake.query;
  socket.join(`dept_${departmentId}`);
});

Message Persistence

Messages are saved to MongoDB before being broadcast:
socket.on('send_message', async (data) => {
  try {
    // 1. Create message in database
    const message = await Message.create({
      conversationId: data.conversationId,
      senderId: socket.userId,
      content: data.content,
      type: data.type,
      timestamp: new Date()
    });
    
    // 2. Broadcast to conversation room
    io.to(data.conversationId).emit('new_message', message);
    
    // 3. Send delivery confirmation
    socket.emit('message_delivered', message._id);
    
    // 4. Update conversation's last message
    await Conversation.findByIdAndUpdate(
      data.conversationId,
      { lastMessage: message._id, updatedAt: new Date() }
    );
    
  } catch (error) {
    socket.emit('message_error', {
      error: error.message
    });
  }
});

Scalability with Redis

For multi-server deployments, use the Redis adapter:
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();

await Promise.all([pubClient.connect(), subClient.connect()]);

io.adapter(createAdapter(pubClient, subClient));
This allows Socket.IO instances across multiple servers to communicate via Redis pub/sub.

Error Handling

Implement robust error handling for connection issues:
socket.on('connect_error', (error) => {
  console.error('Connection error:', error);
  // Show reconnection UI
});

socket.on('reconnect', (attemptNumber) => {
  console.log('Reconnected after', attemptNumber, 'attempts');
  // Rejoin rooms, sync state
});

socket.on('reconnect_failed', () => {
  console.error('Failed to reconnect');
  // Show offline mode
});

Performance Optimization

1. Message Batching

Batch multiple messages to reduce events:
const messageBuffer = [];
const BATCH_SIZE = 10;
const BATCH_INTERVAL = 100; // ms

socket.on('send_message', (message) => {
  messageBuffer.push(message);
  
  if (messageBuffer.length >= BATCH_SIZE) {
    flushMessages();
  }
});

setInterval(() => {
  if (messageBuffer.length > 0) {
    flushMessages();
  }
}, BATCH_INTERVAL);

2. Binary Data

Use binary transmission for file uploads:
// Client
socket.emit('file_upload', fileBuffer);

// Server
socket.on('file_upload', (buffer) => {
  // Handle binary data
});

3. Compression

Enable per-message deflate compression:
const io = new Server(server, {
  perMessageDeflate: {
    threshold: 1024 // Only compress messages > 1KB
  }
});

Security Best Practices

Authentication

Always verify JWT tokens on connection

Authorization

Check user permissions before joining rooms

Input Validation

Validate and sanitize all incoming data

Rate Limiting

Limit events per user to prevent abuse

Rate Limiting Example

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 50, // 50 messages per minute
  handler: (socket) => {
    socket.emit('rate_limit_exceeded');
  }
});

socket.on('send_message', limiter, handleSendMessage);

Testing Socket.IO

Example test with Socket.IO client:
import { io as Client } from 'socket.io-client';

describe('Chat Socket', () => {
  let clientSocket;
  
  beforeEach((done) => {
    clientSocket = Client('http://localhost:3002', {
      auth: { token: 'test-token' }
    });
    clientSocket.on('connect', done);
  });
  
  afterEach(() => {
    clientSocket.close();
  });
  
  it('should receive messages', (done) => {
    clientSocket.on('new_message', (message) => {
      expect(message.content).toBe('Hello');
      done();
    });
    
    clientSocket.emit('send_message', {
      conversationId: '123',
      content: 'Hello'
    });
  });
});

Next Steps

API Reference

Explore REST API endpoints

Widget Integration

Add the chat widget to your site

Development Setup

Set up your environment

Architecture

System architecture overview