How the GeoLibre Embed System Works: Controlling Maps via @geolibre/embed
The GeoLibre embed system uses a versioned postMessage protocol to let parent pages control iframe-embedded maps through the @geolibre/embed TypeScript library, which handles origin validation, request correlation, and typed API methods like setView and addLayer.
GeoLibre supports iframe embedding through a secure, bidirectional messaging protocol defined in the open-source repository opengeos/GeoLibre. The @geolibre/embed package provides a lightweight, dependency-free client that abstracts postMessage complexity, offering a fully typed JavaScript API for managing viewport state, layers, and user interactions from external web applications.
The Two-Part Architecture of the GeoLibre Embed System
The embed system consists of complementary components working across the iframe boundary to establish secure, stateful communication.
The iframe Application (GeoLibre App)
When built with an embed allow-list (GEOLIBRE_EMBED_ORIGINS or VITE_GEOLIBRE_EMBED_ORIGINS), the GeoLibre application initializes a message router that validates incoming message origins and protocol versions against EMBED_API_VERSION. The router emits lifecycle events including ready, viewChanged, and selectionChanged, and acknowledges every command with an ack response to confirm receipt.
The Host-Side Client (@geolibre/embed)
The @geolibre/embed package implements the host-side protocol logic in packages/embed/src/index.ts. It verifies the iframe's origin before transmission using validOrigin, generates unique request IDs for command tracking, and maintains a pending Map to correlate asynchronous responses. The client resolves Promises when acknowledgements arrive or rejects them on timeout, enabling multiple simultaneous commands without race conditions.
Implementing the Connection with @geolibre/embed
Establishing the Handshake and Basic Navigation
The connect function serves as the entry point, performing the initial handshake by awaiting the iframe's ready event before returning a configured GeoLibreEmbedClient instance.
import { connect } from "@geolibre/embed";
const iframe = document.querySelector("iframe#geolibre") as HTMLIFrameElement;
// Connect – resolves when the iframe reports `ready`
const map = await connect(iframe, {
origin: "https://maps.example.com", // exact origin of the GeoLibre app
timeoutMs: 15000, // optional – how long to wait for `ready`
requestTimeoutMs: 10000, // optional – per‑command timeout
});
// Fly to a location
await map.setView({ center: [-122.44, 37.76], zoom: 12 });
// Hide a layer
await map.setLayerVisibility("roads", false);
The connect implementation in packages/embed/src/index.ts (lines 103-108 and 149-155) handles the ready handshake and instantiates the typed client interface.
Managing Dynamic Layers and Exports
The client supports complex layer operations and image generation through the GeoLibreEmbedClient interface defined in packages/embed/src/index.ts (lines 86-90).
// Add a GeoJSON layer
const layerId = await map.addLayer({
id: "my-points",
name: "Points of Interest",
type: "geojson",
source: { type: "geojson", data: myGeoJson },
visible: true,
opacity: 0.8,
});
// Export the current view as a PNG data‑URL
const pngDataUrl = await map.exportImage();
download(pngDataUrl, "map.png");
Subscribing to Embedded Map Events
Host applications can react to user interactions inside the iframe through the event subscription API:
// React to a feature selection made inside the embedded map
const unsubscribe = map.on("selectionChanged", ({ layerId, featureIds }) => {
console.log("User selected", featureIds, "in layer", layerId);
// e.g., show details in a sidebar
});
// Later, when the iframe is removed
unsubscribe(); // stop listening
map.disconnect(); // clean up pending promises and event listeners
The on method and disconnect logic (lines 68-90 in packages/embed/src/index.ts) manage event listener registration and cleanup of the pending promises Map.
Protocol Versioning and Security in GeoLibre Embeds
The GeoLibre embed system uses EMBED_API_VERSION = 2 to ensure compatibility between independently evolving host and iframe code. If the iframe receives a message with an unrecognized version, it ignores the payload, causing the host client to timeout safely rather than execute unknown commands. This version check, combined with strict origin validation against the embed allow-list, prevents cross-origin attacks while maintaining API stability.
Summary
- The GeoLibre embed system enables cross-origin iframe control through a versioned
postMessageprotocol withEMBED_API_VERSION = 2. - The
@geolibre/embedpackage inpackages/embed/src/index.tsprovides a TypeScript client that handles origin validation, request correlation via apendingMap, and timeout management. - Use the
connectfunction to establish the handshake and obtain a typedGeoLibreEmbedClientfor executing commands likesetView,addLayer, andexportImage. - The client generates unique request IDs for each command, allowing multiple in-flight requests to resolve independently when
ackmessages return from the iframe. - Event subscriptions through
onand cleanup viadisconnectensure proper resource management when embedding contexts are destroyed. - Security relies on configurable embed origins (
GEOLIBRE_EMBED_ORIGINSorVITE_GEOLIBRE_EMBED_ORIGINS) and protocol versioning to prevent unauthorized cross-origin access.
Frequently Asked Questions
How does the @geolibre/embed package handle multiple simultaneous commands?
The client generates a unique request ID for each command and stores the associated Promise in a pending Map. When the iframe returns an ack message containing that ID, the client resolves the corresponding Promise. This allows multiple commands to be in flight simultaneously without blocking the host application, with each command respecting the configured requestTimeoutMs limit according to the implementation in packages/embed/src/index.ts.
What security measures does the GeoLibre embed system implement?
Security relies on two mechanisms: origin validation and protocol versioning. The host verifies the iframe's origin before sending any message through the validOrigin check (lines 97-101), while the iframe only accepts messages from origins listed in GEOLIBRE_EMBED_ORIGINS. Additionally, the EMBED_API_VERSION field ensures that incompatible messages are ignored rather than executed, preventing API mismatches from causing errors.
Can I use the embed system without the @geolibre/embed package?
Yes, but it requires manual implementation of the postMessage protocol. You must handle origin validation, generate and track request IDs, manage timeout logic for acknowledgements, and maintain the exact message payload shapes defined in the protocol (such as Viewport, AddLayerSpec, and LayerSummary). The @geolibre/embed package eliminates this boilerplate by providing a type-safe abstraction that is synchronized with the GeoLibre application's protocol version.
What happens if the iframe takes too long to respond?
The connect function accepts a timeoutMs parameter for the initial handshake, while individual commands respect requestTimeoutMs. If either timeout elapses without receiving the expected ready event or command acknowledgement (ack), the Promise rejects with a timeout error. This prevents host applications from waiting indefinitely when the embedded map encounters errors or network issues.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →