# How the GeoLibre Embed System Works: Controlling Maps via @geolibre/embed

> Discover how the GeoLibre embed system uses postMessage and the @geolibre/embed library to control iframe maps. Learn about origin validation and typed API methods like setView.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-05

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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.

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/packages/embed/src/index.ts) (lines 86-90).

```typescript
// 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:

```typescript
// 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`](https://github.com/opengeos/GeoLibre/blob/main/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 `postMessage` protocol with `EMBED_API_VERSION = 2`.
- The `@geolibre/embed` package in [`packages/embed/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/embed/src/index.ts) provides a TypeScript client that handles origin validation, request correlation via a `pending` Map, and timeout management.
- Use the **`connect`** function to establish the handshake and obtain a typed `GeoLibreEmbedClient` for executing commands like `setView`, `addLayer`, and `exportImage`.
- The client generates unique request IDs for each command, allowing multiple in-flight requests to resolve independently when `ack` messages return from the iframe.
- Event subscriptions through **`on`** and cleanup via **`disconnect`** ensure proper resource management when embedding contexts are destroyed.
- Security relies on configurable embed origins (`GEOLIBRE_EMBED_ORIGINS` or `VITE_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`](https://github.com/opengeos/GeoLibre/blob/main/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.