SSE-Observatory
PROTOTYPEA browser-based debugger for Server-Sent Events: query language, replay, and multi-tab stream sharing.
Personal project · Feb–Mar 2026
THE PROBLEM
Debugging an SSE stream usually means curl and scrolling logs. There is no easy way to filter, correlate or replay a live stream.
HOW IT WORKS
- A SharedWorker multiplexes one SSE connection across browser tabs, with reconnect backoff.
- User-written interceptors run in a sandboxed worker pool with a 100 ms timeout and are terminated if they hang.
- Events are filtered with a recursive-descent query language (AND, OR, parentheses) compiled to closures. A canvas timeline and time-travel playback (seek, step, return to live) support replay.
- An Express proxy handles CORS and auth headers, with an SSRF guard, rate limiting and short-lived one-time tickets.
THE PIPELINE
- Express proxyCORS and auth headers, SSRF guard, rate limiting, one-time tickets.
- SharedWorkerOne SSE connection multiplexed across tabs, with reconnect backoff.
- Interceptor workersSandboxed pool with a 100 ms timeout; hung workers are terminated.
- Query languageRecursive descent, compiled to closures (AND, OR, parentheses).
- Timeline and replayCanvas timeline with seek, step and return to live.
SCREENSHOTS
Captured from the project's own repository. Select an image to open it full size.
ENGINEERING EVIDENCE
- One shared SSE connection per stream across tabs, with reconnect backoff. sharedSSEWorker.ts ↗
- Interceptors are isolated in workers and killed on timeout. interceptorSandbox.ts ↗
- Recursive-descent parser that compiles queries into predicate functions. queryParser.ts ↗
- Server-side SSRF guard (private-IP and hostname checks) and rate limiting in the proxy. server.js ↗
- About 103 unit tests plus 18 Playwright end-to-end tests, including reconnect-storm, memory-pressure and worker-death scenarios.
DECISIONS & INVESTIGATIONS
- Proxy tickets for auth tokens were added, then abandoned by the client about 50 minutes laterREVERTED
At HEAD no client code calls the ticket endpoint: obtainSSEProxyTicket() builds a direct proxy URL, so auth tokens are again sent in the URL query string.
- Multiplex one EventSource per (url, token) across tabs in a SharedWorker, with a per-tab fallbackADOPTED
Measured end to end (see investigation sse-sharedworker-one-upstream-connection): three tabs on the same url and token produce one upstream request; a different token produces a second.
The proxy now exists in two implementations, server.js and api/sse/index.js, which differ (ticket scheme; server.js applies its origin and private-address checks only in production mode).
1 tab: 1 total / 1 open upstream request. 2 tabs on the same URL: still 1 / 1. 3 tabs: still 1 / 1; each of the three tabs showed 9 event rows.
Chromium: `while(true){}` was killed at about 105-107 ms; a normal interceptor returned in about 3.5-4 ms including worker creation; a direct call to fetch failed because the name is shadowed.
WHAT ISN'T DONE
- Events are held in a capped array (1,000) rather than a true ring buffer.
- Interceptor workers are killed on timeout, but they are not isolated from the network.
- No CI workflow yet.
NEXT STEPS
Each one comes from a gap listed above. It says what fixing the gap would take; it is not a promise.
- Replace the capped array with a real ring buffer.
- Isolate interceptor workers from the network, not only kill them on timeout.
- Add a CI workflow.
STACK
- TypeScript
- React
- Vite
- Web Workers
- SharedWorker
- IndexedDB
- Express
- Vitest
- Playwright



