feat(canvas): live events + realtime stats for freeform canvases - #3993
feat(canvas): live events + realtime stats for freeform canvases#3993fuziontech wants to merge 1 commit into
Conversation
Freeform canvases can now subscribe to the project's live event stream and read realtime counters — two agents can build live event feeds and users-online readouts without re-polling insights.
New `ph.*` streaming capability, brokered host-side so the sandboxed iframe never holds the project-scoped `live_events_token` JWT:
- `ph.subscribeLiveEvents(params, onEvent, onEnd)` — the canvas posts a `live-subscribe` frame; the renderer opens the SSE to the livestream service (`live.{region}.posthog.com/events`) using a brokered connection config and fans each event into the iframe as a `live-event` postMessage frame. `params.properties` filters only support the livestream operator allowlist (`LIVE_EVENTS_SUPPORTED_OPERATORS`, mirroring the app's live feed).
- `ph.liveStats()` — a ONE-SHOT poll riding the generic `data-request` frame with `method: "liveStats"` → `GET <live host>/stats` → `{ users_on_product, active_recordings }` (the same "users online" pill the Activity page shows). A canvas also derives a client-side 60s sliding-window users-online count from the streamed `distinct_id`s (pattern documented in the agent system prompt) to match the Web-Analytics-Live readout.
Plumbing:
- core: live message frames + schemas on the postMessage protocol, `getLiveEventsUrlFromRegion` (shared), `CanvasDataService.liveConnectionConfig`/`liveStats`, `ICanvasDataService` surface.
- host-router: `canvasData.liveConnectionConfig` + `canvasData.liveStats` procedures.
- ui: `openLiveEventsStream` + `buildLiveEventsUrl`/`parseSseChunk` in the data bridge, `FreeformCanvas` routes the live frames and owns stream lifecycle (close on unmount/unsubscribe).
- prompt: `FREEFORM_BASE` documents the live APIs + the users-online pattern; canvas-templates skill updated with the extension walkthrough.
Tests: schema round-trip cases for the new frames + operators allowlist, and unit tests for SSE URL building/chunk parsing.
Generated-By: PostHog Code
Task-Id: 49f7e918-8341-4c95-a2aa-97ae2aa6a8b9
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
React Doctor found 1 issue in 1 file · 1 warning. 1 warning
Reviewed by React Doctor for commit |
|
Hey @fuziontech, we've migrated this repo into the PostHog/posthog monorepo, so this PR needs to be recreated there. Easiest path: check out the monorepo and run the Shout if you get stuck. |
Problem
Freeform canvases (agent-built React apps in the desktop app) could only fetch historical data via
ph.query/ph.loadInsight— saved insights re-run against ClickHouse. Anything that should update as events arrive (a live event feed, a "users online" counter) had to re-poll an insight, which lags ingestion and hammers/query. PostHog's own Activity → Live events and Web Analytics Live surfaces already solve this with a dedicated livestream SSE service, but canvases had no way to reach it: the sandboxed iframe can't hold the project-scopedlive_events_tokenJWT, and its CSP blocks the host.This adds a host-brokered live events avenue so canvases can subscribe to the live stream and read realtime counters — keeping the token server-side, matching the existing
ph.*security model.Changes
New streaming capability on the
ph.*canvas bridge (shim → postMessage → ui bridge → tRPC → core service, the documented extension path in the canvas-templates skill):ph.subscribeLiveEvents(params, onEvent, onEnd)— opens a live SSE subscription. The canvas posts alive-subscribeframe; the renderer fetches a brokered connection config (canvasData.liveConnectionConfig→CanvasDataService.liveConnectionConfig), opens the SSE tolive.{region}.posthog.com/eventsitself, and fans each event into the iframe as alive-eventframe. The scoped JWT lives only in the renderer — it never crosses into the sandbox.params.propertiesfilters only support the livestream operator allowlist (LIVE_EVENTS_SUPPORTED_OPERATORS, mirroring the app's live feed) — validated by the schema and spelled out in the agent prompt.ph.liveStats()— a ONE-SHOT poll (rides the genericdata-requestframe,method: "liveStats") →canvasData.liveStats→GET <live host>/stats→{ users_on_product, active_recordings }, the same "users online" pill the Activity page shows. For a live readout the prompt also documents the client-side 60s sliding-window count derived from the streameddistinct_ids — matching the Web-Analytics-Live tab without an extra host method.Plumbing:
live-subscribe/live-unsubscribe/live-event/live-stream-status),getLiveEventsUrlFromRegion(shared),CanvasDataService.liveConnectionConfig/liveStats,ICanvasDataServicesurface, cached per-project live JWT.canvasData.liveConnectionConfig+canvasData.liveStatsprocedures.openLiveEventsStream+ exportedbuildLiveEventsUrl/parseSseChunkin the data bridge;FreeformCanvasroutes the live frames and owns stream lifecycle (close on unmount/unsubscribe, replace on resubscribe); sandbox shim getsph.subscribeLiveEvents/ph.liveStats+ event dispatch.FREEFORM_BASEdocuments the live APIs + users-online pattern; canvas-templates SKILL.md gains a "Live / realtime (SSE)" section. Companion MCP PR documents the same in the canvas-publish tool description (see PostHog/posthog).How did you test this?
/eventsURL builder (freeformDataBridge.live.test.ts), and schema round-trip cases for the new live frames + operator allowlist (freeformSchemas.test.ts). Full@posthog/core(2,844) and@posthog/ui(2,413) suites green;core/ui/host-routertypecheck clean;biome checkclean on all touched packages.Automatic notifications
Created with PostHog Code