javascript

examples

examples.js
/**
 * 16.6 WebSockets and Real-Time Communication - Examples
 */

// ============================================
// BASIC WEBSOCKET CONNECTION
// ============================================

console.log('=== Basic WebSocket ===');

// Note: These examples show patterns - you'll need a real WebSocket server

function basicWebSocket() {
  // Create connection
  const socket = new WebSocket('wss://echo.websocket.org');

  socket.addEventListener('open', (event) => {
    console.log('Connected to server');
    socket.send('Hello Server!');
  });

  socket.addEventListener('message', (event) => {
    console.log('Received:', event.data);
  });

  socket.addEventListener('error', (error) => {
    console.error('WebSocket error:', error);
  });

  socket.addEventListener('close', (event) => {
    console.log(`Closed: ${event.code} - ${event.reason}`);
  });

  return socket;
}

// ============================================
// WEBSOCKET STATES
// ============================================

console.log('\n=== WebSocket States ===');

function checkState(socket) {
  const states = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
  console.log('State:', states[socket.readyState]);
  return socket.readyState === WebSocket.OPEN;
}

// ============================================
// WEBSOCKET CLIENT CLASS
// ============================================

console.log('\n=== WebSocket Client Class ===');

class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      reconnect: true,
      reconnectInterval: 1000,
      maxReconnectAttempts: 5,
      ...options,
    };

    this.socket = null;
    this.reconnectAttempts = 0;
    this.listeners = new Map();
    this.messageQueue = [];
  }

  connect() {
    return new Promise((resolve, reject) => {
      console.log('Connecting to:', this.url);
      this.socket = new WebSocket(this.url);

      this.socket.onopen = () => {
        console.log('Connected!');
        this.reconnectAttempts = 0;
        this.flushQueue();
        this.emit('connect');
        resolve(this);
      };

      this.socket.onclose = (event) => {
        console.log('Disconnected:', event.code);
        this.emit('disconnect', event);

        if (this.options.reconnect && !event.wasClean) {
          this.attemptReconnect();
        }
      };

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

      this.socket.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data);
          this.emit('message', data);
          if (data.type) {
            this.emit(data.type, data.payload);
          }
        } catch (e) {
          this.emit('message', event.data);
        }
      };
    });
  }

  send(data) {
    const message = typeof data === 'string' ? data : JSON.stringify(data);

    if (this.isConnected()) {
      this.socket.send(message);
    } else {
      console.log('Queuing message');
      this.messageQueue.push(message);
    }
  }

  flushQueue() {
    console.log(`Flushing ${this.messageQueue.length} queued messages`);
    while (this.messageQueue.length > 0) {
      this.socket.send(this.messageQueue.shift());
    }
  }

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

  attemptReconnect() {
    if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
      console.log('Max reconnection attempts reached');
      this.emit('reconnectFailed');
      return;
    }

    this.reconnectAttempts++;
    const delay = this.options.reconnectInterval * this.reconnectAttempts;
    console.log(
      `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
    );

    setTimeout(() => {
      this.connect().catch(() => {});
    }, delay);
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
    return this;
  }

  off(event, callback) {
    if (this.listeners.has(event)) {
      const callbacks = this.listeners.get(event);
      const index = callbacks.indexOf(callback);
      if (index !== -1) callbacks.splice(index, 1);
    }
    return this;
  }

  emit(event, data) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach((cb) => cb(data));
    }
  }

  close(code = 1000, reason = '') {
    this.options.reconnect = false;
    if (this.socket) {
      this.socket.close(code, reason);
    }
  }
}

// Demo usage (without actual connection)
console.log('Created WebSocketClient class');

// ============================================
// HEARTBEAT PATTERN
// ============================================

console.log('\n=== Heartbeat Pattern ===');

class WebSocketWithHeartbeat extends WebSocketClient {
  constructor(url, options = {}) {
    super(url, { heartbeatInterval: 30000, ...options });
    this.heartbeatTimer = null;
    this.lastPong = Date.now();
  }

  connect() {
    return super.connect().then((client) => {
      this.startHeartbeat();
      return client;
    });
  }

  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.isConnected()) {
        // Check if we received pong recently
        if (Date.now() - this.lastPong > this.options.heartbeatInterval * 2) {
          console.log('Connection appears dead, reconnecting...');
          this.socket.close();
          return;
        }

        this.send({ type: 'ping', timestamp: Date.now() });
      }
    }, this.options.heartbeatInterval);

    this.on('pong', (data) => {
      this.lastPong = Date.now();
      const latency = Date.now() - data.timestamp;
      console.log(`Heartbeat: ${latency}ms latency`);
    });
  }

  close(code, reason) {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
    }
    super.close(code, reason);
  }
}

console.log('Created WebSocketWithHeartbeat class');

// ============================================
// REQUEST-RESPONSE PATTERN
// ============================================

console.log('\n=== Request-Response Pattern ===');

class WebSocketRPC extends WebSocketClient {
  constructor(url, options = {}) {
    super(url, options);
    this.pendingRequests = new Map();
    this.requestId = 0;

    this.on('response', (data) => {
      const { id, result, error } = data;
      const pending = this.pendingRequests.get(id);
      if (pending) {
        this.pendingRequests.delete(id);
        if (error) {
          pending.reject(new Error(error));
        } else {
          pending.resolve(result);
        }
      }
    });
  }

  async request(method, params, timeout = 5000) {
    return new Promise((resolve, reject) => {
      const id = ++this.requestId;

      const timer = setTimeout(() => {
        this.pendingRequests.delete(id);
        reject(new Error('Request timeout'));
      }, timeout);

      this.pendingRequests.set(id, {
        resolve: (result) => {
          clearTimeout(timer);
          resolve(result);
        },
        reject: (error) => {
          clearTimeout(timer);
          reject(error);
        },
      });

      this.send({ type: 'request', id, method, params });
    });
  }
}

console.log('Created WebSocketRPC class');

// ============================================
// CHAT CLIENT
// ============================================

console.log('\n=== Chat Client ===');

class ChatClient extends WebSocketClient {
  constructor(url) {
    super(url);
    this.currentRoom = null;
    this.username = null;
  }

  setUsername(username) {
    this.username = username;
    this.send({ type: 'setUsername', username });
  }

  join(room) {
    this.currentRoom = room;
    this.send({ type: 'join', room });
    console.log(`Joined room: ${room}`);
  }

  leave() {
    if (this.currentRoom) {
      this.send({ type: 'leave', room: this.currentRoom });
      console.log(`Left room: ${this.currentRoom}`);
      this.currentRoom = null;
    }
  }

  sendMessage(text) {
    if (this.currentRoom) {
      this.send({
        type: 'chat',
        room: this.currentRoom,
        text,
        timestamp: Date.now(),
      });
    } else {
      console.warn('Not in a room');
    }
  }

  sendTyping(isTyping) {
    if (this.currentRoom) {
      this.send({
        type: 'typing',
        room: this.currentRoom,
        isTyping,
      });
    }
  }
}

console.log('Created ChatClient class');

// ============================================
// LIVE DATA FEED
// ============================================

console.log('\n=== Live Data Feed ===');

class LiveFeed extends WebSocketClient {
  constructor(url) {
    super(url);
    this.subscriptions = new Set();
  }

  subscribe(channel) {
    this.subscriptions.add(channel);
    if (this.isConnected()) {
      this.send({ type: 'subscribe', channel });
    }
    console.log(`Subscribed to: ${channel}`);
  }

  unsubscribe(channel) {
    this.subscriptions.delete(channel);
    if (this.isConnected()) {
      this.send({ type: 'unsubscribe', channel });
    }
    console.log(`Unsubscribed from: ${channel}`);
  }

  connect() {
    return super.connect().then((client) => {
      // Resubscribe on reconnect
      this.subscriptions.forEach((channel) => {
        this.send({ type: 'subscribe', channel });
      });
      return client;
    });
  }
}

console.log('Created LiveFeed class');

// ============================================
// MESSAGE BUFFER
// ============================================

console.log('\n=== Message Buffer ===');

class BufferedWebSocket extends WebSocketClient {
  constructor(url, options = {}) {
    super(url, {
      bufferSize: 100,
      flushInterval: 100,
      ...options,
    });
    this.buffer = [];
    this.flushTimer = null;
  }

  connect() {
    return super.connect().then((client) => {
      this.startBufferFlush();
      return client;
    });
  }

  startBufferFlush() {
    this.flushTimer = setInterval(() => {
      if (this.buffer.length > 0 && this.isConnected()) {
        const batch = this.buffer.splice(0, this.options.bufferSize);
        this.socket.send(JSON.stringify({ type: 'batch', messages: batch }));
      }
    }, this.options.flushInterval);
  }

  sendBuffered(data) {
    this.buffer.push(data);

    // Immediate flush if buffer is full
    if (this.buffer.length >= this.options.bufferSize) {
      this.flushBuffer();
    }
  }

  flushBuffer() {
    if (this.buffer.length > 0 && this.isConnected()) {
      const batch = this.buffer.splice(0);
      this.socket.send(JSON.stringify({ type: 'batch', messages: batch }));
    }
  }

  close(code, reason) {
    if (this.flushTimer) {
      clearInterval(this.flushTimer);
    }
    this.flushBuffer(); // Flush remaining on close
    super.close(code, reason);
  }
}

console.log('Created BufferedWebSocket class');

// ============================================
// SIMULATION WITHOUT SERVER
// ============================================

console.log('\n=== Simulated WebSocket Demo ===');

// Mock WebSocket for demonstration
class MockWebSocket {
  constructor() {
    this.listeners = {};
    this.readyState = 0;

    setTimeout(() => {
      this.readyState = 1;
      this.trigger('open', {});
    }, 100);
  }

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

  trigger(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach((cb) => cb(data));
    }
  }

  send(data) {
    console.log('Sent:', data);
    // Echo back for demo
    setTimeout(() => {
      this.trigger('message', { data });
    }, 50);
  }

  close() {
    this.readyState = 3;
    this.trigger('close', { code: 1000, wasClean: true });
  }
}

// Demo
const mockSocket = new MockWebSocket();
mockSocket.addEventListener('open', () => console.log('Mock connected'));
mockSocket.addEventListener('message', (e) =>
  console.log('Mock received:', e.data)
);

setTimeout(() => {
  mockSocket.send(JSON.stringify({ type: 'test', message: 'Hello' }));
}, 200);

setTimeout(() => {
  mockSocket.close();
}, 400);

// ============================================
// EXPONENTIAL BACKOFF
// ============================================

console.log('\n=== Exponential Backoff ===');

function calculateBackoff(attempt, baseDelay = 1000, maxDelay = 30000) {
  const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
  // Add jitter
  const jitter = delay * 0.2 * Math.random();
  return Math.floor(delay + jitter);
}

for (let i = 0; i < 6; i++) {
  console.log(`Attempt ${i}: ${calculateBackoff(i)}ms`);
}

console.log('\n=== Examples Complete ===');
Examples - JavaScript Tutorial | DeepML