Encapsulating a Comprehensive WebSocket Client in JavaScript

Below is an improved and concise implementation of a WebSocketClient class. It handles connection management, reconnection logic, heartbeat mechanisms, and message queuing, making it robust for real-world applications.

WebSocket Client Class Implementation

js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
      class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url;
    this.ws = null;
    this.options = {
      reconnectInterval: options.reconnectInterval || 5000,
      maxReconnectInterval: options.maxReconnectInterval || 60000,
      heartbeatInterval: options.heartbeatInterval || 30000,
      heartbeatTimeout: options.heartbeatTimeout || 10000,
      maxReconnectAttempts: options.maxReconnectAttempts || 5,
      maxHeartbeatTimeouts: options.maxHeartbeatTimeouts || 3,
      ...options,
    };
    this.reconnectAttempts = 0;
    this.heartbeatTimeouts = 0;
    this.messageQueue = [];
    this.heartbeatTimer = null;
    this.reconnectTimer = null;
    this.isReconnecting = false;

    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('WebSocket connected');
      this.isReconnecting = false;
      this.reconnectAttempts = 0;
      this.heartbeatTimeouts = 0;
      this.startHeartbeat();
      this.flushMessageQueue();
      this.options.onOpen?.();
    };

    this.ws.onmessage = event => {
      if (event.data === JSON.stringify({ type: 'heartbeat' })) {
        this.resetHeartbeatTimeout();
      }
      this.options.onMessage?.(event);
    };

    this.ws.onclose = event => {
      console.log(`WebSocket closed: ${event.code} ${event.reason}`);
      this.stopHeartbeat();
      this.options.onClose?.(event);

      if (!this.isReconnecting && event.code !== 1000) {
        this.reconnect();
      }
    };

    this.ws.onerror = error => {
      console.error('WebSocket error:', error);
      this.options.onError?.(error);
    };
  }

  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: 'heartbeat' }));
        this.setHeartbeatTimeout();
      }
    }, this.options.heartbeatInterval);
  }

  stopHeartbeat() {
    clearInterval(this.heartbeatTimer);
    clearTimeout(this.heartbeatTimeoutId);
  }

  setHeartbeatTimeout() {
    clearTimeout(this.heartbeatTimeoutId);
    this.heartbeatTimeoutId = setTimeout(() => {
      console.warn('Heartbeat timeout');
      this.heartbeatTimeouts++;
      if (this.heartbeatTimeouts >= this.options.maxHeartbeatTimeouts) {
        this.ws.close();
      }
    }, this.options.heartbeatTimeout);
  }

  resetHeartbeatTimeout() {
    this.heartbeatTimeouts = 0;
    this.setHeartbeatTimeout();
  }

  reconnect() {
    this.isReconnecting = true;
    if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
      const interval = Math.min(
        this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts),
        this.options.maxReconnectInterval
      );
      this.reconnectTimer = setTimeout(() => {
        console.log(`Reconnecting... Attempt ${this.reconnectAttempts + 1}`);
        this.reconnectAttempts++;
        this.connect();
      }, interval);
    } else {
      console.error('Max reconnect attempts reached');
    }
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    } else {
      this.messageQueue.push(data);
    }
  }

  flushMessageQueue() {
    while (this.messageQueue.length > 0) {
      this.send(this.messageQueue.shift());
    }
  }

  close() {
    this.ws.close(1000, 'Closed by client');
    this.stopHeartbeat();
    clearTimeout(this.reconnectTimer);
  }
}
    

Key Features

1. Connection Management:

  • Handles onopen, onclose, and onerror events gracefully.
  • Reconnects automatically using exponential backoff.

2. Heartbeat Mechanism:

  • Sends periodic heartbeat messages to keep the connection alive.
  • Detects and handles heartbeat timeouts.

3. Reconnection Logic:

  • Reconnects when the connection drops unexpectedly, with configurable limits.
  • Stops reconnection attempts after reaching a maximum number of attempts.

4. Message Queue:

  • Caches messages if the connection is closed and sends them once reconnected.

5. Customizable:

  • Options like onOpen, onMessage, and onError allow for flexible callbacks.

Usage Example

js
1234567891011121314
      const wsClient = new WebSocketClient('ws://example.com/socket', {
  reconnectInterval: 3000,
  maxReconnectAttempts: 3,
  onOpen: () => console.log('Connection established'),
  onMessage: event => console.log('Received:', event.data),
  onClose: event => console.log(`Disconnected: ${event.code}`),
  onError: error => console.error('Error:', error),
});

// Send a message
wsClient.send({ type: 'message', content: 'Hello, Server!' });

// Close the connection manually
// wsClient.close();
    

Summary

This WebSocketClient class provides a robust solution for handling WebSocket connections, ensuring stability through reconnection, heartbeat, and message queuing. It is well-suited for real-time applications requiring persistent communication.