JavaScript Real-Time

JavaScript Real-Time Applications: WebSockets, Server-Sent Events, and Live Updates

Build real-time JavaScript applications with WebSockets, Server-Sent Events, WebRTC, and live data synchronization. Master real-time communication patterns.

By JavaScript Document Team
real-timewebsocketsssewebrtclive-datacommunication

Real-time applications provide instant data updates and interactive experiences. This comprehensive guide covers WebSockets, Server-Sent Events, WebRTC, and modern techniques for building responsive, live applications.

WebSocket Communication

WebSocket Client Implementation

// Advanced WebSocket Client with Reconnection and State Management
class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      reconnectInterval: 1000,
      maxReconnectInterval: 30000,
      reconnectDecay: 1.5,
      timeoutInterval: 2000,
      maxReconnectAttempts: null,
      binaryType: 'blob',
      protocols: [],
      ...options,
    };

    this.ws = null;
    this.reconnectAttempts = 0;
    this.readyState = WebSocket.CONNECTING;
    this.url = url;
    this.protocols = this.options.protocols;

    this.listeners = {
      open: [],
      close: [],
      message: [],
      error: [],
      reconnect: [],
      maxReconnectAttemptsReached: [],
    };

    this.messageQueue = [];
    this.isIntentionallyClosed = false;
    this.timerID = null;

    this.connect();
  }

  connect() {
    try {
      this.ws = new WebSocket(this.url, this.protocols);
      this.ws.binaryType = this.options.binaryType;

      this.ws.onopen = (event) => {
        this.onOpen(event);
      };

      this.ws.onclose = (event) => {
        this.onClose(event);
      };

      this.ws.onmessage = (event) => {
        this.onMessage(event);
      };

      this.ws.onerror = (event) => {
        this.onError(event);
      };
    } catch (error) {
      this.onError(error);
    }
  }

  onOpen(event) {
    this.readyState = WebSocket.OPEN;
    this.reconnectAttempts = 0;

    // Send queued messages
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      this.send(message);
    }

    this.emit('open', event);
  }

  onClose(event) {
    this.readyState = WebSocket.CLOSED;

    if (!this.isIntentionallyClosed) {
      this.emit('close', event);
      this.attemptReconnect();
    }
  }

  onMessage(event) {
    let data = event.data;

    // Try to parse JSON
    try {
      data = JSON.parse(event.data);
    } catch (e) {
      // Keep original data if not JSON
    }

    this.emit('message', {
      data,
      originalEvent: event,
      timestamp: Date.now(),
    });
  }

  onError(event) {
    this.emit('error', event);
  }

  attemptReconnect() {
    if (
      this.options.maxReconnectAttempts &&
      this.reconnectAttempts >= this.options.maxReconnectAttempts
    ) {
      this.emit('maxReconnectAttemptsReached');
      return;
    }

    const timeout =
      this.options.reconnectInterval *
      Math.pow(this.options.reconnectDecay, this.reconnectAttempts);

    const maxTimeout = Math.min(timeout, this.options.maxReconnectInterval);

    this.timerID = setTimeout(() => {
      this.reconnectAttempts++;
      this.emit('reconnect', {
        attempt: this.reconnectAttempts,
        maxAttempts: this.options.maxReconnectAttempts,
      });
      this.connect();
    }, maxTimeout);
  }

  send(data) {
    if (this.readyState === WebSocket.OPEN) {
      const message = typeof data === 'object' ? JSON.stringify(data) : data;
      this.ws.send(message);
    } else {
      // Queue message for later
      this.messageQueue.push(data);
    }
  }

  close(code = 1000, reason = '') {
    this.isIntentionallyClosed = true;

    if (this.timerID) {
      clearTimeout(this.timerID);
      this.timerID = null;
    }

    if (this.ws) {
      this.ws.close(code, reason);
    }
  }

  // Event listener management
  addEventListener(type, listener) {
    if (this.listeners[type]) {
      this.listeners[type].push(listener);
    }
  }

  removeEventListener(type, listener) {
    if (this.listeners[type]) {
      const index = this.listeners[type].indexOf(listener);
      if (index > -1) {
        this.listeners[type].splice(index, 1);
      }
    }
  }

  emit(type, data) {
    if (this.listeners[type]) {
      this.listeners[type].forEach((listener) => {
        try {
          listener(data);
        } catch (error) {
          console.error('Error in event listener:', error);
        }
      });
    }
  }

  // Convenience methods
  on(type, listener) {
    this.addEventListener(type, listener);
    return this;
  }

  off(type, listener) {
    this.removeEventListener(type, listener);
    return this;
  }

  // Connection state
  get state() {
    return this.readyState;
  }

  get isConnected() {
    return this.readyState === WebSocket.OPEN;
  }

  get isConnecting() {
    return this.readyState === WebSocket.CONNECTING;
  }

  get isClosed() {
    return this.readyState === WebSocket.CLOSED;
  }
}

// WebSocket Server Implementation (Node.js style)
class WebSocketServer {
  constructor(options = {}) {
    this.options = {
      port: 8080,
      host: 'localhost',
      path: '/',
      maxClients: 100,
      heartbeatInterval: 30000,
      ...options,
    };

    this.clients = new Set();
    this.rooms = new Map();
    this.middleware = [];
    this.routes = new Map();

    this.messageHandlers = new Map();
    this.setupDefaultHandlers();
  }

  setupDefaultHandlers() {
    this.on('connection', (client) => {
      console.log(`Client connected: ${client.id}`);

      client.on('close', () => {
        console.log(`Client disconnected: ${client.id}`);
        this.clients.delete(client);
        this.leaveAllRooms(client);
      });

      client.on('error', (error) => {
        console.error(`Client error ${client.id}:`, error);
      });
    });

    // Built-in message handlers
    this.handleMessage('ping', (client, data) => {
      client.send({ type: 'pong', timestamp: Date.now() });
    });

    this.handleMessage('join-room', (client, data) => {
      this.joinRoom(client, data.room);
    });

    this.handleMessage('leave-room', (client, data) => {
      this.leaveRoom(client, data.room);
    });

    this.handleMessage('room-message', (client, data) => {
      this.broadcastToRoom(data.room, data.message, client);
    });
  }

  // Middleware system
  use(middleware) {
    this.middleware.push(middleware);
  }

  // Message handler registration
  handleMessage(type, handler) {
    this.messageHandlers.set(type, handler);
  }

  on(event, handler) {
    if (!this.eventHandlers) {
      this.eventHandlers = new Map();
    }

    if (!this.eventHandlers.has(event)) {
      this.eventHandlers.set(event, []);
    }

    this.eventHandlers.get(event).push(handler);
  }

  emit(event, ...args) {
    if (this.eventHandlers && this.eventHandlers.has(event)) {
      this.eventHandlers.get(event).forEach((handler) => {
        try {
          handler(...args);
        } catch (error) {
          console.error('Error in event handler:', error);
        }
      });
    }
  }

  // Client management
  addClient(ws, request) {
    const client = {
      id: this.generateClientId(),
      ws,
      request,
      rooms: new Set(),
      metadata: {},
      connectedAt: Date.now(),
      lastPing: Date.now(),
    };

    // Add event forwarding
    client.send = (data) => {
      if (ws.readyState === WebSocket.OPEN) {
        const message = typeof data === 'object' ? JSON.stringify(data) : data;
        ws.send(message);
      }
    };

    client.on = (event, handler) => {
      ws.addEventListener(event, handler);
    };

    client.close = (code, reason) => {
      ws.close(code, reason);
    };

    // Apply middleware
    for (const middleware of this.middleware) {
      try {
        middleware(client, request);
      } catch (error) {
        console.error('Middleware error:', error);
      }
    }

    this.clients.add(client);

    // Setup message handling
    ws.on('message', (data) => {
      try {
        const message = JSON.parse(data);
        this.handleClientMessage(client, message);
      } catch (error) {
        console.error('Invalid message format:', error);
      }
    });

    ws.on('close', () => {
      this.emit('disconnection', client);
    });

    ws.on('error', (error) => {
      this.emit('error', client, error);
    });

    this.emit('connection', client);
    return client;
  }

  handleClientMessage(client, message) {
    client.lastPing = Date.now();

    const { type, data } = message;

    if (this.messageHandlers.has(type)) {
      try {
        this.messageHandlers.get(type)(client, data, message);
      } catch (error) {
        console.error(`Error handling message type ${type}:`, error);
        client.send({
          type: 'error',
          message: 'Internal server error',
          originalType: type,
        });
      }
    } else {
      console.warn(`Unknown message type: ${type}`);
      client.send({
        type: 'error',
        message: `Unknown message type: ${type}`,
        originalType: type,
      });
    }
  }

  // Room management
  joinRoom(client, roomName) {
    if (!this.rooms.has(roomName)) {
      this.rooms.set(roomName, new Set());
    }

    this.rooms.get(roomName).add(client);
    client.rooms.add(roomName);

    client.send({
      type: 'room-joined',
      room: roomName,
      memberCount: this.rooms.get(roomName).size,
    });

    // Notify other room members
    this.broadcastToRoom(
      roomName,
      {
        type: 'member-joined',
        clientId: client.id,
        memberCount: this.rooms.get(roomName).size,
      },
      client
    );
  }

  leaveRoom(client, roomName) {
    if (this.rooms.has(roomName)) {
      this.rooms.get(roomName).delete(client);
      client.rooms.delete(roomName);

      const room = this.rooms.get(roomName);

      if (room.size === 0) {
        this.rooms.delete(roomName);
      } else {
        // Notify remaining members
        this.broadcastToRoom(roomName, {
          type: 'member-left',
          clientId: client.id,
          memberCount: room.size,
        });
      }

      client.send({
        type: 'room-left',
        room: roomName,
      });
    }
  }

  leaveAllRooms(client) {
    client.rooms.forEach((roomName) => {
      this.leaveRoom(client, roomName);
    });
  }

  // Broadcasting
  broadcast(message, excludeClient = null) {
    this.clients.forEach((client) => {
      if (client !== excludeClient && client.ws.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  }

  broadcastToRoom(roomName, message, excludeClient = null) {
    if (this.rooms.has(roomName)) {
      this.rooms.get(roomName).forEach((client) => {
        if (
          client !== excludeClient &&
          client.ws.readyState === WebSocket.OPEN
        ) {
          client.send(message);
        }
      });
    }
  }

  // Heartbeat system
  startHeartbeat() {
    setInterval(() => {
      const now = Date.now();

      this.clients.forEach((client) => {
        const timeSinceLastPing = now - client.lastPing;

        if (timeSinceLastPing > this.options.heartbeatInterval * 2) {
          // Client appears dead
          console.log(`Removing dead client: ${client.id}`);
          client.close(1001, 'Client timeout');
          this.clients.delete(client);
        } else if (timeSinceLastPing > this.options.heartbeatInterval) {
          // Send ping
          client.send({ type: 'ping', timestamp: now });
        }
      });
    }, this.options.heartbeatInterval);
  }

  // Utilities
  generateClientId() {
    return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  getStats() {
    return {
      clientCount: this.clients.size,
      roomCount: this.rooms.size,
      rooms: Array.from(this.rooms.entries()).map(([name, clients]) => ({
        name,
        memberCount: clients.size,
      })),
    };
  }

  getClient(clientId) {
    return Array.from(this.clients).find((client) => client.id === clientId);
  }

  getRoomMembers(roomName) {
    return this.rooms.has(roomName) ? Array.from(this.rooms.get(roomName)) : [];
  }
}

// Real-time Chat Application Example
class RealtimeChat {
  constructor(containerId, wsUrl) {
    this.container = document.getElementById(containerId);
    this.ws = new WebSocketClient(wsUrl);
    this.currentRoom = null;
    this.messages = [];
    this.users = new Map();

    this.setupUI();
    this.setupWebSocket();
  }

  setupUI() {
    this.container.innerHTML = `
      <div class="chat-container">
        <div class="chat-sidebar">
          <div class="user-info">
            <input type="text" id="username" placeholder="Enter username" />
            <button id="connect-btn">Connect</button>
          </div>
          <div class="rooms">
            <h3>Rooms</h3>
            <div id="room-list"></div>
            <input type="text" id="room-input" placeholder="Room name" />
            <button id="join-room-btn">Join Room</button>
          </div>
          <div class="users">
            <h3>Online Users</h3>
            <div id="user-list"></div>
          </div>
        </div>
        <div class="chat-main">
          <div class="chat-header">
            <h2 id="current-room">Select a room</h2>
            <button id="leave-room-btn" style="display: none;">Leave Room</button>
          </div>
          <div id="message-list" class="message-list"></div>
          <div class="message-input">
            <input type="text" id="message-input" placeholder="Type a message..." />
            <button id="send-btn">Send</button>
          </div>
        </div>
      </div>
    `;

    this.setupEventListeners();
  }

  setupEventListeners() {
    document.getElementById('connect-btn').onclick = () => this.connect();
    document.getElementById('join-room-btn').onclick = () => this.joinRoom();
    document.getElementById('leave-room-btn').onclick = () => this.leaveRoom();
    document.getElementById('send-btn').onclick = () => this.sendMessage();

    document.getElementById('message-input').onkeypress = (e) => {
      if (e.key === 'Enter') this.sendMessage();
    };

    document.getElementById('room-input').onkeypress = (e) => {
      if (e.key === 'Enter') this.joinRoom();
    };
  }

  setupWebSocket() {
    this.ws.on('open', () => {
      this.addSystemMessage('Connected to server');
    });

    this.ws.on('close', () => {
      this.addSystemMessage('Disconnected from server');
    });

    this.ws.on('error', (error) => {
      this.addSystemMessage('Connection error: ' + error.message);
    });

    this.ws.on('reconnect', (data) => {
      this.addSystemMessage(`Reconnecting... (attempt ${data.attempt})`);
    });

    this.ws.on('message', (event) => {
      this.handleMessage(event.data);
    });
  }

  handleMessage(message) {
    switch (message.type) {
      case 'room-joined':
        this.onRoomJoined(message);
        break;
      case 'room-left':
        this.onRoomLeft(message);
        break;
      case 'member-joined':
        this.onMemberJoined(message);
        break;
      case 'member-left':
        this.onMemberLeft(message);
        break;
      case 'chat-message':
        this.onChatMessage(message);
        break;
      case 'user-list':
        this.onUserList(message);
        break;
      case 'pong':
        // Handle server pong
        break;
      default:
        console.log('Unknown message type:', message.type);
    }
  }

  connect() {
    const username = document.getElementById('username').value.trim();
    if (!username) {
      alert('Please enter a username');
      return;
    }

    this.username = username;
    this.ws.send({
      type: 'set-username',
      username: username,
    });

    document.getElementById('connect-btn').disabled = true;
  }

  joinRoom() {
    const roomName = document.getElementById('room-input').value.trim();
    if (!roomName) return;

    if (this.currentRoom) {
      this.leaveRoom();
    }

    this.ws.send({
      type: 'join-room',
      room: roomName,
    });

    document.getElementById('room-input').value = '';
  }

  leaveRoom() {
    if (!this.currentRoom) return;

    this.ws.send({
      type: 'leave-room',
      room: this.currentRoom,
    });
  }

  sendMessage() {
    const input = document.getElementById('message-input');
    const message = input.value.trim();

    if (!message || !this.currentRoom) return;

    this.ws.send({
      type: 'room-message',
      room: this.currentRoom,
      message: {
        text: message,
        username: this.username,
        timestamp: Date.now(),
      },
    });

    input.value = '';
  }

  onRoomJoined(data) {
    this.currentRoom = data.room;
    document.getElementById('current-room').textContent = data.room;
    document.getElementById('leave-room-btn').style.display = 'block';
    this.addSystemMessage(
      `Joined room: ${data.room} (${data.memberCount} members)`
    );
    this.clearMessages();
  }

  onRoomLeft(data) {
    this.currentRoom = null;
    document.getElementById('current-room').textContent = 'Select a room';
    document.getElementById('leave-room-btn').style.display = 'none';
    this.addSystemMessage(`Left room: ${data.room}`);
    this.clearMessages();
  }

  onMemberJoined(data) {
    this.addSystemMessage(
      `${data.clientId} joined the room (${data.memberCount} members)`
    );
  }

  onMemberLeft(data) {
    this.addSystemMessage(
      `${data.clientId} left the room (${data.memberCount} members)`
    );
  }

  onChatMessage(data) {
    this.addChatMessage(data.message);
  }

  onUserList(data) {
    const userList = document.getElementById('user-list');
    userList.innerHTML = data.users
      .map((user) => `<div class="user">${user}</div>`)
      .join('');
  }

  addChatMessage(message) {
    const messageList = document.getElementById('message-list');
    const messageElement = document.createElement('div');
    messageElement.className = 'message';

    const time = new Date(message.timestamp).toLocaleTimeString();
    messageElement.innerHTML = `
      <div class="message-header">
        <span class="username">${message.username}</span>
        <span class="timestamp">${time}</span>
      </div>
      <div class="message-text">${this.escapeHtml(message.text)}</div>
    `;

    messageList.appendChild(messageElement);
    messageList.scrollTop = messageList.scrollHeight;
  }

  addSystemMessage(text) {
    const messageList = document.getElementById('message-list');
    const messageElement = document.createElement('div');
    messageElement.className = 'system-message';
    messageElement.textContent = text;

    messageList.appendChild(messageElement);
    messageList.scrollTop = messageList.scrollHeight;
  }

  clearMessages() {
    document.getElementById('message-list').innerHTML = '';
  }

  escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }
}

// Usage
const chat = new RealtimeChat('chat-app', 'ws://localhost:8080');

Server-Sent Events (SSE)

SSE Implementation for Live Updates

// Server-Sent Events Client
class SSEClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      withCredentials: false,
      reconnectTime: 1000,
      maxReconnectTime: 30000,
      reconnectDecay: 1.5,
      maxReconnectAttempts: null,
      headers: {},
      ...options,
    };

    this.eventSource = null;
    this.reconnectAttempts = 0;
    this.listeners = new Map();
    this.reconnectTimeoutId = null;
    this.isIntentionallyClosed = false;

    this.connect();
  }

  connect() {
    if (this.eventSource) {
      this.eventSource.close();
    }

    const url = new URL(this.url);

    // Add headers as query parameters (since EventSource doesn't support custom headers)
    Object.entries(this.options.headers).forEach(([key, value]) => {
      url.searchParams.set(key, value);
    });

    this.eventSource = new EventSource(url.toString(), {
      withCredentials: this.options.withCredentials,
    });

    this.eventSource.onopen = (event) => {
      this.reconnectAttempts = 0;
      this.emit('open', event);
    };

    this.eventSource.onerror = (event) => {
      this.emit('error', event);

      if (!this.isIntentionallyClosed) {
        this.attemptReconnect();
      }
    };

    this.eventSource.onmessage = (event) => {
      this.handleMessage(event);
    };

    // Setup custom event listeners
    this.listeners.forEach((handlers, eventType) => {
      this.eventSource.addEventListener(eventType, (event) => {
        handlers.forEach((handler) => {
          try {
            handler(event);
          } catch (error) {
            console.error('Error in SSE event handler:', error);
          }
        });
      });
    });
  }

  handleMessage(event) {
    let data = event.data;

    try {
      data = JSON.parse(event.data);
    } catch (e) {
      // Keep original data if not JSON
    }

    this.emit('message', {
      data,
      id: event.lastEventId,
      type: event.type,
      originalEvent: event,
    });
  }

  attemptReconnect() {
    if (
      this.options.maxReconnectAttempts &&
      this.reconnectAttempts >= this.options.maxReconnectAttempts
    ) {
      this.emit('maxReconnectAttemptsReached');
      return;
    }

    const timeout = Math.min(
      this.options.reconnectTime *
        Math.pow(this.options.reconnectDecay, this.reconnectAttempts),
      this.options.maxReconnectTime
    );

    this.reconnectTimeoutId = setTimeout(() => {
      this.reconnectAttempts++;
      this.emit('reconnect', {
        attempt: this.reconnectAttempts,
        maxAttempts: this.options.maxReconnectAttempts,
      });
      this.connect();
    }, timeout);
  }

  addEventListener(type, handler) {
    if (!this.listeners.has(type)) {
      this.listeners.set(type, []);
    }

    this.listeners.get(type).push(handler);

    // Add to existing EventSource if connected
    if (this.eventSource) {
      this.eventSource.addEventListener(type, handler);
    }
  }

  removeEventListener(type, handler) {
    if (this.listeners.has(type)) {
      const handlers = this.listeners.get(type);
      const index = handlers.indexOf(handler);

      if (index > -1) {
        handlers.splice(index, 1);
      }

      if (this.eventSource) {
        this.eventSource.removeEventListener(type, handler);
      }
    }
  }

  emit(type, data) {
    if (this.listeners.has(type)) {
      this.listeners.get(type).forEach((handler) => {
        try {
          handler(data);
        } catch (error) {
          console.error('Error in event listener:', error);
        }
      });
    }
  }

  close() {
    this.isIntentionallyClosed = true;

    if (this.reconnectTimeoutId) {
      clearTimeout(this.reconnectTimeoutId);
      this.reconnectTimeoutId = null;
    }

    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
    }
  }

  // Convenience methods
  on(type, handler) {
    this.addEventListener(type, handler);
    return this;
  }

  off(type, handler) {
    this.removeEventListener(type, handler);
    return this;
  }

  get readyState() {
    return this.eventSource ? this.eventSource.readyState : EventSource.CLOSED;
  }

  get url() {
    return this.eventSource ? this.eventSource.url : this.url;
  }
}

// SSE Server Implementation (Node.js style)
class SSEServer {
  constructor() {
    this.clients = new Set();
    this.channels = new Map();
    this.middleware = [];
  }

  // Middleware support
  use(middleware) {
    this.middleware.push(middleware);
  }

  // Handle SSE connection
  handleConnection(req, res) {
    // Set SSE headers
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Headers': 'Cache-Control',
    });

    const client = {
      id: this.generateClientId(),
      req,
      res,
      channels: new Set(),
      lastEventId: 0,
      connectedAt: Date.now(),
      isAlive: true,
    };

    // Apply middleware
    for (const middleware of this.middleware) {
      try {
        middleware(client, req);
      } catch (error) {
        console.error('SSE middleware error:', error);
      }
    }

    this.clients.add(client);

    // Send initial connection event
    this.sendToClient(client, {
      type: 'connected',
      data: { clientId: client.id },
    });

    // Handle client disconnect
    req.on('close', () => {
      this.handleDisconnect(client);
    });

    req.on('error', (error) => {
      console.error('SSE client error:', error);
      this.handleDisconnect(client);
    });

    // Setup heartbeat
    this.setupHeartbeat(client);

    return client;
  }

  handleDisconnect(client) {
    client.isAlive = false;
    this.clients.delete(client);

    // Remove from all channels
    client.channels.forEach((channelName) => {
      this.leaveChannel(client, channelName);
    });

    console.log(`SSE client disconnected: ${client.id}`);
  }

  setupHeartbeat(client) {
    const heartbeatInterval = setInterval(() => {
      if (!client.isAlive) {
        clearInterval(heartbeatInterval);
        return;
      }

      try {
        this.sendToClient(client, {
          type: 'heartbeat',
          data: { timestamp: Date.now() },
        });
      } catch (error) {
        console.error('Heartbeat error:', error);
        clearInterval(heartbeatInterval);
        this.handleDisconnect(client);
      }
    }, 30000);
  }

  // Send event to specific client
  sendToClient(client, event) {
    if (!client.isAlive || client.res.destroyed) {
      return false;
    }

    try {
      const eventId = ++client.lastEventId;
      const data =
        typeof event.data === 'object'
          ? JSON.stringify(event.data)
          : event.data;

      let message = '';

      if (event.id) {
        message += `id: ${event.id}\n`;
      } else {
        message += `id: ${eventId}\n`;
      }

      if (event.type) {
        message += `event: ${event.type}\n`;
      }

      if (event.retry) {
        message += `retry: ${event.retry}\n`;
      }

      message += `data: ${data}\n\n`;

      client.res.write(message);
      return true;
    } catch (error) {
      console.error('Error sending to client:', error);
      this.handleDisconnect(client);
      return false;
    }
  }

  // Broadcast to all clients
  broadcast(event) {
    let successCount = 0;

    this.clients.forEach((client) => {
      if (this.sendToClient(client, event)) {
        successCount++;
      }
    });

    return successCount;
  }

  // Channel management
  createChannel(name) {
    if (!this.channels.has(name)) {
      this.channels.set(name, {
        name,
        clients: new Set(),
        messageHistory: [],
        maxHistory: 100,
      });
    }

    return this.channels.get(name);
  }

  joinChannel(client, channelName) {
    const channel = this.createChannel(channelName);

    channel.clients.add(client);
    client.channels.add(channelName);

    // Send recent message history
    channel.messageHistory.forEach((message) => {
      this.sendToClient(client, message);
    });

    // Notify about joining
    this.sendToClient(client, {
      type: 'channel-joined',
      data: {
        channel: channelName,
        memberCount: channel.clients.size,
      },
    });

    return channel;
  }

  leaveChannel(client, channelName) {
    const channel = this.channels.get(channelName);

    if (channel) {
      channel.clients.delete(client);
      client.channels.delete(channelName);

      if (channel.clients.size === 0) {
        this.channels.delete(channelName);
      }
    }
  }

  // Send to channel
  sendToChannel(channelName, event) {
    const channel = this.channels.get(channelName);

    if (!channel) {
      return 0;
    }

    // Add to history
    channel.messageHistory.push(event);
    if (channel.messageHistory.length > channel.maxHistory) {
      channel.messageHistory.shift();
    }

    let successCount = 0;

    channel.clients.forEach((client) => {
      if (this.sendToClient(client, event)) {
        successCount++;
      }
    });

    return successCount;
  }

  // Utilities
  generateClientId() {
    return `sse-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  getStats() {
    return {
      clientCount: this.clients.size,
      channelCount: this.channels.size,
      channels: Array.from(this.channels.entries()).map(([name, channel]) => ({
        name,
        clientCount: channel.clients.size,
        messageCount: channel.messageHistory.length,
      })),
    };
  }

  getClient(clientId) {
    return Array.from(this.clients).find((client) => client.id === clientId);
  }
}

// Live Dashboard Example using SSE
class LiveDashboard {
  constructor(containerId, sseUrl) {
    this.container = document.getElementById(containerId);
    this.sse = new SSEClient(sseUrl);
    this.metrics = new Map();
    this.charts = new Map();

    this.setupUI();
    this.setupSSE();
  }

  setupUI() {
    this.container.innerHTML = `
      <div class="dashboard">
        <div class="dashboard-header">
          <h1>Live Dashboard</h1>
          <div class="connection-status" id="connection-status">Connecting...</div>
        </div>
        <div class="metrics-grid" id="metrics-grid"></div>
        <div class="charts-container" id="charts-container"></div>
        <div class="events-feed" id="events-feed">
          <h3>Live Events</h3>
          <div id="events-list"></div>
        </div>
      </div>
    `;
  }

  setupSSE() {
    this.sse.on('open', () => {
      this.updateConnectionStatus('Connected', 'connected');
    });

    this.sse.on('error', () => {
      this.updateConnectionStatus('Connection Error', 'error');
    });

    this.sse.on('reconnect', (data) => {
      this.updateConnectionStatus(
        `Reconnecting (${data.attempt})...`,
        'reconnecting'
      );
    });

    // Handle specific events
    this.sse.on('metrics', (event) => {
      this.updateMetrics(event.data);
    });

    this.sse.on('alert', (event) => {
      this.addAlert(event.data);
    });

    this.sse.on('system-event', (event) => {
      this.addEvent(event.data);
    });

    this.sse.on('user-activity', (event) => {
      this.updateUserActivity(event.data);
    });
  }

  updateConnectionStatus(text, status) {
    const statusElement = document.getElementById('connection-status');
    statusElement.textContent = text;
    statusElement.className = `connection-status ${status}`;
  }

  updateMetrics(metrics) {
    const metricsGrid = document.getElementById('metrics-grid');

    Object.entries(metrics).forEach(([key, value]) => {
      let metricElement = document.getElementById(`metric-${key}`);

      if (!metricElement) {
        metricElement = document.createElement('div');
        metricElement.id = `metric-${key}`;
        metricElement.className = 'metric-card';
        metricsGrid.appendChild(metricElement);
      }

      metricElement.innerHTML = `
        <div class="metric-label">${this.formatMetricName(key)}</div>
        <div class="metric-value">${this.formatMetricValue(value)}</div>
        <div class="metric-trend ${this.getTrend(key, value)}"></div>
      `;

      this.metrics.set(key, value);
    });
  }

  addAlert(alert) {
    // Create floating alert
    const alertElement = document.createElement('div');
    alertElement.className = `alert alert-${alert.level}`;
    alertElement.innerHTML = `
      <div class="alert-title">${alert.title}</div>
      <div class="alert-message">${alert.message}</div>
      <button onclick="this.parentElement.remove()">×</button>
    `;

    document.body.appendChild(alertElement);

    // Auto-remove after delay
    setTimeout(() => {
      if (alertElement.parentElement) {
        alertElement.remove();
      }
    }, alert.duration || 5000);

    this.addEvent({
      type: 'alert',
      level: alert.level,
      message: `${alert.title}: ${alert.message}`,
      timestamp: Date.now(),
    });
  }

  addEvent(event) {
    const eventsList = document.getElementById('events-list');
    const eventElement = document.createElement('div');
    eventElement.className = `event event-${event.type}`;

    const time = new Date(event.timestamp).toLocaleTimeString();
    eventElement.innerHTML = `
      <div class="event-time">${time}</div>
      <div class="event-message">${event.message}</div>
    `;

    eventsList.insertBefore(eventElement, eventsList.firstChild);

    // Keep only last 50 events
    while (eventsList.children.length > 50) {
      eventsList.removeChild(eventsList.lastChild);
    }
  }

  updateUserActivity(activity) {
    this.addEvent({
      type: 'user-activity',
      message: `${activity.action} by ${activity.user}`,
      timestamp: activity.timestamp,
    });
  }

  formatMetricName(name) {
    return name
      .replace(/([A-Z])/g, ' $1')
      .replace(/^./, (str) => str.toUpperCase())
      .replace(/_/g, ' ');
  }

  formatMetricValue(value) {
    if (typeof value === 'number') {
      if (value > 1000000) {
        return (value / 1000000).toFixed(1) + 'M';
      } else if (value > 1000) {
        return (value / 1000).toFixed(1) + 'K';
      }
      return value.toLocaleString();
    }
    return value;
  }

  getTrend(key, value) {
    const previous = this.metrics.get(key);
    if (previous === undefined) return '';

    if (value > previous) return 'up';
    if (value < previous) return 'down';
    return 'stable';
  }
}

// Usage
const dashboard = new LiveDashboard(
  'dashboard-container',
  '/api/dashboard/events'
);

WebRTC for Peer-to-Peer Communication

WebRTC Implementation

// WebRTC Peer Connection Manager
class WebRTCPeer {
  constructor(config = {}) {
    this.config = {
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' },
      ],
      ...config,
    };

    this.peerConnection = null;
    this.localStream = null;
    this.remoteStream = null;
    this.dataChannel = null;
    this.isInitiator = false;

    this.listeners = new Map();
    this.candidateQueue = [];
    this.connectionState = 'new';

    this.setupPeerConnection();
  }

  setupPeerConnection() {
    this.peerConnection = new RTCPeerConnection(this.config);

    // ICE candidate handling
    this.peerConnection.onicecandidate = (event) => {
      if (event.candidate) {
        this.emit('icecandidate', event.candidate);
      }
    };

    // Connection state changes
    this.peerConnection.onconnectionstatechange = () => {
      this.connectionState = this.peerConnection.connectionState;
      this.emit('connectionstatechange', this.connectionState);

      if (this.connectionState === 'connected') {
        this.emit('connected');
      } else if (this.connectionState === 'disconnected') {
        this.emit('disconnected');
      } else if (this.connectionState === 'failed') {
        this.emit('failed');
      }
    };

    // ICE connection state changes
    this.peerConnection.oniceconnectionstatechange = () => {
      this.emit(
        'iceconnectionstatechange',
        this.peerConnection.iceConnectionState
      );
    };

    // Remote stream handling
    this.peerConnection.ontrack = (event) => {
      this.remoteStream = event.streams[0];
      this.emit('remotestream', this.remoteStream);
    };

    // Data channel handling
    this.peerConnection.ondatachannel = (event) => {
      const channel = event.channel;
      this.setupDataChannel(channel);
    };
  }

  // Create offer (caller)
  async createOffer(options = {}) {
    this.isInitiator = true;

    try {
      const offer = await this.peerConnection.createOffer(options);
      await this.peerConnection.setLocalDescription(offer);

      this.emit('offer', offer);
      return offer;
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }

  // Create answer (callee)
  async createAnswer(offer, options = {}) {
    try {
      await this.peerConnection.setRemoteDescription(offer);

      // Process queued candidates
      for (const candidate of this.candidateQueue) {
        await this.addIceCandidate(candidate);
      }
      this.candidateQueue = [];

      const answer = await this.peerConnection.createAnswer(options);
      await this.peerConnection.setLocalDescription(answer);

      this.emit('answer', answer);
      return answer;
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }

  // Handle answer (caller)
  async handleAnswer(answer) {
    try {
      await this.peerConnection.setRemoteDescription(answer);

      // Process queued candidates
      for (const candidate of this.candidateQueue) {
        await this.addIceCandidate(candidate);
      }
      this.candidateQueue = [];
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }

  // Add ICE candidate
  async addIceCandidate(candidate) {
    try {
      if (this.peerConnection.remoteDescription) {
        await this.peerConnection.addIceCandidate(candidate);
      } else {
        this.candidateQueue.push(candidate);
      }
    } catch (error) {
      console.error('Error adding ICE candidate:', error);
    }
  }

  // Media handling
  async getUserMedia(constraints = { video: true, audio: true }) {
    try {
      this.localStream = await navigator.mediaDevices.getUserMedia(constraints);

      // Add tracks to peer connection
      this.localStream.getTracks().forEach((track) => {
        this.peerConnection.addTrack(track, this.localStream);
      });

      this.emit('localstream', this.localStream);
      return this.localStream;
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }

  async getDisplayMedia(constraints = { video: true }) {
    try {
      this.localStream =
        await navigator.mediaDevices.getDisplayMedia(constraints);

      // Replace video track
      const videoTrack = this.localStream.getVideoTracks()[0];
      const sender = this.peerConnection
        .getSenders()
        .find((s) => s.track && s.track.kind === 'video');

      if (sender) {
        await sender.replaceTrack(videoTrack);
      } else {
        this.peerConnection.addTrack(videoTrack, this.localStream);
      }

      this.emit('localstream', this.localStream);
      return this.localStream;
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }

  // Data channel
  createDataChannel(label, options = {}) {
    const defaultOptions = {
      ordered: true,
      maxRetransmits: 3,
      ...options,
    };

    this.dataChannel = this.peerConnection.createDataChannel(
      label,
      defaultOptions
    );
    this.setupDataChannel(this.dataChannel);

    return this.dataChannel;
  }

  setupDataChannel(channel) {
    channel.onopen = () => {
      this.emit('datachannel-open', channel);
    };

    channel.onclose = () => {
      this.emit('datachannel-close', channel);
    };

    channel.onmessage = (event) => {
      this.emit('datachannel-message', event.data);
    };

    channel.onerror = (error) => {
      this.emit('datachannel-error', error);
    };

    if (!this.dataChannel) {
      this.dataChannel = channel;
    }
  }

  sendData(data) {
    if (this.dataChannel && this.dataChannel.readyState === 'open') {
      const message = typeof data === 'object' ? JSON.stringify(data) : data;
      this.dataChannel.send(message);
      return true;
    }
    return false;
  }

  // Statistics
  async getStats() {
    try {
      const stats = await this.peerConnection.getStats();
      const parsedStats = {};

      stats.forEach((report) => {
        parsedStats[report.id] = report;
      });

      return parsedStats;
    } catch (error) {
      console.error('Error getting stats:', error);
      return null;
    }
  }

  // Event handling
  on(event, handler) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(handler);
  }

  emit(event, data) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach((handler) => {
        try {
          handler(data);
        } catch (error) {
          console.error('Error in event handler:', error);
        }
      });
    }
  }

  // Cleanup
  close() {
    if (this.localStream) {
      this.localStream.getTracks().forEach((track) => track.stop());
    }

    if (this.dataChannel) {
      this.dataChannel.close();
    }

    if (this.peerConnection) {
      this.peerConnection.close();
    }

    this.emit('closed');
  }

  // State getters
  get state() {
    return this.connectionState;
  }

  get signalingState() {
    return this.peerConnection ? this.peerConnection.signalingState : 'closed';
  }

  get iceConnectionState() {
    return this.peerConnection
      ? this.peerConnection.iceConnectionState
      : 'closed';
  }
}

// Video Chat Application
class VideoChat {
  constructor(containerId, signalingUrl) {
    this.container = document.getElementById(containerId);
    this.signalingSocket = new WebSocketClient(signalingUrl);
    this.peer = null;
    this.roomId = null;
    this.isInitiator = false;

    this.setupUI();
    this.setupSignaling();
  }

  setupUI() {
    this.container.innerHTML = `
      <div class="video-chat">
        <div class="video-container">
          <video id="localVideo" autoplay muted playsinline></video>
          <video id="remoteVideo" autoplay playsinline></video>
        </div>
        <div class="controls">
          <input type="text" id="roomInput" placeholder="Room ID" />
          <button id="joinBtn">Join Room</button>
          <button id="leaveBtn" disabled>Leave Room</button>
          <button id="muteBtn">Mute</button>
          <button id="videoBtn">Video Off</button>
          <button id="screenBtn">Share Screen</button>
        </div>
        <div class="chat">
          <div id="messages"></div>
          <input type="text" id="messageInput" placeholder="Type a message..." />
          <button id="sendBtn">Send</button>
        </div>
        <div class="status" id="status">Not connected</div>
      </div>
    `;

    this.setupEventListeners();
  }

  setupEventListeners() {
    document.getElementById('joinBtn').onclick = () => this.joinRoom();
    document.getElementById('leaveBtn').onclick = () => this.leaveRoom();
    document.getElementById('muteBtn').onclick = () => this.toggleMute();
    document.getElementById('videoBtn').onclick = () => this.toggleVideo();
    document.getElementById('screenBtn').onclick = () => this.shareScreen();
    document.getElementById('sendBtn').onclick = () => this.sendMessage();

    document.getElementById('messageInput').onkeypress = (e) => {
      if (e.key === 'Enter') this.sendMessage();
    };
  }

  setupSignaling() {
    this.signalingSocket.on('open', () => {
      this.updateStatus('Connected to signaling server');
    });

    this.signalingSocket.on('message', (event) => {
      this.handleSignalingMessage(event.data);
    });

    this.signalingSocket.on('error', () => {
      this.updateStatus('Signaling error');
    });
  }

  async joinRoom() {
    const roomInput = document.getElementById('roomInput');
    this.roomId = roomInput.value.trim();

    if (!this.roomId) {
      alert('Please enter a room ID');
      return;
    }

    try {
      // Get user media
      await this.getUserMedia();

      // Join room via signaling
      this.signalingSocket.send({
        type: 'join-room',
        room: this.roomId,
      });

      document.getElementById('joinBtn').disabled = true;
      document.getElementById('leaveBtn').disabled = false;
    } catch (error) {
      console.error('Error joining room:', error);
      alert('Error accessing camera/microphone');
    }
  }

  leaveRoom() {
    if (this.peer) {
      this.peer.close();
      this.peer = null;
    }

    this.signalingSocket.send({
      type: 'leave-room',
      room: this.roomId,
    });

    document.getElementById('remoteVideo').srcObject = null;
    document.getElementById('joinBtn').disabled = false;
    document.getElementById('leaveBtn').disabled = true;

    this.updateStatus('Left room');
  }

  async getUserMedia() {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true,
      });

      document.getElementById('localVideo').srcObject = stream;
      return stream;
    } catch (error) {
      console.error('Error getting user media:', error);
      throw error;
    }
  }

  async handleSignalingMessage(message) {
    switch (message.type) {
      case 'room-joined':
        this.updateStatus(`Joined room: ${message.room}`);
        this.isInitiator = message.isInitiator;

        if (this.isInitiator) {
          await this.createPeerConnection();
          await this.createOffer();
        }
        break;

      case 'user-joined':
        this.updateStatus('Another user joined');
        if (!this.peer) {
          await this.createPeerConnection();
        }
        break;

      case 'offer':
        await this.handleOffer(message.offer);
        break;

      case 'answer':
        await this.handleAnswer(message.answer);
        break;

      case 'ice-candidate':
        await this.handleIceCandidate(message.candidate);
        break;

      case 'user-left':
        this.updateStatus('Other user left');
        document.getElementById('remoteVideo').srcObject = null;
        break;
    }
  }

  async createPeerConnection() {
    this.peer = new WebRTCPeer();

    // Add local stream
    const localStream = document.getElementById('localVideo').srcObject;
    if (localStream) {
      localStream.getTracks().forEach((track) => {
        this.peer.peerConnection.addTrack(track, localStream);
      });
    }

    // Setup event handlers
    this.peer.on('icecandidate', (candidate) => {
      this.signalingSocket.send({
        type: 'ice-candidate',
        room: this.roomId,
        candidate,
      });
    });

    this.peer.on('remotestream', (stream) => {
      document.getElementById('remoteVideo').srcObject = stream;
      this.updateStatus('Connected to peer');
    });

    this.peer.on('connected', () => {
      this.updateStatus('Peer connection established');
    });

    this.peer.on('disconnected', () => {
      this.updateStatus('Peer disconnected');
    });

    // Create data channel for chat
    this.peer.createDataChannel('chat');

    this.peer.on('datachannel-message', (data) => {
      this.addChatMessage(JSON.parse(data), false);
    });
  }

  async createOffer() {
    try {
      const offer = await this.peer.createOffer();

      this.signalingSocket.send({
        type: 'offer',
        room: this.roomId,
        offer,
      });
    } catch (error) {
      console.error('Error creating offer:', error);
    }
  }

  async handleOffer(offer) {
    if (!this.peer) {
      await this.createPeerConnection();
    }

    try {
      const answer = await this.peer.createAnswer(offer);

      this.signalingSocket.send({
        type: 'answer',
        room: this.roomId,
        answer,
      });
    } catch (error) {
      console.error('Error handling offer:', error);
    }
  }

  async handleAnswer(answer) {
    try {
      await this.peer.handleAnswer(answer);
    } catch (error) {
      console.error('Error handling answer:', error);
    }
  }

  async handleIceCandidate(candidate) {
    try {
      await this.peer.addIceCandidate(candidate);
    } catch (error) {
      console.error('Error adding ICE candidate:', error);
    }
  }

  toggleMute() {
    const localVideo = document.getElementById('localVideo');
    const audioTracks = localVideo.srcObject.getAudioTracks();

    if (audioTracks.length > 0) {
      const enabled = audioTracks[0].enabled;
      audioTracks[0].enabled = !enabled;

      const btn = document.getElementById('muteBtn');
      btn.textContent = enabled ? 'Unmute' : 'Mute';
    }
  }

  toggleVideo() {
    const localVideo = document.getElementById('localVideo');
    const videoTracks = localVideo.srcObject.getVideoTracks();

    if (videoTracks.length > 0) {
      const enabled = videoTracks[0].enabled;
      videoTracks[0].enabled = !enabled;

      const btn = document.getElementById('videoBtn');
      btn.textContent = enabled ? 'Video On' : 'Video Off';
    }
  }

  async shareScreen() {
    try {
      await this.peer.getDisplayMedia();
      this.updateStatus('Screen sharing started');
    } catch (error) {
      console.error('Error sharing screen:', error);
    }
  }

  sendMessage() {
    const input = document.getElementById('messageInput');
    const message = input.value.trim();

    if (message && this.peer) {
      const chatMessage = {
        text: message,
        timestamp: Date.now(),
      };

      this.peer.sendData(chatMessage);
      this.addChatMessage(chatMessage, true);
      input.value = '';
    }
  }

  addChatMessage(message, isLocal) {
    const messagesDiv = document.getElementById('messages');
    const messageElement = document.createElement('div');
    messageElement.className = `message ${isLocal ? 'local' : 'remote'}`;

    const time = new Date(message.timestamp).toLocaleTimeString();
    messageElement.innerHTML = `
      <div class="message-text">${message.text}</div>
      <div class="message-time">${time}</div>
    `;

    messagesDiv.appendChild(messageElement);
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
  }

  updateStatus(text) {
    document.getElementById('status').textContent = text;
  }
}

// Usage
const videoChat = new VideoChat('video-chat-container', 'ws://localhost:8080');

Conclusion

Real-time applications require careful consideration of communication patterns, connection management, and user experience. WebSockets provide full-duplex communication for interactive applications, Server-Sent Events offer simple server-to-client streaming, and WebRTC enables direct peer-to-peer communication. Choose the right technology based on your specific requirements: WebSockets for bidirectional real-time communication, SSE for live updates and notifications, and WebRTC for direct media communication. Remember to implement proper error handling, reconnection logic, and graceful degradation for robust real-time applications.