Realtime

Wahoo provides bounded Server-Sent Events (SSE) and WebSocket primitives. Generated realtime routes return 501 Not Implemented until the application adds policy.

Work in progress: Do not use Wahoo in production yet.

SSE Topics

Use a topic for tenant- or subject-scoped events. Authorize the request before you select the topic handler.

events := realtime.NewHub(
    realtime.WithMaxClients(100),
    realtime.WithMaxStreamAge(10*time.Minute),
)

topic, err := events.Topic("tenant:ten_123")
if err != nil {
    return err
}
runtime.Handle("GET /events", authenticated(topic))

events.PublishTo("tenant:ten_123", realtime.Event{
    ID:   "evt_123",
    Name: "project.updated",
    Data: []byte(`{"project_id":"prj_123"}`),
})

The hub has a default 1000-client limit, 15-minute stream lifetime, 25-second heartbeats, one buffered event per client, and slow-client eviction. Event data is copied before delivery. SSE clears the normal HTTP write deadline for the selected long-lived response.

Publish is global. Use it only for intentionally global events. The hub is local to one Go process. Publish through a shared system when you run more than one instance, then deliver to each instance's authorized local clients.

Required Application Policy

  1. Authenticate the session before the SSE handler attaches.
  2. Resolve and authorize the tenant before selecting a topic.
  3. Apply per-principal and per-IP connection limits.
  4. Publish only tenant-scoped data.
  5. Bound event payload size before you publish it.

WebSockets

realtime.WebSocket keeps the request-host same-origin behavior from coder/websocket, sets a 1 MiB message limit, and applies a 15-minute connection context. It does not authenticate, rate limit, serialize writes, or provide shared fanout.

runtime.Handle("GET /ws", authenticated(realtime.WebSocket(handleSocket, &websocket.AcceptOptions{
    OriginPatterns: []string{"app.example.com"},
})))

Do not use realtime.Echo() in a public service. Do not expose internal errors through WebSocket close reasons.

Test

go test -race ./...

Test authorization before connection setup, tenant isolation, client limits, idle timeouts, message-size limits, and write serialization.