javascript 124 lines · 3 tabs

Reconnecting WebSocket Chat Client with a Broadcasting Node Server

Shared by codesnips Aug 2026
3 tabs
const { WebSocketServer } = require('ws');
const crypto = require('crypto');

const wss = new WebSocketServer({ port: 8080 });

function broadcast(payload, except) {
  const data = JSON.stringify(payload);
  for (const client of wss.clients) {
    if (client === except) continue;
    if (client.readyState === client.OPEN) client.send(data);
  }
}

wss.on('connection', (ws) => {
  ws.id = crypto.randomBytes(4).toString('hex');
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });

  broadcast({ type: 'system', text: `${ws.id} joined` }, ws);

  ws.on('message', (raw) => {
    let msg;
    try { msg = JSON.parse(raw); } catch { return; }
    if (typeof msg.text !== 'string') return;
    broadcast({ type: 'chat', from: ws.id, text: msg.text.slice(0, 2000) });
  });

  ws.on('close', () => {
    broadcast({ type: 'system', text: `${ws.id} left` });
  });
});

const heartbeat = setInterval(() => {
  for (const ws of wss.clients) {
    if (!ws.isAlive) { ws.terminate(); continue; }
    ws.isAlive = false;
    ws.ping();
  }
}, 30000);

wss.on('close', () => clearInterval(heartbeat));
3 files · javascript Explain with highlit

This snippet shows a full-duplex chat built on raw WebSockets: a broadcasting server that fans messages out to every connected peer, and a browser client that survives dropped connections without losing its place. The three tabs form one story — the server that accepts and relays, the resilient client that reconnects, and a small consumer that wires the client into UI callbacks.

In ChatServer.js, the server runs on the ws library and keeps every socket in a Set. The broadcast helper serializes once and iterates over wss.clients, skipping any socket whose readyState is not OPEN — a common pitfall, since a socket can linger in CLOSING while still enumerable. Heartbeats are the core reliability mechanism: each connection is marked isAlive, a pong handler flips it back to true, and a shared interval terminates any socket that missed the previous ping. Without this, half-open TCP connections (a client that vanished without a close frame) would leak forever. The server also assigns a short id per client so joins and leaves can be announced.

In ReconnectingSocket.js, the client wraps a native WebSocket behind a stable interface. The key idea is that consumers hold a reference to the wrapper, not the underlying socket, so a reconnect is invisible to them. On close it schedules connect again using exponential backoff with jitter — Math.min(maxDelay, base * 2 ** attempt) plus randomness — which spreads reconnect storms so a restarted server is not hammered by every client at once. Outgoing messages are buffered in queue while offline and flushed on open, giving at-least-once delivery of user input across a blip. A manual close sets stopped so the backoff loop does not fight an intentional shutdown.

In chat-ui.js, the wrapper is consumed like an event emitter: onMessage renders incoming frames and onStatus drives a connection indicator. Because the queue and backoff live in the wrapper, the UI code stays declarative and never touches reconnection logic. The trade-off is that buffered messages may arrive out of their original order relative to server broadcasts, so anything requiring strict ordering would need sequence numbers layered on top.


Related snips

typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs
ruby
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    # Subscribe to a specific room
    room = Room.find(params[:room_id])

ActionCable for real-time WebSocket communication

ruby rails actioncable
by Sarah Mitchell 3 tabs
sql
CREATE TABLE outbox (
    id             BIGSERIAL PRIMARY KEY,
    aggregate_type TEXT        NOT NULL,
    aggregate_id   TEXT        NOT NULL,
    event_type     TEXT        NOT NULL,
    payload        JSONB       NOT NULL,

Transactional outbox in Node (DB write + event)

node postgres reliability
by codesnips 3 tabs
typescript
import { Pool, PoolClient, Client, QueryResult, QueryResultRow } from 'pg';

const MAX_LIFETIME_MS = 30 * 60 * 1000;

export const pool = new Pool({
  connectionString: process.env.DATABASE_URL,

Postgres connection pooling with pg + max lifetime

node postgres connection-pooling
by codesnips 3 tabs
java
package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

WebSocket for real-time communication

java websocket spring-boot
by David Kumar 3 tabs

Share this code

Here's the card — post it anywhere.

Reconnecting WebSocket Chat Client with a Broadcasting Node Server — share card
Link copied