Realtime integration¶
The realtime service (platform/realtime) is a Cloudflare Worker with one
Durable Object per gateway (GatewayRoom, keyed by idFromName(device_id)).
Nexus and the Worker call each other over Connect in both directions; every
call carries X-Internal-Key, the same secret on both sides
(NEXUS_INTERNAL_API_KEY in nexus, INTERNAL_API_KEY in the Worker). Terraform
generates it.
Worker routes¶
| Path | Handler |
|---|---|
GET /healthz |
ok |
/gateway.v1.RealtimePushService/Push |
realtime-push.ts: nexus → DO push |
/gateway/{device_id} |
WebSocket upgrade, forwarded to the device's DO |
The upgrade's role query parameter selects the socket type:
| Role | Credential | Validated by | May send |
|---|---|---|---|
| gateway (default) | ?token= device API token |
ValidateGatewayToken |
telemetry frames, shell output |
viewer |
?ticket= from IssueViewerTicket |
ValidateViewerTicket |
nothing (messages are dropped) |
operator |
?ticket= from IssueShellTicket |
ValidateShellTicket |
shell frames only |
A DO holds at most one gateway socket and any number of viewer and operator
sockets. The device id, user and org are bound at upgrade from nexus's answer,
never from the URL. A viewer session is closed after 12 h and an operator
session after 1 h (close code 4402); the portal reconnects with a fresh
ticket, which fails if access was revoked.
Nexus → Worker: RealtimePushService.Push¶
internal/push/realtime.go. The client is built at startup from
NEXUS_REALTIME_URL and NEXUS_INTERNAL_API_KEY.
PushConfig(deviceID, version, configHash, configData)→apply_configframe.SendCommand(deviceID, commandID, commandType, payload)→commandframe. Used forconfirm_config,set_hostname(afterUpdateGatewaychanges the hostname),restart_agent,reboot, diagnostics,update_agent,reenroll, the published-code commands, andrelay_reassess(relay failover).
PushResponse.status:
| Status | Meaning | nexus |
|---|---|---|
ok |
The agent sent a command_result for this command id within 3 s |
success |
sent |
Written to the socket; nothing to acknowledge (config push) | success |
no_ack |
Command written, no acknowledgement within 3 s | push.ErrNotAcknowledged |
offline |
No gateway socket on the DO | push.ErrOffline |
offline and no_ack are soft failures for config: the desired version is
already persisted, and the agent reconciles through GetDesiredState /
GetConfig when it reconnects. A transport error (Worker 5xx, network) is a
hard error returned to the caller.
ws.send() on a dead socket does not throw, so only an acknowledgement proves
a command arrived; that is why commands wait for command_result.
Worker → Nexus: GatewayInternalService¶
platform/realtime/src/nexus.ts builds a Connect client over fetch with an
interceptor that sets X-Internal-Key.
- Upgrade — the DO validates before accepting the socket. An invalid gateway token gets 401, a token bound to another device id 409, and an RPC error 503 so the agent retries with backoff.
- Each gateway frame — broadcast verbatim to viewers, then dispatched:
heartbeat→Heartbeat,state_report→StateReport(mapping instate-report-mapping.ts),config_report→ConfigReport,agent_update→ReportAgentUpdate,log_batch→UploadInternalLogs.command_resultresolves the pending push and is not forwarded. A failed dispatch is logged and does not close the socket. - Shell frames go gateway ↔ operator only.
- Gateway socket close — viewers get
{type: "status", online: false}immediately, thenDeviceOfflineis called. - Operator socket close —
ReportShellSessionEnd.
Pitfalls¶
- Trailing newline in the secret.
echo "$key" | wrangler secret put …stores the newline; workerd then rejects the header as invalid and every nexus call fails with 503. Useprintf '%s'. - Redirect mode.
@connectrpc/connect-websetsredirect: "error", which workerd rejects.nexus.tswrapsfetchto use"manual"; keep the wrapper. - JSON on the wire. Both directions use Connect-JSON so traffic is readable
in
wrangler tail.