Skip to content

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_config frame.
  • SendCommand(deviceID, commandID, commandType, payload) → command frame. Used for confirm_config, set_hostname (after UpdateGateway changes the hostname), restart_agent, reboot, diagnostics, update_agent, reenroll, the published-code commands, and relay_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 in state-report-mapping.ts), config_report → ConfigReport, agent_update → ReportAgentUpdate, log_batch → UploadInternalLogs. command_result resolves 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, then DeviceOffline is 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. Use printf '%s'.
  • Redirect mode. @connectrpc/connect-web sets redirect: "error", which workerd rejects. nexus.ts wraps fetch to use "manual"; keep the wrapper.
  • JSON on the wire. Both directions use Connect-JSON so traffic is readable in wrangler tail.