CRUMB a card from devarno-cloud

WebSocket Hub & Rooms

sparki intermediate 5 min read

ELI5

The hub is a switchboard. Every connected dashboard, TUI and mobile client plugs in once and gets put in a room (e.g., build:<id>). When the engine has news, it can shout to one client, one room, or everyone — three knobs, no more.

Technical Deep Dive

internal/websocket/hub.go defines Hub, Client, and three message types. The hub runs one goroutine that selects over register, unregister, broadcast, roomBroadcast, and directMessage.

Class Diagram

classDiagram
class Hub {
-map~Client,bool~ clients
-map~string,map~Client,bool~~ rooms
-map~UUID,Client~ clientsByID
-chan Client register
-chan Client unregister
-chan Message broadcast
-chan RoomMessage roomBroadcast
-chan DirectMessage directMessage
-HubMetrics metrics
+Run(ctx)
}
class Client {
+UUID ID
+chan Message send
}
class Message {
+string Type
+map~string,any~ Payload
}
class RoomMessage { +string Room +Message Message }
class DirectMessage { +UUID ClientID +Message Message }
Hub --> Client : tracks
Hub --> Message : broadcasts
Hub --> RoomMessage : roomBroadcast
Hub --> DirectMessage : directMessage

Channel Buffer Sizes

ChannelBuffer
register256
unregister256
broadcast1024
roomBroadcast1024
directMessage1024

Three Send Paths

sequenceDiagram
participant E as engine code
participant H as Hub
participant C as Client(s)
E->>H: hub.broadcast <- msg
H-->>C: send to every clients[true]
E->>H: hub.roomBroadcast <- {room, msg}
H-->>C: send to clients in rooms[room]
E->>H: hub.directMessage <- {clientID, msg}
H-->>C: send to clientsByID[clientID]

Indexes

  • clients — set of all currently-registered clients (used by the all-clients broadcast path).
  • rooms — map<roomName, set>. A client is added to a room via the handler-level join action; one client may sit in many rooms.
  • clientsByID — map<UUID, Client> for O(1) direct message routing.

Metrics

HubMetrics tracks TotalConnections, ActiveConnections, MessagesSent, MessagesReceived, BroadcastsSent. Mutated under mu sync.RWMutex.

Key Terms

  • room → string-keyed subscription bucket; conventionally build:<id>, project:<id>, deploy:<id>
  • register/unregister → channels the connection upgrade handler writes to on connect/disconnect
  • direct message → addressed by client UUID, used for per-user notifications
  • broadcast → fan-out to every connected client; reserve for global system messages

Q&A

Q: What happens if a client’s send channel is full? A: The hub’s standard pattern is to drop the slow client (close send, remove from clients and rooms) so a single stuck consumer cannot back up the central goroutine.

Q: How are direct messages routed? A: Through clientsByID keyed by uuid.UUID. The handler that creates a Client populates this map at register-time.

Q: Are room memberships persisted? A: No. Rooms are in-memory only; a hub restart drops all subscriptions and clients must re-join.

Examples

A build progresses: the worker (sparki-004) emits a log line → handler converts it to a Message{Type:"build.log", Payload:{...}} → calls hub.roomBroadcast <- &RoomMessage{Room:"build:"+id, Message:msg} → every dashboard tab subscribed to that build receives the line within one event-loop tick.

neighbors on the map