How ChocolateLMLite Uses WebSocket Broadcasting for Real-Time Updates

ChocolateLMLite implements WebSocket broadcasting through a static Broadcaster façade that decouples application logic from socket management, enabling real-time UI updates via JSON message propagation to all connected clients.

ChocolateLMLite is an open-source C# application that delivers instant UI synchronization between server-side events and browser clients. According to the gpsnmeajp/chocolatelmlite source code, the project implements WebSocket broadcasting for real-time updates through a clean separation of concerns, isolating socket handling in a dedicated WebServer class while exposing a simple static API via the Broadcaster component.

Core Architecture Components

The broadcasting system relies on three coordinated components that isolate networking complexity from business logic.

The WebServer Class

Located in src/WebServer.cs, this class manages the actual WebSocket connections. It maintains a thread-safe collection of active clients and handles JSON serialization using System.Text.Json with camelCase naming policies.

The Broadcaster Façade

The src/Broadcaster.cs file defines a static class that acts as a dispatch proxy. It stores a delegate reference to the WebServer's broadcast method, allowing any part of the application to push messages without referencing socket APIs directly.

Frontend Integration

Browser clients connect via standard WebSocket APIs defined in files like static/js/gametalk.js. These scripts parse incoming JSON payloads and update the DOM immediately upon receiving server events.

Implementing the Broadcast Façade (src/Broadcaster.cs)

The Broadcaster class eliminates tight coupling between application logic and network infrastructure. It accepts a Func<Dictionary<string, object>, Task> delegate during initialization, which it invokes whenever the application needs to broadcast.

// src/Broadcaster.cs
public static class Broadcaster
{
    static Func<Dictionary<string, object>, Task>? broadcastAction;

    // Called once at startup – registers the concrete Web‑Socket push method.
    public static void Initialize(Func<Dictionary<string, object>, Task> action)
        => broadcastAction = action;

    // Any component calls this to push a message to all clients.
    public static async Task Broadcast(Dictionary<string, object> message)
    {
        if (broadcastAction != null)
            await broadcastAction.Invoke(message);
    }
}

By accepting a delegate rather than a concrete server instance, the design supports testing scenarios where the broadcast action can be replaced with a mock implementation.

WebSocket Server Broadcasting Logic (src/WebServer.cs)

The WebServer class handles connection management and message distribution. It stores active sockets in a List<WebSocket> protected by a lock statement to ensure thread safety during concurrent access.

When broadcasting, the server creates a snapshot of the client list to avoid holding locks during network I/O. It serializes the Dictionary<string, object> payload to JSON using JsonSerializerOptions configured with PropertyNamingPolicy.CamelCase.

// src/WebServer.cs (excerpt)
public class WebServer
{
    private readonly List<WebSocket> _clients = new();   // thread‑safe via lock
    private readonly JsonSerializerOptions _jsonOpts = new() 
    { 
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase 
    };

    // Called by Broadcaster – sends JSON to every connected client.
    public async Task Broadcast(Dictionary<string, object> payload)
    {
        var json = JsonSerializer.Serialize(payload, _jsonOpts);
        var buffer = Encoding.UTF8.GetBytes(json);
        var segment = new ArraySegment<byte>(buffer);

        List<WebSocket> snapshot;
        lock (_clients) snapshot = _clients.ToList();   // copy to avoid locking during I/O

        foreach (var ws in snapshot)
        {
            if (ws.State == WebSocketState.Open)
                await ws.SendAsync(
                    segment, 
                    WebSocketMessageType.Text, 
                    true, 
                    CancellationToken.None);
        }
    }
}

This implementation ensures that slow clients or network latency do not block the broadcast loop for other recipients.

Application Startup and Registration (src/Program.cs)

The application entry point wires the Broadcaster to the WebServer instance. During startup, the server instantiates WebServer, then registers its Broadcast method with the static façade.

// src/Program.cs (excerpt)
var server = new WebServer(/* config */);
Broadcaster.Initialize(server.Broadcast);   // ← registers the broadcast delegate
await server.StartAsync();                 // starts the HTTP/Web‑Socket listener

Once initialized, any component throughout the codebase can call Broadcaster.Broadcast() to trigger real-time updates without knowing which clients are connected or how serialization occurs.

Handling Real-Time Updates in the Browser (static/js/gametalk.js)

Client-side JavaScript establishes a WebSocket connection to the backend endpoint (typically /ws). The event listener parses incoming JSON and routes messages based on type fields, updating chat bubbles or status indicators without page reloads.

// static/js/gametalk.js (excerpt)
const socket = new WebSocket(
    `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`
);

socket.addEventListener('message', event => {
    const msg = JSON.parse(event.data);
    switch (msg.type) {
        case 'chat':
            renderChatBubble(msg.author, msg.content);
            break;
        case 'status':
            updateStatusBar(msg.status);
            break;
    }
});

This architecture enables instantaneous UI reflection of server-side events such as LLM responses, persona switches, or system status changes.

Summary

  • ChocolateLMLite uses a delegate-based façade pattern to separate WebSocket broadcasting from application logic.
  • The Broadcaster class in src/Broadcaster.cs provides a static API that accepts Dictionary<string, object> messages and forwards them to a registered delegate.
  • WebServer in src/WebServer.cs manages thread-safe client lists using lock statements and broadcasts JSON using System.Text.Json with camelCase serialization.
  • Startup initialization in src/Program.cs connects the Broadcaster to the WebServer via Broadcaster.Initialize(server.Broadcast).
  • Browser clients in static/js/gametalk.js consume WebSocket messages to update the DOM in real time without HTTP polling.

Frequently Asked Questions

How does ChocolateLMLite ensure thread safety during WebSocket broadcasting?

The WebServer class maintains a List<WebSocket> field that is accessed exclusively within lock (_clients) blocks. When broadcasting, it creates a snapshot copy of the list to iterate over, preventing modification exceptions while allowing the main collection to accept new connections or removals during transmission.

What data format does ChocolateLMLite use for real-time messages?

The system uses Dictionary<string, object> payloads internally, which WebServer.Broadcast serializes to JSON using System.Text.Json.JsonSerializer. The serializer options specify PropertyNamingPolicy.CamelCase, ensuring consistent lowercase property names in the transmitted JSON.

Can the Broadcaster be tested without an active WebSocket server?

Yes. Because Broadcaster.Initialize accepts a Func<Dictionary<string, object>, Task> delegate rather than a concrete type, unit tests can inject mock implementations that capture messages or write to logs instead of transmitting over network sockets. This design decouples message generation from transport concerns.

Which frontend files handle WebSocket connections in ChocolateLMLite?

The primary client-side WebSocket logic resides in static/js/gametalk.js, which creates the WebSocket instance, attaches message event listeners, and parses JSON payloads to update chat interfaces and status displays dynamically.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →