# Architecture of the WebSocket Notification System for Real-Time Updates

> Discover the modular, event-driven architecture of the WebSocket notification system. Learn how WebSocketServer and NotificationManager deliver scalable real-time updates.

- Repository: [Daniel Avila/claude-code-templates](https://github.com/davila7/claude-code-templates)
- Tags: architecture
- Published: 2026-04-26

---

**The WebSocket notification system in davila7/claude-code-templates employs a modular, event-driven architecture centered on two core classes—`WebSocketServer` for low-level socket operations and `NotificationManager` for high-level notification orchestration—wired together by the Analytics core to deliver scalable real-time updates.**

This **WebSocket notification system for real-time updates** powers the analytics platform in the `davila7/claude-code-templates` repository. The design isolates connection lifecycle management from business logic, providing throttling, history tracking, and channel-based subscription filtering through a clean, extensible API.

## Core Components

The architecture consists of four primary layers that handle distinct responsibilities:

| Component | File Path | Responsibility | Key Methods |
|-----------|-----------|----------------|-------------|
| **WebSocketServer** | [`src/analytics/notifications/WebSocketServer.js`](https://github.com/davila7/claude-code-templates/blob/main/src/analytics/notifications/WebSocketServer.js) | Low-level WebSocket handling, connection lifecycle, heartbeat, broadcast, and per-client message queuing. | `initialize()`, `handleConnection()`, `broadcast()`, `sendToClient()`, `queueMessage()`, `startHeartbeat()`, `getStats()` |
| **NotificationManager** | [`src/analytics/notifications/NotificationManager.js`](https://github.com/davila7/claude-code-templates/blob/main/src/analytics/notifications/NotificationManager.js) | High-level notification orchestration, type-specific helpers, throttling, subscriber registry, and history management. | `initialize()`, `notifyConversationStateChange()`, `notifyDataRefresh()`, `notifyNewMessage()`, `subscribe()`, `isThrottled()`, `getStats()` |
| **Analytics Core** | [`src/analytics.js`](https://github.com/davila7/claude-code-templates/blob/main/src/analytics.js) | Server bootstrap that instantiates and connects the WebSocket and notification layers to other subsystems. | `initializeWebSocket()`, `setupNotificationSubscriptions()`, `setupConsoleBridgeIntegration()` |
| **Console Bridge** | [`src/console-bridge.js`](https://github.com/davila7/claude-code-templates/blob/main/src/console-bridge.js) | Optional secondary WebSocket endpoint for local web UI integration that forwards console interactions to the main server. | `setupWebSocketServer()`, `initialize()` |

## System Interaction Flow

The system follows a precise lifecycle from server startup through message delivery to graceful shutdown.

### Server Initialization

The Analytics core boots the stack in [`src/analytics.js`](https://github.com/davila7/claude-code-templates/blob/main/src/analytics.js) through the `initializeWebSocket()` method. It first instantiates `WebSocketServer` with the HTTP server, configuration options including `path: '/ws'` and `heartbeatInterval: 30000`, and a `PerformanceMonitor` instance for metrics collection. After awaiting `webSocketServer.initialize()`, which opens the WS listener, it creates a `NotificationManager` instance and registers event handlers for client messages like `refresh_requested`.

### Client Connection Lifecycle

When a browser or service connects, `WebSocketServer.handleConnection()` generates a unique client ID and stores connection metadata including IP address, user agent, and an empty subscription `Set`. The server immediately transmits a welcome payload with `type: 'connection'` and starts a **ping/pong heartbeat** via `startHeartbeat()` to detect and terminate dead connections.

### Message Routing and Channel Subscriptions

Incoming client messages trigger `WebSocketServer.handleClientMessage()`, which parses JSON payloads and dispatches to specific handlers. Valid message types include `subscribe`, `unsubscribe`, `ping`, and `refresh_request`. Subscription requests add channel names to the client's `subscriptions` Set, enabling fine-grained filtering where `broadcast()` only delivers messages to clients that have explicitly joined that channel.

### Broadcasting and Message Queuing

The `broadcast()` method in `WebSocketServer` serializes payloads, adds timestamps, and iterates over active connections. If a `channel` argument is provided, the system checks `client.subscriptions.has(channel)` before transmission. When no clients are currently connected, messages are not dropped but instead persisted via `queueMessage()` for delivery to the first client that connects, ensuring no data loss during intermittent connectivity.

### High-Level Notification Pipeline

`NotificationManager` provides domain-specific convenience methods such as `notifyConversationStateChange()`, `notifyDataRefresh()`, and `notifyNewMessage()`. Each method constructs a notification object containing type, payload, timestamp, and unique ID, then:

1. Validates throttling state via `isThrottled(key, interval)` to prevent spam.
2. Persists to in-memory history using `addToHistory()`.
3. Invokes `WebSocketServer.broadcast()` or targeted send methods.
4. Notifies local subscribers registered through the `subscribe()` API.

External modules like file watchers register callbacks using `notificationManager.subscribe(type, callback)`, which stores functions in a `Map<string, Set<Function>>`. When notifications fire, all callbacks execute safely with errors logged without breaking the pipeline.

### Graceful Shutdown

Both classes expose shutdown methods to ensure clean resource disposal. `WebSocketServer.close()` stops the heartbeat timer, terminates all client sockets, and clears internal maps. `NotificationManager.shutdown()` clears the subscriber registry and stops any periodic throttle-map cleanup intervals.

## Implementation Examples

### Bootstrapping the WebSocket Layer

In [`src/analytics.js`](https://github.com/davila7/claude-code-templates/blob/main/src/analytics.js), the Analytics class initializes the infrastructure:

```javascript
// Inside Analytics.initializeWebSocket()
this.webSocketServer = new WebSocketServer(this.httpServer, {
  path: '/ws',
  heartbeatInterval: 30000
}, this.performanceMonitor);
await this.webSocketServer.initialize();

this.notificationManager = new NotificationManager(this.webSocketServer);
await this.notificationManager.initialize();

```

### Sending Domain-Specific Notifications

Any module can trigger updates through high-level methods:

```javascript
// After loading fresh analytics data
this.notificationManager.notifyDataRefresh(this.data, 'websocket_request');

// When conversation state changes
this.notificationManager.notifyConversationStateChange(conversationId, newState);

```

### Subscribing to Custom Events

Internal modules register for notifications without WebSocket complexity:

```javascript
// In the file-watcher module
const unsub = this.notificationManager.subscribe('file_change', ({filePath, changeType}) => {
  console.log(`File ${changeType}: ${filePath}`);
});

// Cleanup when no longer needed
unsub();

```

### Client-Side Connection

Browser clients connect to `ws://localhost:3333/ws` and subscribe to channels:

```javascript
const ws = new WebSocket('ws://localhost:3333/ws');

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'subscribe', 
    channel: 'conversation_updates'
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === 'new_message') {
    // Update UI with msg.data.message
  }
};

```

## Extensibility Points

The architecture supports several extension vectors without modifying core classes:

- **Custom Notification Types**: Add methods to `NotificationManager` that build payloads and call `webSocketServer.broadcast()` with new channel identifiers.
- **Persistence Layer**: Replace `addToHistory()` and `getHistory()` implementations in `NotificationManager` with database-backed storage (e.g., SQLite, Supabase) while maintaining the same public API.
- **Authentication**: Wrap `WebSocketServer.handleConnection()` with JWT verification middleware, leveraging existing [`src/lib/api/auth.ts`](https://github.com/davila7/claude-code-templates/blob/main/src/lib/api/auth.ts) logic used for HTTP routes.
- **Channel Architecture**: Clients can subscribe to arbitrary string identifiers; the server already filters broadcasts using `client.subscriptions.has(channel)`.

## Summary

- The **WebSocket notification system for real-time updates** relies on two primary classes: `WebSocketServer` for transport and `NotificationManager` for business logic.
- **Channel-based filtering** ensures clients only receive messages for subscribed topics via `Set` storage per connection.
- **Message queuing** prevents data loss when no clients are connected by holding broadcasts in memory until the next connection.
- **Throttling and history** management in `NotificationManager` protect against notification spam while maintaining recent context.
- **Graceful shutdown** procedures ensure clean termination of heartbeats, sockets, and subscriber registries.

## Frequently Asked Questions

### How does the WebSocket server detect and handle disconnected clients?

The `WebSocketServer` class implements a heartbeat mechanism via `startHeartbeat()` that sends periodic ping frames. If a client fails to respond with a pong within the configured interval, the server terminates the connection and removes the client from the active connections Map, preventing resource leaks.

### Can notification history persist across server restarts?

Currently, notification history is stored in-memory within `NotificationManager` via `addToHistory()`. To enable persistence, you can extend the class to replace the in-memory store with a database implementation (such as SQLite or Supabase) without changing the public `getHistory()` or `addToHistory()` method signatures.

### How are notification channels secured?

The base implementation does not enforce authentication on WebSocket connections. To secure channels, wrap the `handleConnection()` method in [`src/analytics/notifications/WebSocketServer.js`](https://github.com/davila7/claude-code-templates/blob/main/src/analytics/notifications/WebSocketServer.js) with JWT verification using the existing authentication logic from [`src/lib/api/auth.ts`](https://github.com/davila7/claude-code-templates/blob/main/src/lib/api/auth.ts), rejecting unauthorized connections before they subscribe to sensitive channels.

### What happens if a notification is sent while no clients are connected?

The `broadcast()` method automatically detects when the client list is empty and routes the message to `queueMessage()`. This system maintains an internal queue of pending messages and delivers them to the first client that establishes a connection, ensuring no real-time updates are lost during periods of zero connectivity.