How to Reconnect WebSockets Reliably

December, 30th 2024 2 min read

WebSockets are a powerful tool for real-time communication between a client and a server. However, handling connection interruptions and ensuring reliability can be challenging. This guide demonstrates how to implement a robust WebSocket encapsulation with automatic reconnection, event listeners, and customizable parameters.

Why Encapsulate WebSocket Logic?

By encapsulating WebSocket logic, you:

  • Simplify implementation across your app.
  • Handle reconnection automatically without duplicating code.
  • Improve maintainability and scalability.

Key Features of the WebSocket Encapsulation

  1. Automatic Reconnection
  2. Event Listeners
  3. Customizable Parameters
  4. Error Handling

Implementation: WebSocketReconnect Class

js
class WebSocketReconnect {
  constructor(
    url,
    maxReconnectAttempts = 3,
    reconnectInterval = 20000,
    maxReconnectTime = 180000
  ) {
    this.url = url;
    this.maxReconnectAttempts = maxReconnectAttempts;
    this.reconnectInterval = reconnectInterval;
    this.maxReconnectTime = maxReconnectTime;
    this.reconnectCount = 0;
    this.reconnectTimeout = null;
    this.startTime = null;
    this.socket = null;
    this.listenerEvents = {};

    if (this.checkWssUrl()) {
      this.connect();
    }
  }

  checkWssUrl(url = this.url) {
    if (/wss:\/\/.*/.test(url) || /ws:\/\/.*/.test(url)) {
      return true;
    } else {
      console.error('Invalid WebSocket URL');
      return false;
    }
  }

  connect() {
    console.log('Connecting...');
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      console.log('WebSocket Connection Opened');
      this.triggerEvent('open');
      this.clearReconnectTimeout();
      this.reconnectCount = 0;
    };

    this.socket.onclose = event => {
      console.log('WebSocket Connection Closed', event);
      this.triggerEvent('close');
      this.handleReconnect();
    };

    this.socket.onerror = error => {
      console.error('WebSocket Error', error);
      this.triggerEvent('error');
      this.handleReconnect();
    };
  }

  triggerEvent(type) {
    const events = this.listenerEvents[type] || [];
    events.forEach(callback => callback());
  }

  addEventListener(type, callback) {
    if (!this.listenerEvents[type]) {
      this.listenerEvents[type] = [];
    }
    this.listenerEvents[type].push(callback);
  }

  handleReconnect() {
    if (
      this.reconnectCount < this.maxReconnectAttempts &&
      (!this.startTime || Date.now() - this.startTime < this.maxReconnectTime)
    ) {
      this.reconnectCount++;
      console.log(
        `Reconnecting (${this.reconnectCount}/${this.maxReconnectAttempts})...`
      );

      this.reconnectTimeout = setTimeout(() => {
        this.connect();
      }, this.reconnectInterval);

      if (!this.startTime) {
        this.startTime = Date.now();
      }
    } else {
      console.log('Max reconnection attempts reached or timeout exceeded.');
    }
  }

  clearReconnectTimeout() {
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout);
      this.reconnectTimeout = null;
    }
  }

  close() {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      this.socket.close();
    }
    this.clearReconnectTimeout();
    this.reconnectCount = 0;
    this.startTime = null;
  }
}

Example Usage

js
import WebSocketReconnect from './WebSocketReconnect';

const ws = new WebSocketReconnect('ws://your-websocket-url');

ws.addEventListener('open', () => console.log('WebSocket opened'));
ws.addEventListener('close', () => console.log('WebSocket closed'));
ws.addEventListener('error', () => console.log('Error occurred'));

ws.close();