Skip to content

feat(canvas): live events + realtime stats for freeform canvases - #3993

Draft
fuziontech wants to merge 1 commit into
mainfrom
posthog-code/canvas-live-events
Draft

feat(canvas): live events + realtime stats for freeform canvases#3993
fuziontech wants to merge 1 commit into
mainfrom
posthog-code/canvas-live-events

Conversation

@fuziontech

Copy link
Copy Markdown
Member

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-scoped live_events_token JWT, 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 a live-subscribe frame; the renderer fetches a brokered connection config (canvasData.liveConnectionConfigCanvasDataService.liveConnectionConfig), opens the SSE to live.{region}.posthog.com/events itself, and fans each event into the iframe as a live-event frame. The scoped JWT lives only in the renderer — it never crosses into the sandbox. params.properties filters 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 generic data-request frame, method: "liveStats") → canvasData.liveStatsGET <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 streamed distinct_ids — matching the Web-Analytics-Live tab without an extra host method.

Plumbing:

  • core: live message frames + Zod schemas on the postMessage protocol (live-subscribe/live-unsubscribe/live-event/live-stream-status), getLiveEventsUrlFromRegion (shared), CanvasDataService.liveConnectionConfig/liveStats, ICanvasDataService surface, cached per-project live JWT.
  • host-router: canvasData.liveConnectionConfig + canvasData.liveStats procedures.
  • ui: openLiveEventsStream + exported buildLiveEventsUrl/parseSseChunk in the data bridge; FreeformCanvas routes the live frames and owns stream lifecycle (close on unmount/unsubscribe, replace on resubscribe); sandbox shim gets ph.subscribeLiveEvents/ph.liveStats + event dispatch.
  • prompt/docs: FREEFORM_BASE documents 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?

  • Automated: new unit tests for the SSE chunk parser + /events URL 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-router typecheck clean; biome check clean on all touched packages.
  • Not run: end-to-end against a live project (requires the running desktop app + a signed-in PostHog project with ingesting traffic). The SSE parse + URL + schema layers are covered by the unit tests above; the actual stream rendering should be verified in the app before un-drafting.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Created with PostHog Code

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
@trunk-io

trunk-io Bot commented Jul 30, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

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

@github-actions

Copy link
Copy Markdown

React Doctor found 1 issue in 1 file · 1 warning.

1 warning

src/features/canvas/freeform/FreeformCanvas.tsx

Reviewed by React Doctor for commit 53233cc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant