feat(embed): add a versioned postMessage API for host pages - #1468
Conversation
URL parameters configure an embed once, at load. A host page that frames
GeoLibre had no way to keep talking to the map: every interaction meant
reloading the iframe with a new `?url=`, throwing away the analyst's
session, and nothing the user did inside the map was visible outside it.
Add an opt-in runtime protocol on top of the existing embed plumbing.
Host to app: `loadProject`, `setView`, `highlightFeature`, `openTool`
(the runtime twin of `?tool=`). App to host: `ready`, `ack`,
`projectLoaded`, `selectionChanged`, `viewChanged` (throttled),
`toolCompleted`, `serverFileWritten`. Every message is versioned
`{v, type, payload}`, and app messages carry `source: "geolibre"` so a
host can filter its own postMessage traffic.
The API is off by default. It activates only when the deployment names
the origins it trusts (`GEOLIBRE_EMBED_ORIGINS` on the Docker image,
`VITE_GEOLIBRE_EMBED_ORIGINS` at build time), so a public deployment can
never be driven by whoever frames it. The allowlist is enforced in both
directions, and setting it also narrows the existing Jupyter/`?embed=1`
project and scripting bridges to those origins.
Fixes #1462
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughGeoLibre adds an opt-in, versioned ChangesEmbedded postMessage API
Estimated code review effort: 4 (Complex) | ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 PR preview
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/geolibre-desktop/src/hooks/useEmbedApi.ts`:
- Around line 122-134: The applyHighlight function currently acknowledges
success when resolveHighlightIds finds no features, potentially clearing the
selection for source-backed layers. Update applyHighlight to locate or rehydrate
matching features from the layer’s MapLibre source when layer.geojson is
unavailable, and reject with an unsuccessful acknowledgement when no target
features can be resolved; only call selectFeatures, highlightFeature, and return
success after matches are found.
In `@docs/user-guide/embedding.md`:
- Around line 102-105: Update the embedding documentation’s message-delivery
statement to qualify that outbound messages target listed origins, but may use
"*" when the allowlist contains the wildcard or no allowlist is configured.
Preserve the existing explanation that unlisted inbound origins are ignored.
In `@tests/embed-api.test.ts`:
- Around line 269-271: Update the “requires an id” test to match the surrounding
error-case assertions by storing or reusing the parsed request and checking it
with a truthiness guard plus the `"error" in parsed` pattern, rather than
calling hasOwnProperty directly.
- Around line 275-279: Update the features array annotation in the test to use
Feature<null>[] so its null geometries match the declared type. Align any nearby
related feature-array annotations that also contain null geometries, while
leaving feature data unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e99453dd-0240-47bc-98c4-f43931b683b2
📒 Files selected for processing (11)
Dockerfileapps/geolibre-desktop/src/components/layout/DesktopShell.tsxapps/geolibre-desktop/src/hooks/embedHost.tsapps/geolibre-desktop/src/hooks/useEmbedApi.tsapps/geolibre-desktop/src/hooks/useEmbedBridge.tsapps/geolibre-desktop/src/lib/embed-api.tsdocker/entrypoint.shdocs/features.mddocs/getting-started.mddocs/user-guide/embedding.mdtests/embed-api.test.ts
…ng them
Explicit `featureId`/`featureIds` were passed through unchecked, so a
`highlightFeature` naming an id no feature carries — a typo, or any layer
whose features live in its MapLibre source rather than `layer.geojson` —
selected a phantom id, drew no highlight, and still answered `ack {ok:
true}`. Resolve ids against the layer's features under the same
`String(feature.id ?? index)` convention the map controller uses, and
reject a request that names features but resolves to none, leaving the
user's existing selection untouched. A request naming nothing is still
the documented "clear the highlight" form.
Also fix the docs claim that outbound messages are never addressed to
`*` (they are, before the handshake, when the `*` wildcard is
configured), type the null-geometry test fixtures as `Feature<null>[]`
(`Feature` defaults its geometry parameter to `Geometry`), and match the
surrounding assertion style in one test.
|
This is great. Looking at the code closer now, i had the impression that the embedding for jupyter books already does something similar so parts of this might be a little redundant to it. I will test and prepare a PR with the findings. |
Fixes #1462
What this adds
URL parameters (
?url=,?maponly,?tool=) configure an embed once, at load. A host page that frames GeoLibre had no way to keep talking to a live map: every interaction meant reloading the iframe, throwing away the analyst's session, and nothing the user did inside the map was visible to the host.This adds an opt-in, versioned
postMessageprotocol layered on the existing embed plumbing.Host to GeoLibre
loadProject{ url }.geolibre.jsonwithout reloading the iframesetView{ bbox }or{ center, zoom, bearing, pitch, duration }highlightFeature{ layerId, featureId | featureIds | filter, fit }filtermatches propertiesopenTool{ id, params }paramspre-filled (runtime twin of?tool=)GeoLibre to host:
ready,ack(for any message sent with arequestId),projectLoaded,selectionChanged,viewChanged(throttled to ~4/s),toolCompleted,serverFileWritten.Every message is
{ v: 1, type, payload }; app messages also carrysource: "geolibre"so a host can filter its own traffic.Security
The API is off by default. It activates only when the deployment names the origins it trusts:
docker run -e GEOLIBRE_EMBED_ORIGINS="https://portal.example.com" ghcr.io/opengeos/geolibre:latest(or
VITE_GEOLIBRE_EMBED_ORIGINSat build time for a static deployment). A public deployment can therefore never be driven by whoever frames it. The allowlist is enforced in both directions: unlisted senders are ignored, and outbound messages are addressed to a listed origin, never*. The entrypoint validates each entry and fails the boot on a malformed one.Setting the allowlist also narrows the existing Jupyter /
?embed=1project and scripting bridges to those origins, closing the "any framing parent is trusted" gap for deployments that can name their hosts. With no allowlist configured, nothing about those bridges changes (the Jupyter widget's host origin is arbitrary and cannot be listed in advance).Verification
Verified against the real app: a harness page served from a second origin framing the dev server, with a project of three field polygons.
loadProjectswapped the project in place:projectLoaded+ack ok, no reloadsetViewwith a bbox and with center/zoom/pitch, both acked and animatedhighlightFeaturebyfilter: {crop: "corn"}highlighted 2 of 3 features, fitted to them, and reportedselectionChanged {featureIds: ["f1","f3"]}; byfeatureId; and{layerId}alone cleared itopenTool aspectwith{z_factor: 2}opened the Whitebox dialog preselected and pre-filledloadProject(javascript:URL) was refused withack {ok: false, error}rather than acted ontoolCompleted {id: "centroids", status: "success", outputLayerNames: ["Centroids"]}readyand itsloadProjectwas ignored (map stayed empty)serverFileWrittenis derived from the same processing-history entry'soutputPathand was not exercised live (it needs a sidecar file-based tool).39 new unit tests cover origin parsing/allowlisting, envelope validation, every verb payload, and highlight resolution. Full frontend suite (4000 tests), production build, and pre-commit all pass.
Docs
docs/user-guide/embedding.md: new "Talking to the map at runtime" section with the message reference and a complete host-page exampledocs/getting-started.md: the Docker env vardocs/features.md: one line under DeploymentSummary by CodeRabbit
postMessageembed API to control embedded GeoLibre maps (load projects, change view, highlight features, open tools).GEOLIBRE_EMBED_ORIGINS(Docker/runtime) andVITE_GEOLIBRE_EMBED_ORIGINS(build), with stricter origin validation.