Real-Time Subscriptions¶
How the admin panel receives live data from SpacetimeDB without polling.
Overview¶
The admin panel maintains a persistent WebSocket connection to SpacetimeDB for read-only subscriptions. This connection provides live updates to all panel pages — when a ban is created, a player connects, or an admin action is logged, the panel updates instantly.
Connection Architecture¶
graph TD
Browser
subgraph StdbProvider["StdbProvider (React context)"]
WS["WebSocket → SpacetimeDB (port 3000)"]
Sub["Subscribe to 6 platform tables"]
SA["Receive SubscribeApplied (initial rows)"]
TU["Receive TransactionUpdate (incremental changes)"]
end
subgraph UseTable["useTable‹T› hook"]
Insert["onInsert → add to React state"]
Update["onUpdate → replace in React state"]
Delete["onDelete → remove from React state"]
end
subgraph UseCluster["useClusterState() hook"]
RQ["React Query (10s stale, 30s polling for computed state)"]
end
Browser --> StdbProvider
Browser --> UseTable
Browser --> UseCluster
StdbProvider¶
The StdbProvider component initialises the SpacetimeDB connection on mount:
// lib/stdb-provider.tsx
function StdbProvider({ children }) {
useEffect(() => {
const conn = new SpacetimeDBClient(STDB_HOST, MODULE_NAME);
conn.subscribe([
'SELECT * FROM server_config',
'SELECT * FROM ban_entry',
'SELECT * FROM whitelist_entry',
'SELECT * FROM admin_role',
'SELECT * FROM panel_session',
'SELECT * FROM panel_audit_log',
]);
conn.connect();
}, []);
return <StdbContext.Provider value={conn}>{children}</StdbContext.Provider>;
}
useTable Hook¶
Generic hook that converts SpacetimeDB table callbacks into React state:
function useTable<T>(tableName: string): T[] {
const [rows, setRows] = useState<T[]>([]);
useEffect(() => {
const table = connection.db.getTable(tableName);
setRows([...table]);
table.onInsert((row) =>
setRows(prev => [...prev, row]));
table.onUpdate((oldRow, newRow) =>
setRows(prev => prev.map(r => r === oldRow ? newRow : r)));
table.onDelete((row) =>
setRows(prev => prev.filter(r => r !== row)));
return () => { /* cleanup subscriptions */ };
}, [tableName]);
return rows;
}
Update Flow¶
1. Admin clicks "Ban Player" in panel
2. POST /api/ban → API Gateway → panel_ban_player reducer
3. Reducer creates BanEntry row in STDB
4. STDB pushes TransactionUpdate to ALL connected WebSocket clients
5. Panel's BanEntry subscription receives onInsert callback
6. useTable updates React state → components re-render
7. Dashboard ban feed, Players page ban status, all update instantly
Key insight: The panel that triggered the action AND every other connected panel session see the update simultaneously. No polling, no manual refresh.
Connection Guard¶
The ConnectionGuard component wraps pages that require an active STDB connection:
| State | Display |
|---|---|
| Connecting | Spinner with "Connecting to SpacetimeDB..." |
| Connected | Page content |
| Disconnected | Warning with reconnect button |
| Error | Error message with details |
BSATN Protocol¶
The WebSocket connection uses the SpacetimeDB v2 binary protocol with BSATN (Binary SpacetimeDB Algebraic Type Notation) encoding:
| Message | Direction | Description |
|---|---|---|
| Subscribe | Client → Server | SQL queries declaring interest |
| SubscribeApplied | Server → Client | Initial rows matching queries |
| TransactionUpdate | Server → Client | Incremental changes (inserts + deletes) |
| ReducerResult | Server → Client | Outcome of reducer calls |
Related Pages¶
- Subscriptions — platform subscription architecture
- Panel Overview — panel technology stack
- BSATN Wire Format — binary protocol details (restricted)