+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 117 of 354

πŸ”Œ WebSockets with TypeScript: Real-Time Communication

Master WebSockets for real-time communication with type-safe implementation, connection management, and production-ready patterns πŸš€

πŸš€Intermediate
20 min read

Prerequisites

  • Understanding of async/await and Promise patterns πŸ“
  • Basic knowledge of browser and Node.js APIs ⚑
  • Experience with event-driven programming πŸ’»

What you'll learn

  • Master WebSocket connections and real-time messaging 🎯
  • Build type-safe WebSocket communication patterns πŸ—οΈ
  • Implement connection resilience and error recovery πŸ›
  • Create production-ready real-time applications ✨

🎯 Introduction

Welcome to the real-time world of WebSockets! πŸ”Œ If HTTP is like sending letters through the mail, then WebSockets are like having a direct phone line - instant, bidirectional communication that makes real-time features like chat, live updates, and collaborative editing possible!

Think of WebSockets as persistent tunnels πŸš‡ between your client and server. Once established, both sides can send messages instantly without the overhead of HTTP headers, request/response cycles, or polling. It’s the backbone of modern real-time web applications!

By the end of this tutorial, you’ll be a master of real-time communication, able to build chat applications, live dashboards, collaborative tools, and any other real-time feature your imagination can conjure. Let’s connect to the future! 🌐

πŸ“š Understanding WebSockets

πŸ€” What Are WebSockets?

WebSockets provide full-duplex communication channels over a single TCP connection. They start with an HTTP handshake but then upgrade to a persistent connection that allows both client and server to send data at any time!

// 🌟 Basic WebSocket connection
// client.ts - Browser WebSocket client
class WebSocketClient {
  private socket: WebSocket | null = null;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;
  private reconnectDelay = 1000;

  constructor(private url: string) {}

  connect(): Promise<void> {
    return new Promise((resolve, reject) => {
      console.log('πŸ”Œ Connecting to WebSocket server...');
      
      this.socket = new WebSocket(this.url);

      this.socket.onopen = (event) => {
        console.log('βœ… WebSocket connection established!');
        this.reconnectAttempts = 0;
        resolve();
      };

      this.socket.onmessage = (event) => {
        console.log('πŸ“¨ Message received:', event.data);
        this.handleMessage(JSON.parse(event.data));
      };

      this.socket.onclose = (event) => {
        console.log('πŸ”Œ WebSocket connection closed:', event.code, event.reason);
        this.handleReconnect();
      };

      this.socket.onerror = (error) => {
        console.error('❌ WebSocket error:', error);
        reject(error);
      };
    });
  }

  send(message: any): void {
    if (this.socket?.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify(message));
    } else {
      console.warn('⚠️ WebSocket not connected. Message not sent:', message);
    }
  }

  private handleMessage(message: any): void {
    // Override in subclasses or provide callback
    console.log('πŸ“₯ Handling message:', message);
  }

  private handleReconnect(): void {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      console.log(`πŸ”„ Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
      
      setTimeout(() => {
        this.connect().catch(console.error);
      }, this.reconnectDelay * this.reconnectAttempts);
    } else {
      console.error('πŸ’₯ Max reconnection attempts reached');
    }
  }

  disconnect(): void {
    if (this.socket) {
      this.socket.close();
      this.socket = null;
    }
  }
}

πŸ’‘ Key Characteristics

  • πŸ”„ Bidirectional: Both client and server can send messages
  • ⚑ Low Latency: No HTTP overhead for each message
  • πŸ“‘ Real-Time: Instant message delivery
  • πŸ”Œ Persistent: Connection stays open until explicitly closed
// 🎨 Type-safe WebSocket message system
interface BaseMessage {
  type: string;
  timestamp: number;
  id: string;
}

interface ChatMessage extends BaseMessage {
  type: 'chat_message';
  content: string;
  userId: string;
  username: string;
}

interface UserJoinedMessage extends BaseMessage {
  type: 'user_joined';
  userId: string;
  username: string;
}

interface UserLeftMessage extends BaseMessage {
  type: 'user_left';
  userId: string;
  username: string;
}

interface TypingMessage extends BaseMessage {
  type: 'typing';
  userId: string;
  username: string;
  isTyping: boolean;
}

// 🎯 Union type for all possible messages
type WebSocketMessage = ChatMessage | UserJoinedMessage | UserLeftMessage | TypingMessage;

// πŸ“¦ Message factory for type safety
class MessageFactory {
  static createChatMessage(content: string, userId: string, username: string): ChatMessage {
    return {
      type: 'chat_message',
      content,
      userId,
      username,
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };
  }

  static createUserJoinedMessage(userId: string, username: string): UserJoinedMessage {
    return {
      type: 'user_joined',
      userId,
      username,
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };
  }

  static createTypingMessage(userId: string, username: string, isTyping: boolean): TypingMessage {
    return {
      type: 'typing',
      userId,
      username,
      isTyping,
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };
  }
}

πŸ†š WebSockets vs Other Real-Time Technologies

// 🐌 HTTP Polling - inefficient, delayed updates
class HTTPPollingClient {
  private intervalId: number | null = null;

  startPolling(url: string, interval = 1000): void {
    // ❌ Wasteful - constantly hitting server even when no updates
    this.intervalId = window.setInterval(async () => {
      try {
        const response = await fetch(url);
        const data = await response.json();
        this.handleUpdate(data);
      } catch (error) {
        console.error('Polling error:', error);
      }
    }, interval);
  }

  private handleUpdate(data: any): void {
    console.log('πŸ“Š Polled update:', data);
  }

  stopPolling(): void {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }
}

// πŸš€ Server-Sent Events - one-way real-time
class SSEClient {
  private eventSource: EventSource | null = null;

  connect(url: string): void {
    // βœ… Real-time but only server β†’ client
    this.eventSource = new EventSource(url);

    this.eventSource.onmessage = (event) => {
      console.log('πŸ“‘ SSE message:', JSON.parse(event.data));
    };

    this.eventSource.onerror = (error) => {
      console.error('❌ SSE error:', error);
    };
  }

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

// πŸ”Œ WebSockets - full duplex real-time (BEST!)
class RealTimeClient {
  private socket: WebSocket | null = null;

  connect(url: string): void {
    // βœ… Bidirectional real-time with low overhead
    this.socket = new WebSocket(url);

    this.socket.onmessage = (event) => {
      console.log('⚑ Real-time message:', JSON.parse(event.data));
    };

    // Can send anytime!
    setTimeout(() => {
      this.send({ type: 'ping', message: 'Hello server!' });
    }, 1000);
  }

  send(data: any): void {
    if (this.socket?.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify(data));
    }
  }
}

πŸ—οΈ Building a Type-Safe WebSocket Client

🎯 Advanced WebSocket Client Implementation

Let’s build a production-ready WebSocket client with TypeScript that handles all the edge cases and provides a clean API:

// πŸ—οΈ Advanced WebSocket client with full TypeScript support
interface WebSocketClientConfig {
  autoReconnect?: boolean;
  maxReconnectAttempts?: number;
  reconnectDelay?: number;
  heartbeatInterval?: number;
  connectionTimeout?: number;
}

interface WebSocketClientEvents {
  connected: () => void;
  disconnected: (reason: string) => void;
  message: (message: WebSocketMessage) => void;
  error: (error: Error) => void;
  reconnecting: (attempt: number) => void;
}

class TypeSafeWebSocketClient {
  private socket: WebSocket | null = null;
  private reconnectAttempts = 0;
  private heartbeatTimer: number | null = null;
  private connectionTimeoutTimer: number | null = null;
  private eventListeners: Partial<WebSocketClientEvents> = {};
  
  constructor(
    private url: string,
    private config: WebSocketClientConfig = {}
  ) {
    // πŸŽ›οΈ Default configuration
    this.config = {
      autoReconnect: true,
      maxReconnectAttempts: 5,
      reconnectDelay: 1000,
      heartbeatInterval: 30000,
      connectionTimeout: 10000,
      ...config
    };
  }

  // 🎧 Event listener management
  on<K extends keyof WebSocketClientEvents>(
    event: K,
    listener: WebSocketClientEvents[K]
  ): void {
    this.eventListeners[event] = listener;
  }

  off<K extends keyof WebSocketClientEvents>(event: K): void {
    delete this.eventListeners[event];
  }

  private emit<K extends keyof WebSocketClientEvents>(
    event: K,
    ...args: Parameters<NonNullable<WebSocketClientEvents[K]>>
  ): void {
    const listener = this.eventListeners[event];
    if (listener) {
      (listener as any)(...args);
    }
  }

  // πŸ”Œ Connection management
  async connect(): Promise<void> {
    return new Promise((resolve, reject) => {
      console.log('πŸ”Œ Initiating WebSocket connection...');
      
      // ⏰ Connection timeout
      this.connectionTimeoutTimer = window.setTimeout(() => {
        reject(new Error('Connection timeout'));
      }, this.config.connectionTimeout);

      try {
        this.socket = new WebSocket(this.url);
        this.setupEventHandlers(resolve, reject);
      } catch (error) {
        this.clearConnectionTimeout();
        reject(error);
      }
    });
  }

  private setupEventHandlers(
    resolve: () => void,
    reject: (error: Error) => void
  ): void {
    if (!this.socket) return;

    this.socket.onopen = () => {
      console.log('βœ… WebSocket connected successfully!');
      this.clearConnectionTimeout();
      this.reconnectAttempts = 0;
      this.startHeartbeat();
      this.emit('connected');
      resolve();
    };

    this.socket.onmessage = (event) => {
      try {
        const message: WebSocketMessage = JSON.parse(event.data);
        
        // πŸ’“ Handle heartbeat responses
        if (message.type === 'pong') {
          console.log('πŸ’“ Heartbeat response received');
          return;
        }

        console.log('πŸ“¨ Message received:', message.type);
        this.emit('message', message);
      } catch (error) {
        console.error('❌ Failed to parse message:', error);
        this.emit('error', new Error('Failed to parse message'));
      }
    };

    this.socket.onclose = (event) => {
      console.log(`πŸ”Œ WebSocket closed: ${event.code} - ${event.reason}`);
      this.cleanup();
      this.emit('disconnected', event.reason);
      
      if (this.config.autoReconnect && event.code !== 1000) {
        this.attemptReconnect();
      }
    };

    this.socket.onerror = (error) => {
      console.error('❌ WebSocket error:', error);
      this.clearConnectionTimeout();
      this.emit('error', new Error('WebSocket connection error'));
      reject(new Error('WebSocket connection error'));
    };
  }

  // πŸ’“ Heartbeat mechanism
  private startHeartbeat(): void {
    if (this.config.heartbeatInterval && this.config.heartbeatInterval > 0) {
      this.heartbeatTimer = window.setInterval(() => {
        if (this.isConnected()) {
          console.log('πŸ’“ Sending heartbeat...');
          this.send({
            type: 'ping',
            timestamp: Date.now(),
            id: crypto.randomUUID()
          } as any);
        }
      }, this.config.heartbeatInterval);
    }
  }

  private stopHeartbeat(): void {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }

  // πŸ”„ Reconnection logic
  private attemptReconnect(): void {
    if (this.reconnectAttempts >= (this.config.maxReconnectAttempts || 5)) {
      console.error('πŸ’₯ Max reconnection attempts exceeded');
      return;
    }

    this.reconnectAttempts++;
    const delay = (this.config.reconnectDelay || 1000) * this.reconnectAttempts;
    
    console.log(`πŸ”„ Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
    this.emit('reconnecting', this.reconnectAttempts);

    setTimeout(() => {
      this.connect().catch((error) => {
        console.error('❌ Reconnection failed:', error);
      });
    }, delay);
  }

  // πŸ“€ Message sending with type safety
  send<T extends WebSocketMessage>(message: T): boolean {
    if (!this.isConnected()) {
      console.warn('⚠️ Cannot send message: WebSocket not connected');
      return false;
    }

    try {
      this.socket!.send(JSON.stringify(message));
      console.log('πŸ“€ Message sent:', message.type);
      return true;
    } catch (error) {
      console.error('❌ Failed to send message:', error);
      this.emit('error', new Error('Failed to send message'));
      return false;
    }
  }

  // πŸ” Connection state
  isConnected(): boolean {
    return this.socket?.readyState === WebSocket.OPEN;
  }

  getReadyState(): string {
    if (!this.socket) return 'DISCONNECTED';
    
    switch (this.socket.readyState) {
      case WebSocket.CONNECTING: return 'CONNECTING';
      case WebSocket.OPEN: return 'OPEN';
      case WebSocket.CLOSING: return 'CLOSING';
      case WebSocket.CLOSED: return 'CLOSED';
      default: return 'UNKNOWN';
    }
  }

  // 🧹 Cleanup
  private clearConnectionTimeout(): void {
    if (this.connectionTimeoutTimer) {
      clearTimeout(this.connectionTimeoutTimer);
      this.connectionTimeoutTimer = null;
    }
  }

  private cleanup(): void {
    this.stopHeartbeat();
    this.clearConnectionTimeout();
  }

  // πŸ”Œ Disconnect
  disconnect(code = 1000, reason = 'Client disconnect'): void {
    if (this.socket) {
      console.log('πŸ”Œ Disconnecting WebSocket...');
      this.config.autoReconnect = false; // Prevent auto-reconnect
      this.socket.close(code, reason);
    }
    this.cleanup();
  }
}

πŸ› οΈ Real-World Chat Application

Let’s build a complete chat application that demonstrates real-world WebSocket usage:

// πŸ’¬ Chat application using our WebSocket client
interface User {
  id: string;
  username: string;
  avatar?: string;
  isOnline: boolean;
}

interface ChatRoom {
  id: string;
  name: string;
  users: User[];
  messageCount: number;
}

class ChatApplication {
  private client: TypeSafeWebSocketClient;
  private currentUser: User | null = null;
  private currentRoom: ChatRoom | null = null;
  private users = new Map<string, User>();
  private messageHistory: ChatMessage[] = [];
  private typingUsers = new Set<string>();
  private typingTimer: number | null = null;

  constructor(private websocketUrl: string) {
    this.client = new TypeSafeWebSocketClient(websocketUrl, {
      autoReconnect: true,
      maxReconnectAttempts: 10,
      heartbeatInterval: 30000
    });

    this.setupEventHandlers();
  }

  private setupEventHandlers(): void {
    // 🎧 WebSocket event handlers
    this.client.on('connected', () => {
      console.log('πŸŽ‰ Connected to chat server!');
      this.authenticateUser();
    });

    this.client.on('disconnected', (reason) => {
      console.log('😞 Disconnected from chat server:', reason);
      this.handleDisconnection();
    });

    this.client.on('message', (message) => {
      this.handleIncomingMessage(message);
    });

    this.client.on('error', (error) => {
      console.error('πŸ’₯ Chat client error:', error);
      this.handleError(error);
    });

    this.client.on('reconnecting', (attempt) => {
      console.log(`πŸ”„ Reconnecting to chat server (attempt ${attempt})...`);
    });
  }

  // πŸš€ Initialize chat application
  async initialize(username: string): Promise<void> {
    this.currentUser = {
      id: crypto.randomUUID(),
      username,
      isOnline: true
    };

    try {
      await this.client.connect();
      console.log('βœ… Chat application initialized!');
    } catch (error) {
      console.error('❌ Failed to initialize chat:', error);
      throw error;
    }
  }

  private authenticateUser(): void {
    if (!this.currentUser) return;

    const authMessage = MessageFactory.createAuthMessage(
      this.currentUser.id,
      this.currentUser.username
    );

    this.client.send(authMessage);
  }

  // πŸ’¬ Send chat message
  sendMessage(content: string): void {
    if (!this.currentUser || !this.currentRoom) {
      console.warn('⚠️ Cannot send message: User not authenticated or not in room');
      return;
    }

    const message = MessageFactory.createChatMessage(
      content,
      this.currentUser.id,
      this.currentUser.username
    );

    const success = this.client.send(message);
    if (success) {
      // Add to local history immediately for responsiveness
      this.messageHistory.push(message);
      this.notifyMessageSent(message);
    }
  }

  // πŸ“ Handle typing indicators
  startTyping(): void {
    if (!this.currentUser) return;

    const typingMessage = MessageFactory.createTypingMessage(
      this.currentUser.id,
      this.currentUser.username,
      true
    );

    this.client.send(typingMessage);

    // πŸ• Auto-stop typing after 3 seconds
    if (this.typingTimer) {
      clearTimeout(this.typingTimer);
    }

    this.typingTimer = window.setTimeout(() => {
      this.stopTyping();
    }, 3000);
  }

  stopTyping(): void {
    if (!this.currentUser) return;

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

    const typingMessage = MessageFactory.createTypingMessage(
      this.currentUser.id,
      this.currentUser.username,
      false
    );

    this.client.send(typingMessage);
  }

  // 🏠 Room management
  joinRoom(roomId: string): void {
    if (!this.currentUser) return;

    const joinMessage: JoinRoomMessage = {
      type: 'join_room',
      roomId,
      userId: this.currentUser.id,
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };

    this.client.send(joinMessage);
  }

  leaveRoom(): void {
    if (!this.currentUser || !this.currentRoom) return;

    const leaveMessage: LeaveRoomMessage = {
      type: 'leave_room',
      roomId: this.currentRoom.id,
      userId: this.currentUser.id,
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };

    this.client.send(leaveMessage);
    this.currentRoom = null;
    this.messageHistory = [];
    this.users.clear();
  }

  // πŸ“¨ Handle incoming messages
  private handleIncomingMessage(message: WebSocketMessage): void {
    switch (message.type) {
      case 'chat_message':
        this.handleChatMessage(message);
        break;
      
      case 'user_joined':
        this.handleUserJoined(message);
        break;
      
      case 'user_left':
        this.handleUserLeft(message);
        break;
      
      case 'typing':
        this.handleTypingIndicator(message);
        break;
      
      case 'room_joined':
        this.handleRoomJoined(message as RoomJoinedMessage);
        break;
      
      case 'user_list':
        this.handleUserList(message as UserListMessage);
        break;
      
      default:
        console.warn('🀷 Unknown message type:', (message as any).type);
    }
  }

  private handleChatMessage(message: ChatMessage): void {
    console.log(`πŸ’¬ ${message.username}: ${message.content}`);
    
    // Avoid duplicates (message might already be in history if sent by us)
    const exists = this.messageHistory.some(m => m.id === message.id);
    if (!exists) {
      this.messageHistory.push(message);
    }
    
    this.notifyNewMessage(message);
  }

  private handleUserJoined(message: UserJoinedMessage): void {
    console.log(`πŸ‘‹ ${message.username} joined the chat`);
    
    const user: User = {
      id: message.userId,
      username: message.username,
      isOnline: true
    };
    
    this.users.set(user.id, user);
    this.notifyUserJoined(user);
  }

  private handleUserLeft(message: UserLeftMessage): void {
    console.log(`πŸ‘‹ ${message.username} left the chat`);
    
    const user = this.users.get(message.userId);
    if (user) {
      user.isOnline = false;
      this.notifyUserLeft(user);
    }
  }

  private handleTypingIndicator(message: TypingMessage): void {
    if (message.userId === this.currentUser?.id) return; // Ignore our own typing

    if (message.isTyping) {
      this.typingUsers.add(message.username);
      console.log(`✏️ ${message.username} is typing...`);
    } else {
      this.typingUsers.delete(message.username);
    }
    
    this.notifyTypingUpdate();
  }

  private handleRoomJoined(message: RoomJoinedMessage): void {
    this.currentRoom = {
      id: message.roomId,
      name: message.roomName,
      users: [],
      messageCount: 0
    };
    
    console.log(`🏠 Joined room: ${message.roomName}`);
    this.notifyRoomJoined(this.currentRoom);
  }

  private handleUserList(message: UserListMessage): void {
    this.users.clear();
    
    message.users.forEach(userData => {
      const user: User = {
        id: userData.id,
        username: userData.username,
        isOnline: true
      };
      this.users.set(user.id, user);
    });
    
    if (this.currentRoom) {
      this.currentRoom.users = Array.from(this.users.values());
    }
    
    this.notifyUserListUpdate();
  }

  private handleDisconnection(): void {
    // Mark all users as offline
    this.users.forEach(user => {
      user.isOnline = false;
    });
    
    this.typingUsers.clear();
    this.notifyDisconnection();
  }

  private handleError(error: Error): void {
    this.notifyError(error);
  }

  // πŸ”” Event notifications (override these in your UI layer)
  protected notifyNewMessage(message: ChatMessage): void {
    console.log('πŸ”” New message notification');
  }

  protected notifyMessageSent(message: ChatMessage): void {
    console.log('βœ… Message sent confirmation');
  }

  protected notifyUserJoined(user: User): void {
    console.log('πŸ”” User joined notification');
  }

  protected notifyUserLeft(user: User): void {
    console.log('πŸ”” User left notification');
  }

  protected notifyTypingUpdate(): void {
    const typingList = Array.from(this.typingUsers);
    console.log('✏️ Typing users:', typingList);
  }

  protected notifyRoomJoined(room: ChatRoom): void {
    console.log('🏠 Room joined notification');
  }

  protected notifyUserListUpdate(): void {
    console.log('πŸ‘₯ User list updated');
  }

  protected notifyDisconnection(): void {
    console.log('πŸ”Œ Disconnection notification');
  }

  protected notifyError(error: Error): void {
    console.error('❌ Error notification:', error);
  }

  // πŸ“Š Getters for current state
  get onlineUsers(): User[] {
    return Array.from(this.users.values()).filter(u => u.isOnline);
  }

  get messages(): ChatMessage[] {
    return [...this.messageHistory];
  }

  get currentTypingUsers(): string[] {
    return Array.from(this.typingUsers);
  }

  get isConnected(): boolean {
    return this.client.isConnected();
  }

  get connectionState(): string {
    return this.client.getReadyState();
  }

  // πŸ”Œ Cleanup
  disconnect(): void {
    this.stopTyping();
    this.client.disconnect();
  }
}

// πŸ“ Additional message types for complete chat functionality
interface AuthMessage extends BaseMessage {
  type: 'auth';
  userId: string;
  username: string;
}

interface JoinRoomMessage extends BaseMessage {
  type: 'join_room';
  roomId: string;
  userId: string;
}

interface LeaveRoomMessage extends BaseMessage {
  type: 'leave_room';
  roomId: string;
  userId: string;
}

interface RoomJoinedMessage extends BaseMessage {
  type: 'room_joined';
  roomId: string;
  roomName: string;
}

interface UserListMessage extends BaseMessage {
  type: 'user_list';
  users: Array<{
    id: string;
    username: string;
  }>;
}

// 🏭 Extended message factory
class ExtendedMessageFactory extends MessageFactory {
  static createAuthMessage(userId: string, username: string): AuthMessage {
    return {
      type: 'auth',
      userId,
      username,
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };
  }
}

πŸ› οΈ Server-Side WebSocket Implementation

πŸ—οΈ Node.js WebSocket Server with TypeScript

Let’s create a robust WebSocket server using Node.js and the ws library:

// πŸ–₯️ Server-side WebSocket implementation
import WebSocket from 'ws';
import { createServer } from 'http';
import { v4 as uuidv4 } from 'uuid';

interface ConnectedClient {
  id: string;
  socket: WebSocket;
  user?: {
    id: string;
    username: string;
  };
  room?: string;
  isAlive: boolean;
  lastPong: number;
}

interface ChatRoom {
  id: string;
  name: string;
  clients: Set<string>;
  messageHistory: ChatMessage[];
  createdAt: number;
}

class WebSocketChatServer {
  private wss: WebSocket.Server;
  private clients = new Map<string, ConnectedClient>();
  private rooms = new Map<string, ChatRoom>();
  private heartbeatInterval: NodeJS.Timeout;

  constructor(port = 8080) {
    // πŸ—οΈ Create HTTP server and WebSocket server
    const server = createServer();
    this.wss = new WebSocket.Server({ server });

    // 🎧 Setup WebSocket event handlers
    this.wss.on('connection', (socket, request) => {
      this.handleConnection(socket, request);
    });

    // πŸ’“ Setup heartbeat mechanism
    this.heartbeatInterval = setInterval(() => {
      this.performHeartbeat();
    }, 30000);

    // πŸš€ Start server
    server.listen(port, () => {
      console.log(`πŸš€ WebSocket server running on port ${port}`);
    });

    // 🧹 Cleanup on shutdown
    process.on('SIGTERM', () => this.shutdown());
    process.on('SIGINT', () => this.shutdown());
  }

  private handleConnection(socket: WebSocket, request: any): void {
    const clientId = uuidv4();
    const client: ConnectedClient = {
      id: clientId,
      socket,
      isAlive: true,
      lastPong: Date.now()
    };

    this.clients.set(clientId, client);

    console.log(`πŸ”Œ New client connected: ${clientId}`);
    console.log(`πŸ‘₯ Total clients: ${this.clients.size}`);

    // 🎧 Setup client event handlers
    socket.on('message', (data) => {
      this.handleMessage(clientId, data);
    });

    socket.on('close', (code, reason) => {
      this.handleDisconnection(clientId, code, reason.toString());
    });

    socket.on('error', (error) => {
      console.error(`❌ Client ${clientId} error:`, error);
    });

    socket.on('pong', () => {
      const client = this.clients.get(clientId);
      if (client) {
        client.isAlive = true;
        client.lastPong = Date.now();
      }
    });

    // πŸ‘‹ Send welcome message
    this.sendToClient(clientId, {
      type: 'server_message',
      content: 'Welcome to the chat server!',
      timestamp: Date.now(),
      id: uuidv4()
    } as any);
  }

  private handleMessage(clientId: string, data: WebSocket.Data): void {
    const client = this.clients.get(clientId);
    if (!client) return;

    try {
      const message: WebSocketMessage = JSON.parse(data.toString());
      console.log(`πŸ“¨ Message from ${clientId}:`, message.type);

      switch (message.type) {
        case 'auth':
          this.handleAuth(clientId, message as AuthMessage);
          break;
        
        case 'join_room':
          this.handleJoinRoom(clientId, message as JoinRoomMessage);
          break;
        
        case 'leave_room':
          this.handleLeaveRoom(clientId, message as LeaveRoomMessage);
          break;
        
        case 'chat_message':
          this.handleChatMessage(clientId, message as ChatMessage);
          break;
        
        case 'typing':
          this.handleTyping(clientId, message as TypingMessage);
          break;
        
        case 'ping':
          this.handlePing(clientId);
          break;
        
        default:
          console.warn(`🀷 Unknown message type: ${(message as any).type}`);
      }
    } catch (error) {
      console.error(`❌ Failed to parse message from ${clientId}:`, error);
      this.sendToClient(clientId, {
        type: 'error',
        message: 'Invalid message format',
        timestamp: Date.now(),
        id: uuidv4()
      } as any);
    }
  }

  private handleAuth(clientId: string, message: AuthMessage): void {
    const client = this.clients.get(clientId);
    if (!client) return;

    // πŸ” Set user information
    client.user = {
      id: message.userId,
      username: message.username
    };

    console.log(`βœ… Client ${clientId} authenticated as ${message.username}`);

    // πŸ“ Send authentication confirmation
    this.sendToClient(clientId, {
      type: 'auth_success',
      userId: message.userId,
      username: message.username,
      timestamp: Date.now(),
      id: uuidv4()
    } as any);

    // 🏠 Send available rooms
    this.sendRoomList(clientId);
  }

  private handleJoinRoom(clientId: string, message: JoinRoomMessage): void {
    const client = this.clients.get(clientId);
    if (!client || !client.user) return;

    const roomId = message.roomId;
    
    // 🏠 Create room if it doesn't exist
    if (!this.rooms.has(roomId)) {
      this.rooms.set(roomId, {
        id: roomId,
        name: `Room ${roomId}`,
        clients: new Set(),
        messageHistory: [],
        createdAt: Date.now()
      });
      console.log(`🏠 Created new room: ${roomId}`);
    }

    const room = this.rooms.get(roomId)!;

    // πŸšͺ Leave current room if in one
    if (client.room) {
      this.leaveClientFromRoom(clientId);
    }

    // 🏠 Join new room
    client.room = roomId;
    room.clients.add(clientId);

    console.log(`🏠 ${client.user.username} joined room ${roomId}`);

    // πŸ“ Send room joined confirmation
    this.sendToClient(clientId, {
      type: 'room_joined',
      roomId,
      roomName: room.name,
      timestamp: Date.now(),
      id: uuidv4()
    } as any);

    // πŸ“œ Send message history
    room.messageHistory.forEach(msg => {
      this.sendToClient(clientId, msg);
    });

    // πŸ‘₯ Send user list
    this.sendUserListToRoom(roomId);

    // πŸ“’ Announce user joined to room
    const joinMessage: UserJoinedMessage = {
      type: 'user_joined',
      userId: client.user.id,
      username: client.user.username,
      timestamp: Date.now(),
      id: uuidv4()
    };

    this.broadcastToRoom(roomId, joinMessage, clientId);
  }

  private handleLeaveRoom(clientId: string, message: LeaveRoomMessage): void {
    this.leaveClientFromRoom(clientId);
  }

  private leaveClientFromRoom(clientId: string): void {
    const client = this.clients.get(clientId);
    if (!client || !client.room || !client.user) return;

    const room = this.rooms.get(client.room);
    if (!room) return;

    // πŸšͺ Remove from room
    room.clients.delete(clientId);
    console.log(`πŸšͺ ${client.user.username} left room ${client.room}`);

    // πŸ“’ Announce user left to room
    const leftMessage: UserLeftMessage = {
      type: 'user_left',
      userId: client.user.id,
      username: client.user.username,
      timestamp: Date.now(),
      id: uuidv4()
    };

    this.broadcastToRoom(client.room, leftMessage, clientId);

    // πŸ‘₯ Update user list for remaining clients
    this.sendUserListToRoom(client.room);

    // 🧹 Clean up empty rooms
    if (room.clients.size === 0) {
      this.rooms.delete(client.room);
      console.log(`πŸ—‘οΈ Removed empty room: ${client.room}`);
    }

    client.room = undefined;
  }

  private handleChatMessage(clientId: string, message: ChatMessage): void {
    const client = this.clients.get(clientId);
    if (!client || !client.room || !client.user) {
      console.warn(`⚠️ Unauthorized chat message from ${clientId}`);
      return;
    }

    const room = this.rooms.get(client.room);
    if (!room) return;

    // πŸ’Ύ Store message in room history
    room.messageHistory.push(message);

    // πŸ“ Limit history size (keep last 100 messages)
    if (room.messageHistory.length > 100) {
      room.messageHistory = room.messageHistory.slice(-100);
    }

    console.log(`πŸ’¬ ${client.user.username} in ${client.room}: ${message.content}`);

    // πŸ“’ Broadcast message to all room members
    this.broadcastToRoom(client.room, message);
  }

  private handleTyping(clientId: string, message: TypingMessage): void {
    const client = this.clients.get(clientId);
    if (!client || !client.room) return;

    // πŸ“’ Broadcast typing indicator to room (except sender)
    this.broadcastToRoom(client.room, message, clientId);
  }

  private handlePing(clientId: string): void {
    // πŸ’“ Respond with pong
    this.sendToClient(clientId, {
      type: 'pong',
      timestamp: Date.now(),
      id: uuidv4()
    } as any);
  }

  private handleDisconnection(clientId: string, code: number, reason: string): void {
    const client = this.clients.get(clientId);
    
    console.log(`πŸ”Œ Client ${clientId} disconnected: ${code} - ${reason}`);
    
    if (client && client.room) {
      this.leaveClientFromRoom(clientId);
    }

    this.clients.delete(clientId);
    console.log(`πŸ‘₯ Total clients: ${this.clients.size}`);
  }

  // πŸ’“ Heartbeat mechanism
  private performHeartbeat(): void {
    const now = Date.now();
    
    this.clients.forEach((client, clientId) => {
      if (!client.isAlive || (now - client.lastPong) > 60000) {
        // πŸ’€ Client is dead, terminate connection
        console.log(`πŸ’€ Terminating dead client: ${clientId}`);
        client.socket.terminate();
        return;
      }

      client.isAlive = false;
      client.socket.ping();
    });
  }

  // πŸ“€ Send message to specific client
  private sendToClient(clientId: string, message: any): void {
    const client = this.clients.get(clientId);
    if (!client || client.socket.readyState !== WebSocket.OPEN) return;

    try {
      client.socket.send(JSON.stringify(message));
    } catch (error) {
      console.error(`❌ Failed to send message to ${clientId}:`, error);
    }
  }

  // πŸ“’ Broadcast message to all clients in a room
  private broadcastToRoom(roomId: string, message: any, excludeClientId?: string): void {
    const room = this.rooms.get(roomId);
    if (!room) return;

    room.clients.forEach(clientId => {
      if (clientId !== excludeClientId) {
        this.sendToClient(clientId, message);
      }
    });
  }

  // 🏠 Send room list to client
  private sendRoomList(clientId: string): void {
    const roomList = Array.from(this.rooms.values()).map(room => ({
      id: room.id,
      name: room.name,
      userCount: room.clients.size
    }));

    this.sendToClient(clientId, {
      type: 'room_list',
      rooms: roomList,
      timestamp: Date.now(),
      id: uuidv4()
    });
  }

  // πŸ‘₯ Send user list to all clients in room
  private sendUserListToRoom(roomId: string): void {
    const room = this.rooms.get(roomId);
    if (!room) return;

    const users = Array.from(room.clients)
      .map(clientId => {
        const client = this.clients.get(clientId);
        return client?.user ? {
          id: client.user.id,
          username: client.user.username
        } : null;
      })
      .filter(user => user !== null);

    const userListMessage = {
      type: 'user_list',
      users,
      timestamp: Date.now(),
      id: uuidv4()
    };

    this.broadcastToRoom(roomId, userListMessage);
  }

  // πŸ“Š Server statistics
  getStats(): any {
    return {
      totalClients: this.clients.size,
      totalRooms: this.rooms.size,
      authenticatedClients: Array.from(this.clients.values()).filter(c => c.user).length,
      roomsWithClients: Array.from(this.rooms.values()).filter(r => r.clients.size > 0).length
    };
  }

  // πŸ”Œ Shutdown server gracefully
  private shutdown(): void {
    console.log('πŸ›‘ Shutting down WebSocket server...');
    
    clearInterval(this.heartbeatInterval);
    
    // πŸ‘‹ Send goodbye message to all clients
    this.clients.forEach((client, clientId) => {
      this.sendToClient(clientId, {
        type: 'server_shutdown',
        message: 'Server is shutting down',
        timestamp: Date.now(),
        id: uuidv4()
      });
      
      setTimeout(() => {
        client.socket.close(1001, 'Server shutdown');
      }, 1000);
    });

    // πŸ”Œ Close WebSocket server
    setTimeout(() => {
      this.wss.close(() => {
        console.log('βœ… WebSocket server closed');
        process.exit(0);
      });
    }, 2000);
  }
}

// πŸš€ Start the server
const chatServer = new WebSocketChatServer(8080);

// πŸ“Š Log stats every 30 seconds
setInterval(() => {
  const stats = chatServer.getStats();
  console.log('πŸ“Š Server stats:', stats);
}, 30000);

🎨 Advanced WebSocket Patterns

πŸ”„ Connection Pool Management

For applications that need to manage multiple WebSocket connections:

// 🏊 WebSocket connection pool for managing multiple connections
interface ConnectionConfig {
  url: string;
  protocols?: string[];
  autoReconnect?: boolean;
  maxReconnectAttempts?: number;
}

class WebSocketConnectionPool {
  private connections = new Map<string, TypeSafeWebSocketClient>();
  private connectionConfigs = new Map<string, ConnectionConfig>();

  async addConnection(
    name: string,
    config: ConnectionConfig
  ): Promise<TypeSafeWebSocketClient> {
    if (this.connections.has(name)) {
      throw new Error(`Connection '${name}' already exists`);
    }

    console.log(`πŸ”Œ Adding connection: ${name}`);
    
    const client = new TypeSafeWebSocketClient(config.url, {
      autoReconnect: config.autoReconnect ?? true,
      maxReconnectAttempts: config.maxReconnectAttempts ?? 5
    });

    // 🎧 Setup common event handlers
    client.on('connected', () => {
      console.log(`βœ… Connection '${name}' established`);
    });

    client.on('disconnected', (reason) => {
      console.log(`πŸ”Œ Connection '${name}' disconnected: ${reason}`);
    });

    client.on('error', (error) => {
      console.error(`❌ Connection '${name}' error:`, error);
    });

    try {
      await client.connect();
      this.connections.set(name, client);
      this.connectionConfigs.set(name, config);
      return client;
    } catch (error) {
      console.error(`❌ Failed to add connection '${name}':`, error);
      throw error;
    }
  }

  getConnection(name: string): TypeSafeWebSocketClient | undefined {
    return this.connections.get(name);
  }

  async removeConnection(name: string): Promise<void> {
    const connection = this.connections.get(name);
    if (!connection) {
      console.warn(`⚠️ Connection '${name}' not found`);
      return;
    }

    console.log(`πŸ—‘οΈ Removing connection: ${name}`);
    connection.disconnect();
    this.connections.delete(name);
    this.connectionConfigs.delete(name);
  }

  // πŸ“’ Broadcast message to all connections
  broadcastToAll(message: any): void {
    this.connections.forEach((connection, name) => {
      if (connection.isConnected()) {
        connection.send(message);
      } else {
        console.warn(`⚠️ Connection '${name}' not connected, skipping broadcast`);
      }
    });
  }

  // πŸ“’ Broadcast to specific connections
  broadcastToConnections(connectionNames: string[], message: any): void {
    connectionNames.forEach(name => {
      const connection = this.connections.get(name);
      if (connection?.isConnected()) {
        connection.send(message);
      } else {
        console.warn(`⚠️ Connection '${name}' not found or not connected`);
      }
    });
  }

  // πŸ“Š Get pool status
  getStatus(): Array<{ name: string; connected: boolean; state: string }> {
    return Array.from(this.connections.entries()).map(([name, connection]) => ({
      name,
      connected: connection.isConnected(),
      state: connection.getReadyState()
    }));
  }

  // πŸ”„ Reconnect all disconnected connections
  async reconnectAll(): Promise<void> {
    const reconnectPromises = Array.from(this.connections.entries())
      .filter(([, connection]) => !connection.isConnected())
      .map(async ([name, connection]) => {
        try {
          console.log(`πŸ”„ Reconnecting '${name}'...`);
          await connection.connect();
          console.log(`βœ… '${name}' reconnected`);
        } catch (error) {
          console.error(`❌ Failed to reconnect '${name}':`, error);
        }
      });

    await Promise.allSettled(reconnectPromises);
  }

  // 🧹 Cleanup all connections
  async cleanup(): Promise<void> {
    console.log('🧹 Cleaning up WebSocket connection pool...');
    
    const cleanupPromises = Array.from(this.connections.keys()).map(name =>
      this.removeConnection(name)
    );

    await Promise.allSettled(cleanupPromises);
    console.log('βœ… Connection pool cleanup complete');
  }
}

πŸ” WebSocket Authentication and Security

Implementing secure WebSocket connections with authentication:

// πŸ” Secure WebSocket client with authentication
interface AuthConfig {
  token?: string;
  apiKey?: string;
  username?: string;
  password?: string;
}

class SecureWebSocketClient extends TypeSafeWebSocketClient {
  private authConfig: AuthConfig;
  private isAuthenticated = false;

  constructor(url: string, authConfig: AuthConfig, config?: WebSocketClientConfig) {
    super(url, config);
    this.authConfig = authConfig;
  }

  async connect(): Promise<void> {
    // πŸ”Œ Connect to WebSocket
    await super.connect();
    
    // πŸ” Authenticate after connection
    await this.authenticate();
  }

  private async authenticate(): Promise<void> {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Authentication timeout'));
      }, 10000);

      // 🎧 Listen for auth response
      const originalMessageHandler = this.handleMessage;
      this.handleMessage = (message: WebSocketMessage) => {
        if ((message as any).type === 'auth_success') {
          console.log('βœ… Authentication successful!');
          clearTimeout(timeout);
          this.isAuthenticated = true;
          this.handleMessage = originalMessageHandler;
          resolve();
        } else if ((message as any).type === 'auth_error') {
          console.error('❌ Authentication failed:', (message as any).error);
          clearTimeout(timeout);
          reject(new Error((message as any).error || 'Authentication failed'));
        } else {
          // Pass other messages to original handler
          originalMessageHandler.call(this, message);
        }
      };

      // πŸ“€ Send authentication message
      const authMessage = this.createAuthMessage();
      super.send(authMessage);
    });
  }

  private createAuthMessage(): any {
    const baseMessage = {
      type: 'authenticate',
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };

    if (this.authConfig.token) {
      return {
        ...baseMessage,
        method: 'token',
        token: this.authConfig.token
      };
    }

    if (this.authConfig.apiKey) {
      return {
        ...baseMessage,
        method: 'api_key',
        apiKey: this.authConfig.apiKey
      };
    }

    if (this.authConfig.username && this.authConfig.password) {
      return {
        ...baseMessage,
        method: 'credentials',
        username: this.authConfig.username,
        password: this.authConfig.password
      };
    }

    throw new Error('No valid authentication method provided');
  }

  // πŸ”’ Override send to ensure authentication
  send<T extends WebSocketMessage>(message: T): boolean {
    if (!this.isAuthenticated) {
      console.warn('⚠️ Cannot send message: Not authenticated');
      return false;
    }

    return super.send(message);
  }

  // πŸ” Update authentication credentials
  updateAuth(newAuthConfig: AuthConfig): void {
    this.authConfig = { ...this.authConfig, ...newAuthConfig };
    this.isAuthenticated = false;
    
    if (this.isConnected()) {
      this.authenticate().catch(error => {
        console.error('❌ Re-authentication failed:', error);
        this.emit('error', error);
      });
    }
  }

  get authenticated(): boolean {
    return this.isAuthenticated;
  }
}

// πŸ›‘οΈ Message encryption for sensitive data
class EncryptedWebSocketClient extends SecureWebSocketClient {
  private encryptionKey: string;

  constructor(
    url: string,
    authConfig: AuthConfig,
    encryptionKey: string,
    config?: WebSocketClientConfig
  ) {
    super(url, authConfig, config);
    this.encryptionKey = encryptionKey;
  }

  // πŸ” Encrypt message before sending
  send<T extends WebSocketMessage>(message: T): boolean {
    try {
      const encryptedMessage = {
        type: 'encrypted',
        data: this.encrypt(JSON.stringify(message)),
        timestamp: Date.now(),
        id: crypto.randomUUID()
      };

      return super.send(encryptedMessage as any);
    } catch (error) {
      console.error('❌ Failed to encrypt message:', error);
      return false;
    }
  }

  // πŸ”“ Decrypt incoming messages
  protected handleMessage(message: WebSocketMessage): void {
    if ((message as any).type === 'encrypted') {
      try {
        const decryptedData = this.decrypt((message as any).data);
        const originalMessage = JSON.parse(decryptedData);
        super.handleMessage(originalMessage);
      } catch (error) {
        console.error('❌ Failed to decrypt message:', error);
      }
    } else {
      super.handleMessage(message);
    }
  }

  // πŸ” Simple encryption (use a proper crypto library in production!)
  private encrypt(data: string): string {
    // ⚠️ This is a simple example - use proper encryption in production!
    return btoa(data + this.encryptionKey);
  }

  // πŸ”“ Simple decryption
  private decrypt(encryptedData: string): string {
    // ⚠️ This is a simple example - use proper decryption in production!
    const decoded = atob(encryptedData);
    return decoded.substring(0, decoded.length - this.encryptionKey.length);
  }
}

πŸš€ Performance Optimization and Best Practices

⚑ Message Batching and Throttling

For high-frequency applications, implement message batching:

// ⚑ High-performance WebSocket client with message batching
interface BatchConfig {
  maxBatchSize: number;
  maxBatchDelay: number;
  enableCompression: boolean;
}

class BatchedWebSocketClient extends TypeSafeWebSocketClient {
  private messageBatch: WebSocketMessage[] = [];
  private batchTimer: number | null = null;
  private batchConfig: BatchConfig;

  constructor(
    url: string,
    batchConfig: BatchConfig = {
      maxBatchSize: 10,
      maxBatchDelay: 100,
      enableCompression: false
    },
    config?: WebSocketClientConfig
  ) {
    super(url, config);
    this.batchConfig = batchConfig;
  }

  // πŸ“¦ Batched message sending
  send<T extends WebSocketMessage>(message: T): boolean {
    if (!this.isConnected()) {
      console.warn('⚠️ Cannot batch message: WebSocket not connected');
      return false;
    }

    // πŸ“₯ Add message to batch
    this.messageBatch.push(message);
    console.log(`πŸ“¦ Added message to batch (${this.messageBatch.length}/${this.batchConfig.maxBatchSize})`);

    // πŸš€ Send immediately if batch is full
    if (this.messageBatch.length >= this.batchConfig.maxBatchSize) {
      this.flushBatch();
      return true;
    }

    // ⏰ Schedule batch send if not already scheduled
    if (!this.batchTimer) {
      this.batchTimer = window.setTimeout(() => {
        this.flushBatch();
      }, this.batchConfig.maxBatchDelay);
    }

    return true;
  }

  // πŸ”„ Send all batched messages
  private flushBatch(): void {
    if (this.messageBatch.length === 0) return;

    console.log(`πŸš€ Flushing batch of ${this.messageBatch.length} messages`);

    const batchMessage = {
      type: 'batch',
      messages: this.messageBatch,
      compressed: this.batchConfig.enableCompression,
      timestamp: Date.now(),
      id: crypto.randomUUID()
    };

    // πŸ—œοΈ Compress if enabled
    if (this.batchConfig.enableCompression) {
      batchMessage.messages = this.compressMessages(this.messageBatch);
    }

    // πŸ“€ Send the batch
    const success = super.send(batchMessage as any);

    if (success) {
      // 🧹 Clear batch
      this.messageBatch = [];
      
      // ⏰ Clear timer
      if (this.batchTimer) {
        clearTimeout(this.batchTimer);
        this.batchTimer = null;
      }
    } else {
      console.error('❌ Failed to send batch, keeping messages for retry');
    }
  }

  // πŸ—œοΈ Simple message compression
  private compressMessages(messages: WebSocketMessage[]): any {
    // Simple compression: group messages by type
    const compressed: { [type: string]: WebSocketMessage[] } = {};
    
    messages.forEach(message => {
      if (!compressed[message.type]) {
        compressed[message.type] = [];
      }
      compressed[message.type].push(message);
    });

    return compressed;
  }

  // 🚨 Force flush on disconnect
  disconnect(code?: number, reason?: string): void {
    this.flushBatch(); // Send any pending messages
    super.disconnect(code, reason);
  }
}

// πŸŽ›οΈ Message throttling for rate limiting
class ThrottledWebSocketClient extends TypeSafeWebSocketClient {
  private messageQueue: Array<{ message: WebSocketMessage; timestamp: number }> = [];
  private rateLimitWindow = 1000; // 1 second
  private maxMessagesPerWindow = 10;

  send<T extends WebSocketMessage>(message: T): boolean {
    const now = Date.now();
    
    // 🧹 Clean old messages from queue
    this.messageQueue = this.messageQueue.filter(
      item => now - item.timestamp < this.rateLimitWindow
    );

    // 🚫 Check rate limit
    if (this.messageQueue.length >= this.maxMessagesPerWindow) {
      console.warn('⚠️ Rate limit exceeded, dropping message');
      return false;
    }

    // πŸ“₯ Add to queue and send
    this.messageQueue.push({ message, timestamp: now });
    return super.send(message);
  }

  // πŸ“Š Get current rate limit status
  getRateLimitStatus(): { used: number; limit: number; resetTime: number } {
    const now = Date.now();
    const validMessages = this.messageQueue.filter(
      item => now - item.timestamp < this.rateLimitWindow
    );

    const oldestMessage = validMessages[0];
    const resetTime = oldestMessage 
      ? oldestMessage.timestamp + this.rateLimitWindow 
      : now;

    return {
      used: validMessages.length,
      limit: this.maxMessagesPerWindow,
      resetTime
    };
  }
}

πŸ§ͺ Testing WebSocket Applications

πŸ”¬ Unit Testing WebSocket Clients

// πŸ§ͺ Testing utilities for WebSocket applications
import { jest } from '@jest/globals';

// 🎭 Mock WebSocket for testing
class MockWebSocket {
  static CONNECTING = 0;
  static OPEN = 1;
  static CLOSING = 2;
  static CLOSED = 3;

  readyState = MockWebSocket.CONNECTING;
  onopen: ((event: Event) => void) | null = null;
  onclose: ((event: CloseEvent) => void) | null = null;
  onmessage: ((event: MessageEvent) => void) | null = null;
  onerror: ((event: Event) => void) | null = null;

  private listeners: { [key: string]: Function[] } = {};

  constructor(public url: string, public protocols?: string | string[]) {
    // πŸ• Simulate async connection
    setTimeout(() => {
      this.readyState = MockWebSocket.OPEN;
      if (this.onopen) {
        this.onopen(new Event('open'));
      }
    }, 10);
  }

  send(data: string | ArrayBuffer | Blob): void {
    if (this.readyState !== MockWebSocket.OPEN) {
      throw new Error('WebSocket is not open');
    }
    
    // Echo messages back for testing
    setTimeout(() => {
      if (this.onmessage) {
        this.onmessage({
          data: `echo: ${data}`,
          type: 'message'
        } as MessageEvent);
      }
    }, 1);
  }

  close(code?: number, reason?: string): void {
    this.readyState = MockWebSocket.CLOSING;
    
    setTimeout(() => {
      this.readyState = MockWebSocket.CLOSED;
      if (this.onclose) {
        this.onclose({
          code: code || 1000,
          reason: reason || '',
          wasClean: true
        } as CloseEvent);
      }
    }, 1);
  }

  // Test helpers
  simulateMessage(data: any): void {
    if (this.onmessage) {
      this.onmessage({
        data: JSON.stringify(data),
        type: 'message'
      } as MessageEvent);
    }
  }

  simulateError(): void {
    if (this.onerror) {
      this.onerror(new Event('error'));
    }
  }

  simulateClose(code = 1000, reason = ''): void {
    this.readyState = MockWebSocket.CLOSED;
    if (this.onclose) {
      this.onclose({
        code,
        reason,
        wasClean: code === 1000
      } as CloseEvent);
    }
  }
}

// πŸ§ͺ Test suite for WebSocket client
describe('WebSocket Client Tests', () => {
  let mockWebSocket: MockWebSocket;
  let client: TypeSafeWebSocketClient;

  beforeEach(() => {
    // 🎭 Replace global WebSocket with mock
    (global as any).WebSocket = MockWebSocket;
    
    client = new TypeSafeWebSocketClient('ws://localhost:8080');
  });

  afterEach(() => {
    client.disconnect();
  });

  test('πŸ”Œ should connect successfully', async () => {
    const connectPromise = client.connect();
    
    // βœ… Should resolve when connection opens
    await expect(connectPromise).resolves.toBeUndefined();
    expect(client.isConnected()).toBe(true);
  });

  test('πŸ“€ should send messages when connected', async () => {
    await client.connect();
    
    const testMessage = {
      type: 'test',
      content: 'Hello WebSocket!',
      timestamp: Date.now(),
      id: 'test-123'
    };

    const success = client.send(testMessage as any);
    expect(success).toBe(true);
  });

  test('🚫 should not send messages when disconnected', () => {
    const testMessage = {
      type: 'test',
      content: 'Hello WebSocket!',
      timestamp: Date.now(),
      id: 'test-123'
    };

    const success = client.send(testMessage as any);
    expect(success).toBe(false);
  });

  test('πŸ“¨ should handle incoming messages', async () => {
    await client.connect();
    
    const messageHandler = jest.fn();
    client.on('message', messageHandler);

    // πŸ“¨ Simulate incoming message
    const testMessage = {
      type: 'chat_message',
      content: 'Test message',
      userId: 'user-123',
      username: 'testuser',
      timestamp: Date.now(),
      id: 'msg-123'
    };

    // Access the underlying mock WebSocket
    const ws = (client as any).socket as MockWebSocket;
    ws.simulateMessage(testMessage);

    // ⏰ Wait for message processing
    await new Promise(resolve => setTimeout(resolve, 10));

    expect(messageHandler).toHaveBeenCalledWith(testMessage);
  });

  test('πŸ”„ should attempt reconnection on unexpected disconnect', async () => {
    const client = new TypeSafeWebSocketClient('ws://localhost:8080', {
      autoReconnect: true,
      maxReconnectAttempts: 2,
      reconnectDelay: 100
    });

    await client.connect();
    
    const reconnectingHandler = jest.fn();
    client.on('reconnecting', reconnectingHandler);

    // πŸ’₯ Simulate unexpected disconnect
    const ws = (client as any).socket as MockWebSocket;
    ws.simulateClose(1006, 'Connection lost');

    // ⏰ Wait for reconnection attempt
    await new Promise(resolve => setTimeout(resolve, 150));

    expect(reconnectingHandler).toHaveBeenCalledWith(1);
  });

  test('❌ should handle connection errors', async () => {
    const errorHandler = jest.fn();
    client.on('error', errorHandler);

    // Start connection
    const connectPromise = client.connect();

    // πŸ’₯ Simulate connection error
    setTimeout(() => {
      const ws = (client as any).socket as MockWebSocket;
      ws.simulateError();
    }, 5);

    // ❌ Should reject the connection promise
    await expect(connectPromise).rejects.toThrow();
    expect(errorHandler).toHaveBeenCalled();
  });

  test('πŸ’“ should handle heartbeat', async () => {
    const client = new TypeSafeWebSocketClient('ws://localhost:8080', {
      heartbeatInterval: 100
    });

    await client.connect();
    
    // ⏰ Wait for heartbeat
    await new Promise(resolve => setTimeout(resolve, 150));

    // πŸ” Verify heartbeat was sent (this would need access to sent messages)
    // In a real implementation, you'd check that a ping message was sent
    expect(client.isConnected()).toBe(true);
  });
});

// 🎭 Integration test with mock server
describe('WebSocket Integration Tests', () => {
  let mockServer: MockWebSocketServer;

  beforeEach(() => {
    mockServer = new MockWebSocketServer();
  });

  afterEach(() => {
    mockServer.stop();
  });

  test('πŸ’¬ should handle chat flow', async () => {
    const client1 = new ChatApplication('ws://localhost:8080');
    const client2 = new ChatApplication('ws://localhost:8080');

    // πŸ”Œ Connect both clients
    await client1.initialize('Alice');
    await client2.initialize('Bob');

    // 🏠 Join the same room
    client1.joinRoom('test-room');
    client2.joinRoom('test-room');

    // ⏰ Wait for room join
    await new Promise(resolve => setTimeout(resolve, 50));

    // πŸ’¬ Alice sends a message
    client1.sendMessage('Hello Bob!');

    // ⏰ Wait for message delivery
    await new Promise(resolve => setTimeout(resolve, 50));

    // βœ… Bob should receive the message
    const bobMessages = client2.messages;
    expect(bobMessages).toHaveLength(1);
    expect(bobMessages[0].content).toBe('Hello Bob!');
    expect(bobMessages[0].username).toBe('Alice');
  });
});

// 🎭 Mock WebSocket server for integration tests
class MockWebSocketServer {
  private clients: MockWebSocket[] = [];

  constructor() {
    // Override WebSocket constructor to track connections
    const originalWebSocket = (global as any).WebSocket;
    (global as any).WebSocket = class extends MockWebSocket {
      constructor(url: string, protocols?: string | string[]) {
        super(url, protocols);
        this.registerClient(this);
      }
    };
  }

  private registerClient(client: MockWebSocket): void {
    this.clients.push(client);
    
    // 🎧 Handle client messages
    client.onmessage = (event) => {
      this.handleClientMessage(client, JSON.parse(event.data));
    };
  }

  private handleClientMessage(sender: MockWebSocket, message: any): void {
    // πŸ“’ Broadcast to all other clients
    this.clients.forEach(client => {
      if (client !== sender && client.readyState === MockWebSocket.OPEN) {
        client.simulateMessage(message);
      }
    });
  }

  stop(): void {
    this.clients.forEach(client => {
      client.simulateClose();
    });
    this.clients = [];
  }
}

🎯 Common Pitfalls and Best Practices

❌ Common Mistakes to Avoid

// ❌ Common WebSocket pitfalls and their solutions

class WebSocketPitfalls {
  // ❌ WRONG: Not handling connection states properly
  static wrongConnectionHandling(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    // πŸ’₯ This will fail - socket might not be open yet!
    socket.send(JSON.stringify({ type: 'immediate' }));
  }

  // βœ… CORRECT: Wait for connection before sending
  static correctConnectionHandling(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    socket.onopen = () => {
      // βœ… Now it's safe to send
      socket.send(JSON.stringify({ type: 'safe' }));
    };
  }

  // ❌ WRONG: Not handling reconnections
  static wrongReconnectionHandling(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    socket.onclose = () => {
      console.log('Connection closed'); // πŸ’₯ That's it? No reconnection!
    };
  }

  // βœ… CORRECT: Implement proper reconnection logic
  static correctReconnectionHandling(): void {
    let reconnectAttempts = 0;
    const maxAttempts = 5;
    
    function connect(): void {
      const socket = new WebSocket('ws://localhost:8080');
      
      socket.onclose = (event) => {
        if (event.code !== 1000 && reconnectAttempts < maxAttempts) {
          reconnectAttempts++;
          setTimeout(() => connect(), 1000 * reconnectAttempts);
        }
      };
    }
    
    connect();
  }

  // ❌ WRONG: Not validating incoming messages
  static wrongMessageHandling(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    socket.onmessage = (event) => {
      // πŸ’₯ This could crash if data is invalid JSON!
      const message = JSON.parse(event.data);
      console.log(message.content.toUpperCase()); // πŸ’₯ What if content is undefined?
    };
  }

  // βœ… CORRECT: Always validate incoming data
  static correctMessageHandling(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    socket.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        
        // βœ… Validate message structure
        if (typeof message === 'object' && message.type) {
          switch (message.type) {
            case 'chat_message':
              if (typeof message.content === 'string') {
                console.log(message.content.toUpperCase());
              }
              break;
            // Handle other message types...
          }
        }
      } catch (error) {
        console.error('Invalid message received:', error);
      }
    };
  }

  // ❌ WRONG: Memory leaks with event listeners
  static wrongEventListenerCleanup(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    // πŸ’₯ These listeners are never cleaned up!
    socket.onmessage = (event) => { /* handle */ };
    socket.onerror = (error) => { /* handle */ };
    
    // Later in the app...
    // socket = null; // πŸ’₯ Memory leak! Listeners still exist
  }

  // βœ… CORRECT: Properly cleanup event listeners
  static correctEventListenerCleanup(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    const messageHandler = (event: MessageEvent) => { /* handle */ };
    const errorHandler = (event: Event) => { /* handle */ };
    
    socket.addEventListener('message', messageHandler);
    socket.addEventListener('error', errorHandler);
    
    // βœ… Cleanup function
    function cleanup(): void {
      socket.removeEventListener('message', messageHandler);
      socket.removeEventListener('error', errorHandler);
      socket.close();
    }
    
    // Call cleanup when component unmounts or app closes
    window.addEventListener('beforeunload', cleanup);
  }

  // ❌ WRONG: Not handling large message payloads
  static wrongLargeMessageHandling(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    // πŸ’₯ This could overwhelm the connection!
    const hugeData = new Array(1000000).fill('data');
    socket.send(JSON.stringify(hugeData));
  }

  // βœ… CORRECT: Implement message chunking for large data
  static correctLargeMessageHandling(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    function sendLargeData(data: any[], chunkSize = 1000): void {
      const chunks = [];
      for (let i = 0; i < data.length; i += chunkSize) {
        chunks.push(data.slice(i, i + chunkSize));
      }
      
      const messageId = crypto.randomUUID();
      
      chunks.forEach((chunk, index) => {
        socket.send(JSON.stringify({
          type: 'data_chunk',
          messageId,
          chunkIndex: index,
          totalChunks: chunks.length,
          data: chunk
        }));
      });
    }
  }

  // ❌ WRONG: Blocking the main thread with heavy processing
  static wrongHeavyProcessing(): void {
    const socket = new WebSocket('ws://localhost:8080');
    
    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      // πŸ’₯ This blocks the UI thread!
      for (let i = 0; i < 1000000; i++) {
        // Heavy computation
        Math.sqrt(data.numbers[i % data.numbers.length]);
      }
    };
  }

  // βœ… CORRECT: Use Web Workers for heavy processing
  static correctHeavyProcessing(): void {
    const socket = new WebSocket('ws://localhost:8080');
    const worker = new Worker('/heavy-processor.js');
    
    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      // βœ… Offload to worker thread
      worker.postMessage(data);
    };
    
    worker.onmessage = (event) => {
      // βœ… Process results without blocking UI
      console.log('Heavy processing complete:', event.data);
    };
  }
}

πŸ† Best Practices Summary

// πŸ† WebSocket best practices checklist
class WebSocketBestPractices {
  // βœ… 1. Always implement proper error handling
  static implementErrorHandling(): void {
    const client = new TypeSafeWebSocketClient('ws://localhost:8080');
    
    client.on('error', (error) => {
      console.error('WebSocket error:', error);
      // Implement user notification
      // Log to error tracking service
      // Attempt recovery if possible
    });
  }

  // βœ… 2. Use heartbeat/ping-pong to detect dead connections
  static implementHeartbeat(): void {
    const client = new TypeSafeWebSocketClient('ws://localhost:8080', {
      heartbeatInterval: 30000 // Ping every 30 seconds
    });
  }

  // βœ… 3. Implement exponential backoff for reconnections
  static implementExponentialBackoff(): void {
    const client = new TypeSafeWebSocketClient('ws://localhost:8080', {
      autoReconnect: true,
      maxReconnectAttempts: 10,
      reconnectDelay: 1000 // Will be multiplied by attempt number
    });
  }

  // βœ… 4. Use proper message validation and typing
  static implementMessageValidation(): void {
    interface ValidMessage {
      type: string;
      timestamp: number;
      id: string;
    }

    function isValidMessage(data: any): data is ValidMessage {
      return (
        typeof data === 'object' &&
        typeof data.type === 'string' &&
        typeof data.timestamp === 'number' &&
        typeof data.id === 'string'
      );
    }

    const client = new TypeSafeWebSocketClient('ws://localhost:8080');
    
    client.on('message', (message) => {
      if (isValidMessage(message)) {
        // βœ… Type-safe processing
        console.log(`Valid message: ${message.type}`);
      }
    });
  }

  // βœ… 5. Implement proper cleanup
  static implementCleanup(): void {
    class ComponentWithWebSocket {
      private client: TypeSafeWebSocketClient;

      constructor() {
        this.client = new TypeSafeWebSocketClient('ws://localhost:8080');
      }

      // βœ… Always cleanup on component unmount
      destroy(): void {
        this.client.disconnect();
        // Clear any timers
        // Remove event listeners
        // Cancel pending operations
      }
    }
  }

  // βœ… 6. Use connection pooling for multiple connections
  static useConnectionPooling(): void {
    const pool = new WebSocketConnectionPool();
    
    // Add connections for different services
    pool.addConnection('chat', { url: 'ws://chat.example.com' });
    pool.addConnection('notifications', { url: 'ws://notifications.example.com' });
    pool.addConnection('live-data', { url: 'ws://data.example.com' });
  }

  // βœ… 7. Implement proper security measures
  static implementSecurity(): void {
    // Use secure WebSocket (WSS) in production
    const client = new SecureWebSocketClient(
      'wss://secure.example.com',
      { token: 'your-auth-token' }
    );

    // Validate all incoming data
    // Implement rate limiting
    // Use encryption for sensitive data
  }

  // βœ… 8. Monitor connection quality
  static monitorConnectionQuality(): void {
    const client = new TypeSafeWebSocketClient('ws://localhost:8080');
    
    let messagesSent = 0;
    let messagesReceived = 0;
    let latencySum = 0;
    let latencyCount = 0;

    // Track sent messages
    const originalSend = client.send.bind(client);
    client.send = (message: any) => {
      messagesSent++;
      (message as any).sentAt = Date.now();
      return originalSend(message);
    };

    // Track received messages and latency
    client.on('message', (message) => {
      messagesReceived++;
      
      if ((message as any).sentAt) {
        const latency = Date.now() - (message as any).sentAt;
        latencySum += latency;
        latencyCount++;
      }
    });

    // Log metrics periodically
    setInterval(() => {
      const avgLatency = latencyCount > 0 ? latencySum / latencyCount : 0;
      console.log('WebSocket Metrics:', {
        messagesSent,
        messagesReceived,
        averageLatency: avgLatency,
        connectionState: client.getReadyState()
      });
    }, 60000); // Every minute
  }
}

πŸŽ‰ Hands-On Exercises

πŸ’» Exercise 1: Build a Simple Chat Room

Create a basic chat room application:

// πŸ’» Exercise 1: Simple Chat Room
// Your task: Implement the missing methods

class SimpleChatRoom {
  private client: TypeSafeWebSocketClient;
  private username: string;
  private messageContainer: HTMLElement;
  private messageInput: HTMLInputElement;

  constructor(username: string) {
    this.username = username;
    this.client = new TypeSafeWebSocketClient('ws://localhost:8080');
    
    // Get DOM elements
    this.messageContainer = document.getElementById('messages')!;
    this.messageInput = document.getElementById('messageInput') as HTMLInputElement;
    
    this.setupEventListeners();
  }

  async connect(): Promise<void> {
    // TODO: Connect to WebSocket and handle connection events
    // Hint: Use this.client.connect() and setup event handlers
  }

  private setupEventListeners(): void {
    // TODO: Setup WebSocket event listeners
    // Handle: connected, disconnected, message, error events
  }

  sendMessage(): void {
    // TODO: Send a chat message
    // Hint: Get text from this.messageInput, create message object, send via this.client
  }

  private displayMessage(message: ChatMessage): void {
    // TODO: Display message in the UI
    // Hint: Create div element, set innerHTML, append to this.messageContainer
  }

  private showStatus(status: string): void {
    // TODO: Show connection status to user
    console.log(status);
  }
}

// πŸ§ͺ Test your implementation:
// const chatRoom = new SimpleChatRoom('YourName');
// chatRoom.connect();

πŸ’» Exercise 2: Real-Time Collaborative Editor

Create a collaborative text editor with conflict resolution:

// πŸ’» Exercise 2: Collaborative Editor
// Your task: Implement operational transforms for conflict-free editing

interface EditOperation {
  type: 'insert' | 'delete';
  position: number;
  content?: string;
  length?: number;
  userId: string;
  timestamp: number;
}

class CollaborativeEditor {
  private client: TypeSafeWebSocketClient;
  private textArea: HTMLTextAreaElement;
  private operations: EditOperation[] = [];
  private userId: string;

  constructor(userId: string) {
    this.userId = userId;
    this.client = new TypeSafeWebSocketClient('ws://localhost:8080');
    this.textArea = document.getElementById('editor') as HTMLTextAreaElement;
    
    this.setupEventListeners();
  }

  private setupEventListeners(): void {
    // TODO: Setup text area change listeners
    // TODO: Setup WebSocket message handlers
    // Hint: Listen for 'input' events on textArea
    // Hint: Handle incoming edit operations from other users
  }

  private handleTextChange(event: Event): void {
    // TODO: Detect what changed and create edit operation
    // Hint: Compare current text with previous state
    // Hint: Send operation to other users via WebSocket
  }

  private applyOperation(operation: EditOperation): void {
    // TODO: Apply incoming operation to local text
    // Hint: Transform operation based on local operations
    // Hint: Update textArea.value
  }

  private transformOperation(
    operation: EditOperation,
    localOperations: EditOperation[]
  ): EditOperation {
    // TODO: Implement operational transform
    // This is the tricky part - handle concurrent edits!
    // Hint: Adjust position based on previous operations
    return operation;
  }
}

πŸ’» Exercise 3: Real-Time Dashboard

Build a live data dashboard with WebSocket updates:

// πŸ’» Exercise 3: Real-Time Dashboard
// Your task: Create a dashboard that updates with live data

interface MetricUpdate {
  type: 'metric_update';
  metricName: string;
  value: number;
  timestamp: number;
}

interface ChartDataPoint {
  timestamp: number;
  value: number;
}

class RealTimeDashboard {
  private client: TypeSafeWebSocketClient;
  private metrics = new Map<string, ChartDataPoint[]>();
  private charts = new Map<string, any>(); // Chart.js instances

  constructor() {
    this.client = new TypeSafeWebSocketClient('ws://localhost:8080');
    this.setupWebSocket();
  }

  private setupWebSocket(): void {
    // TODO: Setup WebSocket connection and message handling
    // Hint: Handle 'metric_update' messages
  }

  private handleMetricUpdate(update: MetricUpdate): void {
    // TODO: Update metric data and refresh chart
    // Hint: Add new data point to this.metrics
    // Hint: Keep only last 50 data points for performance
    // Hint: Update corresponding chart
  }

  private createChart(metricName: string, canvasId: string): void {
    // TODO: Create Chart.js chart for the metric
    // Hint: Use Chart.js library to create line chart
  }

  private updateChart(metricName: string): void {
    // TODO: Update chart with new data
    // Hint: Get chart from this.charts, update data, call chart.update()
  }

  // Bonus: Add data filtering by time range
  filterDataByTimeRange(hours: number): void {
    // TODO: Filter metrics to show only last N hours
  }
}

🎯 Final Project: Complete Real-Time Application

Combine everything you’ve learned to build a comprehensive real-time application:

// πŸ† Final Project: Multi-Feature Real-Time App
// Combine chat, notifications, live updates, and collaboration!

class RealTimeApp {
  private chatClient: ChatApplication;
  private dashboardClient: RealTimeDashboard;
  private notificationClient: TypeSafeWebSocketClient;
  private collaborativeEditor: CollaborativeEditor;

  constructor(private userId: string, private username: string) {
    this.initializeClients();
    this.setupGlobalEventHandlers();
  }

  private initializeClients(): void {
    // TODO: Initialize all WebSocket clients
    // TODO: Setup cross-client communication
    // TODO: Handle global connection state
  }

  private setupGlobalEventHandlers(): void {
    // TODO: Handle app-wide events
    // TODO: Coordinate between different features
    // TODO: Implement global error handling
  }

  // TODO: Add methods for:
  // - User presence tracking
  // - Cross-feature notifications
  // - Data synchronization
  // - Offline support with queued operations
  // - Performance monitoring
}

🎊 Conclusion

Congratulations! πŸŽ‰ You’ve mastered the art of real-time communication with WebSockets and TypeScript! You’ve learned how to:

  • πŸ”Œ Create robust WebSocket connections with automatic reconnection
  • πŸ’¬ Build complete chat applications with typing indicators
  • πŸ—οΈ Implement type-safe message passing systems
  • πŸ›‘οΈ Add authentication and security measures
  • ⚑ Optimize performance with batching and throttling
  • πŸ§ͺ Test WebSocket applications thoroughly
  • 🎯 Avoid common pitfalls and follow best practices

You’re now equipped to build any real-time feature your applications need - from simple notifications to complex collaborative tools. WebSockets are your gateway to creating engaging, interactive user experiences that work seamlessly across all network conditions.

Keep practicing, keep building, and remember: the web is real-time, and now you have the power to make it happen! πŸš€βœ¨

Next Steps: Try building a real-time game, a collaborative whiteboard, or a live streaming dashboard. The possibilities are endless with your new WebSocket superpowers! 🌟