How CasaOS Implements WebSocket Connections for Real-Time Hardware Status

CasaOS uses the Gorilla WebSocket library to upgrade HTTP connections at the /ws endpoint, stores active connections in a global slice, and broadcasts JSON-encoded hardware status updates through a background goroutine that prunes dead connections automatically.

CasaOS leverages persistent bi-directional communication channels to deliver instantaneous hardware status updates to web clients without requiring page refreshes. This architecture pushes real-time peer and device information from the backend to connected browsers using a lightweight event-driven subsystem. The implementation centers on the Gorilla WebSocket library for connection management and a custom broadcast loop for data distribution.

WebSocket Endpoint Registration and Connection Upgrade

The WebSocket handshake begins at a dedicated endpoint registered in the Echo router. In route/v1.go, the system attaches a GET handler to the /ws path that delegates to the connection upgrade logic.

When a client requests this endpoint, the ConnectWebSocket function in route/v1/file.go performs the protocol upgrade using Gorilla's Upgrader struct:

// route/v1/file.go
func ConnectWebSocket(ctx echo.Context) error {
    upgrader := websocket.Upgrader{
        CheckOrigin: func(r *http.Request) bool { return true },
    }
    ws, err := upgrader.Upgrade(ctx.Response(), ctx.Request(), nil)
    if err != nil {
        return err
    }
    // store the connection globally
    service.WebSocketConns = append(service.WebSocketConns, ws)
    return nil
}

The CheckOrigin callback allows connections from any origin, enabling cross-origin access for the web interface. Upon successful upgrade, the function immediately appends the resulting *websocket.Conn pointer to a global collection for later broadcasting.

Connection Storage and Global State Management

Active WebSocket connections are maintained in a package-level slice declared in service/service.go. The WebSocketConns variable holds all current client connections as an in-memory registry:

  • Global accessibility: Any service can reference this slice to broadcast messages
  • Dynamic growth: Connections append to the slice as clients join
  • Cleanup responsibility: The broadcast loop manages removal of stale connections

This global state approach eliminates the need for a separate connection manager service, keeping the architecture simple for single-node deployments.

Real-Time Broadcast Logic in the Notify Service

The SendMeg function in service/notify.go implements the core broadcasting mechanism. Running continuously in a background goroutine, this method gathers current hardware status and pushes updates to every connected client:

// service/notify.go (simplified)
func SendMeg() {
    for {
        // retrieve fresh peer list
        list := MyService.Notify().GetList(types.NOTIFY_APP)
        payload, _ := json.Marshal(list)

        // send to every open websocket
        var alive []*websocket.Conn
        for _, c := range service.WebSocketConns {
            if err := c.WriteMessage(websocket.TextMessage, payload); err == nil {
                alive = append(alive, c) // keep only healthy connections
            }
        }
        service.WebSocketConns = alive
        if len(alive) == 0 {
            service.SocketRun = false
        }
        time.Sleep(2 * time.Second)
    }
}

The function performs three critical operations:

  1. Data aggregation: Calls GetList to fetch current peer and hardware status
  2. Connection filtering: Writes the JSON payload to each socket, retaining only connections without errors
  3. Lifecycle management: Sets SocketRun = false when no clients remain, halting the loop until new connections arrive

This pruning strategy ensures that dead or disconnected clients do not accumulate in memory, preventing resource leaks over long runtimes.

Hardware Status Integration

When hardware events occur—such as USB device changes or peer appearances—the Peer service updates its internal model. These changes propagate to WebSocket clients through the following flow:

  • The Peer service maintains the authoritative hardware state
  • The Notify service queries this state via GetList(types.NOTIFY_APP)
  • Every two seconds, SendMeg serializes the updated peer list into JSON
  • All active connections in WebSocketConns receive the payload via WriteMessage

This polling-based approach ensures that web interfaces display current hardware topology without requiring manual refreshes or complex event subscription mechanisms.

Client-Side Connection Handling

Web clients establish connections using standard browser WebSocket APIs. The JavaScript implementation connects to the host-relative /ws endpoint and handles incoming status messages:

const ws = new WebSocket('ws://<casa-os-host>/ws');
ws.onmessage = (event) => {
    const hardwareStatus = JSON.parse(event.data);
    console.log('Real-time status:', hardwareStatus);
};
ws.onclose = () => console.log('WebSocket closed');

Clients receive JSON payloads containing peer lists and hardware state changes, which frontend applications can render immediately to reflect system status changes.

Graceful Shutdown and Connection Lifecycle

The broadcast system implements efficient lifecycle management to conserve CPU cycles when idle. The SocketRun boolean flag controls the SendMeg goroutine execution:

  • Automatic suspension: When WebSocketConns empties, the loop sets SocketRun = false and stops iterating
  • Restart on demand: New connections hitting the /ws endpoint reactivate the broadcast loop
  • Resource efficiency: No background processing occurs while no clients are listening

This design optimizes resource usage on low-power hardware typical of CasaOS deployments.

Summary

  • Gorilla WebSocket handles the HTTP upgrade and low-level frame management for all connections
  • The /ws endpoint in route/v1/file.go upgrades requests and stores connections in the global WebSocketConns slice
  • SendMeg in service/notify.go broadcasts JSON-encoded hardware status every two seconds while filtering dead connections
  • Automatic cleanup removes failed connections from the global slice during each broadcast iteration
  • Lifecycle flags pause the broadcast loop when no clients are connected, resuming only when new WebSocket handshakes occur

Frequently Asked Questions

What WebSocket library does CasaOS use?

CasaOS uses the Gorilla WebSocket library to handle protocol upgrades and message framing. This library provides the Upgrader type used in route/v1/file.go to transform HTTP requests into persistent WebSocket connections, along with the WriteMessage method for broadcasting data to clients.

How does CasaOS handle disconnected WebSocket clients?

The system implements optimistic pruning during the broadcast phase. When SendMeg iterates through WebSocketConns, it attempts to write to each connection and collects only those without errors into a new alive slice. Failed writes indicate disconnected clients, which are automatically excluded from the updated global slice, effectively removing dead connections without explicit disconnect handlers.

What endpoint is used for WebSocket connections in CasaOS?

WebSocket connections are established at the GET /ws endpoint. This route is registered in route/v1.go and handled by the ConnectWebSocket function in route/v1/file.go. Clients must request this specific path to trigger the protocol upgrade from HTTP to WebSocket.

How often does CasaOS broadcast hardware status updates?

The broadcast loop executes every two seconds. The SendMeg function includes a time.Sleep(2 * time.Second) call at the end of each iteration, creating a polling interval that balances real-time responsiveness with CPU usage on resource-constrained devices.

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 →