Skip to content

[Enhancement]: Replace polling on /workspace/:id/events and /session-groups/events with Server-Sent Events (SSE) #3126

Description

@Pallavikumarimdb

Problem

The client currently polls two endpoints on a tight interval (sub-second) to stay in sync with workspace state:

  • GET /workspace/:id/events
  • GET /workspace/:id/session-groups/events

Sample from local dev logs (~1 second window):

GET /workspace/ws_8901851c2c65/events 200 5ms
OPTIONS /workspace/ws_8901851c2c65/session-groups/events 204 0ms
GET /workspace/ws_8901851c2c65/session-groups/events 200 0ms
OPTIONS /workspace/ws_8901851c2c65/events 204 0ms
GET /workspace/ws_8901851c2c65/events 200 2ms

This pattern repeats continuously for the lifetime of an open workspace, generating roughly 6–8 requests/second per client (each GET preceded by an OPTIONS preflight).

Why this is a problem:

  1. Wasted round trips - most polls return no meaningful state change, so the majority of requests do nothing but consume server and network resources.
  2. CORS preflight overhead - every GET is preceded by an OPTIONS request, doubling actual request volume for zero payload gain. This suggests Access-Control-Max-Age isn't set/cached properly on the client side, but even with that fixed, polling itself remains wasteful.
  3. Scales linearly with active workspaces - each open client sustains this load indefinitely. In multi-tenant or hosted environments this compounds quickly.
  4. No backoff when idle - polling continues at the same rate regardless of whether anything has changed, or whether the tab/window is even in the foreground.
  5. Endpoint naming already implies push - both endpoints are literally named events, suggesting the original intent was a streaming interface that ended up implemented as poll-based, likely as a stopgap.

Proposed solution

Migrate both endpoints from polling to Server-Sent Events:

  • Auth cannot use native EventSource. All requests currently require Authorization: Bearer <token> (openwork-server.ts). Native EventSource can't send custom headers, and passing the token as a query param is a non-starter (leaks into logs/history/referrer). Implementation must use a fetch()-based readable stream reader instead which also means losing EventSource's built-in auto-reconnect; reconnect-with-backoff and Last-Event-ID tracking need to be hand-rolled.
  • Server idle timeout will kill long-lived connections. server.ts sets idleTimeout: 120s, mirrored in serve-node.ts's keepAliveTimeout. Any SSE stream idle for 2 minutes gets silently dropped by the Node HTTP layer. Requires either server-sent heartbeat comments (:\n\n) every ~30s, or a separate timeout policy for streaming routes.
  • No pub/sub layer exists yet. ReloadEventStore and SessionGroupEventStore (events.ts, session-groups.ts) are in-memory ring buffers with no subscribe/notify mechanism. This needs to be built, including per-workspace connection tracking and cleanup on client disconnect, before any streaming can happen this is new infrastructure, not a refactor of existing code.
  • Desktop/Tauri routing dependency. openwork-server.ts already has an OPENWORK_STREAM_URL_RE regex that routes /events-matching URLs to window.fetch instead of Tauri's IPC-based desktopFetch, because Tauri's fetch blocks until the body closes (which would freeze the desktop webview on an infinite stream). /events and session-groups/events both appear to match this regex already, but this needs explicit testing confirmation, not just static analysis verify in a real Tauri build, not just reading the regex.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions