Hub & Commands
RoidHubWS — the WebSocket client singleton that connects to the backend Roid Hub, handles reconnection, and routes inbound messages to registered callbacks. RoidHubCommand — stateless helper functions that send typed commands to the hub.
RoidHubWS
A class-based WebSocket wrapper exported as a module-level singleton via getRoidHubWS(). The singleton pattern ensures that all parts of the application share the same connection. Calling resetRoidHubWS() destroys the current instance and creates a fresh one — used by the disconnect flow to guarantee a clean state.
Constructor Config
| Option | Default | Description |
|---|---|---|
| wsUrl | VITE_HUB_WS_URL env var | WebSocket server URL. Query params user_id and ticket are appended when non-empty. |
| reconnectInterval | 3 000 ms | Base delay between reconnect attempts. Each attempt doubles the delay (exponential backoff), capped at 30 000 ms. |
| maxReconnectAttempts | Infinity | Maximum number of consecutive reconnect attempts. When reached, reconnection stops silently. |
| connectionTimeout | 10 000 ms | How long to wait for the WebSocket open event before aborting and attempting a reconnect. |
Public API
connect(): voidCreates the WebSocket connection. Noop if already OPEN or CONNECTING. Sets isIntentionallyClosed = false so auto-reconnect is active.
disconnect(): voidCloses the socket and clears timers. Sets isIntentionallyClosed = true so the onclose handler does NOT trigger a reconnect. Also clears the message callback to prevent stale deliveries.
isConnected(): booleanReturns true when readyState === WebSocket.OPEN.
sendCommand(command: object): voidJSON-serialises the command object and sends it over the socket. Logs an error and returns silently if not connected.
setMessageCallback(cb): voidRegisters the inbound message handler. Called with a parsed HubMessage on every text or Blob frame received. Blob frames are converted to text asynchronously before parsing.
setConnectionStatusCallback(cb): voidCalled with (true) on successful open; called with (false, reason) on close or timeout.
getUserId(): stringReturns the current authenticated user id (used to populate the from field on outbound commands).
Singleton helpers
getRoidHubWS()Returns the current singleton, creating it with default config on first call.
resetRoidHubWS()Calls disconnect() on the current singleton, then replaces it with a fresh instance. Used by handleDisconnect in RoidRunnerPanel.
Reconnect Strategy
Connection fails (onclose fires while isIntentionallyClosed = false) or connection timeout fires.
handleReconnect() is called. reconnectAttempts is incremented.
Delay = min(reconnectInterval × 2^(attempts-1), 30 000 ms). A setTimeout schedules createConnection().
On success, reconnectAttempts resets to 0 and connectionStatusCallback(true) fires.
If maxReconnectAttempts is reached, reconnection stops. Panel shows disconnected status.
HubMessage type
export interface HubMessage {
cmd: string; // Command identifier (run_roid, kill_roid, roid_output, …)
[key: string]: unknown; // Additional payload fields vary by cmd
}
// Common outbound command shape:
{
cmd: string, // e.g. "run_roid" | "kill_roid" | "get_roids" | "get_bars"
from: string, // user_id
to?: string, // roid_id (when targeting a specific process)
data?: unknown // payload (e.g. slang source string for run_roid)
}RoidHubCommand
Pure functions that call hub.sendCommand() with the correct payload shape. All accept the hub instance and userId as their first two arguments, keeping the command module stateless and independently testable.
| Function | cmd sent | Extra payload | Purpose |
|---|---|---|---|
| sendRunSlang(hub, slang, userId) | run_roid | data: slang string | Dispatch the current graph's decompiled Slang to the hub to start a new roid process. |
| sendKillRoid(hub, userId, roidId) | kill_roid | to: roidId | Terminate a running roid process by ID. |
| sendGetRoids(hub, userId) | get_roids | (none) | Request the current list of running roid processes for this user. |
| sendGetBars(hub, userId, roidId) | get_bars | to: roidId | Request the full cached bar and output history for a roid. |
| sendGetNodes(hub, userId, roidId) | get_nodes | to: roidId | Request the output node name list for a roid. |
| sendGetSlang(hub, userId, roidId) | get_slang | to: roidId | Request the Slang source code for a roid (to display in SlangViewerModal). |
RoidRunnerHeader
A 36 px toolbar bar that shows the Roid Runner label with a live connection dot, and four action buttons. It also owns the Run flow: decompile → validate → send.
Props
| Prop | Type | Description |
|---|---|---|
| theme | 'light' | 'dark' | Controls button colours and border tones. |
| connected | boolean | Drives the green/red status dot and enables/disables buttons. |
| hub | RoidHubWS | The hub singleton; used to send commands from the header. |
| onLog | (entry: LogEntry) => void | Append a log entry to the panel logger. |
| onConnect | () => void | Called when the Connect button is clicked. |
| onDisconnect | () => void | Called when the Disconnect button is clicked. |
| onGetRoids | () => void | Called when the Refresh button is clicked. |
Run Flow
decompile({ excludeConfig: true }) is called. If the result is empty, an error is logged and the flow aborts.
The decompiled Slang source is logged line-by-line as "request" entries (blue in the logger).
validateSlang(slang) is called. Any validation errors are logged and the flow aborts.
sendRunSlang(hub, slang, userId) sends the cmd: "run_roid" message to the hub.
The hub responds with a run_roid message; the panel appends the new roid and auto-selects it.
Button states
| Button | Enabled when | Active colour |
|---|---|---|
| Connect | !connected | #3b82f6 (blue) |
| Run ▶ | connected | #22c55e (green) |
| Refresh ⟳ | connected | #6366f1 (indigo) |
| Disconnect | connected | #ef4444 (red) |