> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go.gbgplc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedding

> React, framework-agnostic, and SSR patterns for displaying journey iframes.

This guide covers how to embed the bridge-connected iframe in your web application — including React, vanilla JavaScript / Vue / Angular / Svelte, server-rendered frameworks, and multi-iframe pages.

## React integration

For React apps, `BridgeHostProvider` is the recommended path: wrap your iframe-rendering subtree, track the iframe element in state, and use hooks to interact with the bridge.

### Using BridgeHostProvider

```tsx theme={null}
import { BridgeHostProvider, useBridgeCapability } from "@gbg/go-bridge-web-react";
import { useState } from "react";

export function JourneyScreen({ journeyUrl }: { journeyUrl: string }) {
  const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);
  const allowedOrigins = [new URL(journeyUrl).origin];

  return (
    <BridgeHostProvider
      iframe={iframe}
      allowedOrigins={allowedOrigins}
      hostVersion="1.0.0"
      capabilities={{
        "camera.document": { supported: true, version: "1.0" },
      }}
    >
      <DocumentCaptureHandler />
      <iframe ref={setIframe} src={journeyUrl} allow="camera" />
    </BridgeHostProvider>
  );
}

function DocumentCaptureHandler() {
  useBridgeCapability("camera.document.capture", {
    handle: async (request, responder) => {
      const image = await captureDocument();
      responder.success({ image });
    },
  });
  return null;
}
```

The provider builds the underlying `BridgeHost` once `iframe` transitions from `null` to a real element. Children using the hooks see `host` come online at that point.

### Reading bridge state in components

`useBridgeHost()` returns the current `BridgeHost`, or `null` while the iframe isn't attached. Use it to read observable state and call imperative methods.

```tsx theme={null}
import { useBridgeHost } from "@gbg/go-bridge-web-react";

function MessageCountBadge() {
  const host = useBridgeHost();
  const count = host?.receivedMessages.length ?? 0;
  return <span className="badge">{count}</span>;
}
```

<Note>
  `host.receivedMessages` and `host.pendingRequests` are arrays, not observables. They mutate in place as messages arrive — React will not re-render the badge component unless something else triggers a render. For reactive UI, subscribe to events via [`useBridgeHostEvent`](/docs/go-v2/developer-integration/sdks/web/api-reference#usebridgehostevent) or to errors via [`useBridgeHostError`](/docs/go-v2/developer-integration/sdks/web/api-reference#usebridgehosterror), and lift state to your own components.
</Note>

### Listening for events

Subscribe to specific event actions emitted by the journey using `useBridgeHostEvent`:

```tsx theme={null}
import { useBridgeHostEvent } from "@gbg/go-bridge-web-react";
import { useNavigate } from "react-router-dom";

function JourneyCompletionRouter() {
  const navigate = useNavigate();

  useBridgeHostEvent("journey.completed", (data) => {
    navigate(`/success?journey=${data.journeyId}`);
  });

  useBridgeHostEvent("journey.failed", (data) => {
    navigate(`/failure?reason=${data.reason}`);
  });

  return null;
}
```

The hook subscribes on mount and unsubscribes on unmount. Stable listener identities (via `useCallback`) prevent re-subscription on every render.

### Surfacing errors

Subscribe to internal SDK errors with `useBridgeHostError` and lift to React state if you need to render them:

```tsx theme={null}
import { useBridgeHostError } from "@gbg/go-bridge-web-react";
import { useState } from "react";

function ErrorBanner() {
  const [error, setError] = useState<Error | null>(null);
  useBridgeHostError(setError);

  if (!error) return null;
  return <div className="error-banner">{error.message}</div>;
}
```

### Dynamic journey URLs

Two patterns for changing journey URLs.

**Pattern A — Update `iframe.src`** (preserves the `BridgeHost`):

```tsx theme={null}
function DynamicJourney() {
  const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);
  const [journeyUrl, setJourneyUrl] = useState("https://journey.example.com/step1");

  return (
    <BridgeHostProvider
      iframe={iframe}
      allowedOrigins={["https://journey.example.com"]}
      hostVersion="1.0.0"
    >
      <iframe ref={setIframe} src={journeyUrl} allow="camera" />
      <button onClick={() => setJourneyUrl("https://journey.example.com/step2")}>
        Next step
      </button>
    </BridgeHostProvider>
  );
}
```

The host's `allowedOrigins` snapshot still applies — both URLs must share an origin already in the allowlist.

**Pattern B — Remount the provider** (rebuilds the host with fresh config):

```tsx theme={null}
function MultiOriginJourney() {
  const [origin, setOrigin] = useState("https://journey-eu.example.com");
  return (
    <BridgeHostProvider
      key={origin}
      iframe={iframe}
      allowedOrigins={[origin]}
      hostVersion="1.0.0"
    >
      {/* ... */}
    </BridgeHostProvider>
  );
}
```

Changing `key` discards the existing host (calls `detach`) and rebuilds with the new config. Use this when `allowedOrigins`, `hostVersion`, or `capabilities` need to change at runtime.

### Multiple iframes on one page

Each `BridgeHostProvider` is independent and owns its own `BridgeHost`. Use this pattern when you have two side-by-side journeys, or when you split capture flows into separate iframes.

```tsx theme={null}
function SplitView() {
  const [iframeA, setIframeA] = useState<HTMLIFrameElement | null>(null);
  const [iframeB, setIframeB] = useState<HTMLIFrameElement | null>(null);

  return (
    <div className="split">
      <BridgeHostProvider iframe={iframeA} allowedOrigins={[...]} hostVersion="1.0.0">
        <DocumentCaptureHandler />
        <iframe ref={setIframeA} src={urlA} allow="camera" />
      </BridgeHostProvider>
      <BridgeHostProvider iframe={iframeB} allowedOrigins={[...]} hostVersion="1.0.0">
        <SelfieCaptureHandler />
        <iframe ref={setIframeB} src={urlB} allow="camera" />
      </BridgeHostProvider>
    </div>
  );
}
```

The two hosts are isolated — events and capabilities don't cross between them.

## Framework-agnostic integration

If you're not using React, instantiate `BridgeHost` directly from the core `@gbg/go-bridge-web` package.

### Vanilla JavaScript

```typescript theme={null}
import { BridgeHost } from "@gbg/go-bridge-web";

const iframe = document.getElementById("journey-frame") as HTMLIFrameElement;
const journeyUrl = "https://journey.example.com";
iframe.src = journeyUrl;

const host = new BridgeHost({
  iframe,
  allowedOrigins: [new URL(journeyUrl).origin],
  hostVersion: "1.0.0",
  capabilities: {
    "camera.document": { supported: true, version: "1.0" },
  },
});

host.registerHandler("camera.document.capture", {
  handle: async (request, responder) => {
    const image = await captureDocument();
    responder.success({ image });
  },
});

// When the journey screen is removed from the page:
function teardown() {
  host.detach();
}
```

With direct usage you own the lifecycle. Call `host.detach()` when the iframe is removed or the screen unmounts to release the `window` `message` listener.

### Vue 3 (Composition API)

```vue theme={null}
<script setup lang="ts">
import { BridgeHost } from "@gbg/go-bridge-web";
import { ref, onMounted, onUnmounted } from "vue";

const props = defineProps<{ journeyUrl: string }>();
const iframeRef = ref<HTMLIFrameElement | null>(null);
let host: BridgeHost | null = null;

onMounted(() => {
  if (!iframeRef.value) return;
  host = new BridgeHost({
    iframe: iframeRef.value,
    allowedOrigins: [new URL(props.journeyUrl).origin],
    hostVersion: "1.0.0",
    capabilities: {
      "camera.document": { supported: true, version: "1.0" },
    },
  });
  host.registerHandler("camera.document.capture", {
    handle: async (_request, responder) => {
      const image = await captureDocument();
      responder.success({ image });
    },
  });
});

onUnmounted(() => {
  host?.detach();
});
</script>

<template>
  <iframe ref="iframeRef" :src="props.journeyUrl" allow="camera" />
</template>
```

### Angular

```typescript theme={null}
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy, Input } from "@angular/core";
import { BridgeHost } from "@gbg/go-bridge-web";

@Component({
  selector: "app-journey",
  template: `<iframe #journey [src]="journeyUrl" allow="camera"></iframe>`,
})
export class JourneyComponent implements AfterViewInit, OnDestroy {
  @Input() journeyUrl!: string;
  @ViewChild("journey") iframeRef!: ElementRef<HTMLIFrameElement>;
  private host: BridgeHost | null = null;

  ngAfterViewInit() {
    this.host = new BridgeHost({
      iframe: this.iframeRef.nativeElement,
      allowedOrigins: [new URL(this.journeyUrl).origin],
      hostVersion: "1.0.0",
      capabilities: { "camera.document": { supported: true, version: "1.0" } },
    });
    this.host.registerHandler("camera.document.capture", {
      handle: async (_request, responder) => {
        const image = await captureDocument();
        responder.success({ image });
      },
    });
  }

  ngOnDestroy() {
    this.host?.detach();
  }
}
```

### Svelte

```svelte theme={null}
<script lang="ts">
  import { BridgeHost } from "@gbg/go-bridge-web";
  import { onMount, onDestroy } from "svelte";

  export let journeyUrl: string;
  let iframeEl: HTMLIFrameElement;
  let host: BridgeHost | null = null;

  onMount(() => {
    host = new BridgeHost({
      iframe: iframeEl,
      allowedOrigins: [new URL(journeyUrl).origin],
      hostVersion: "1.0.0",
      capabilities: { "camera.document": { supported: true, version: "1.0" } },
    });
    host.registerHandler("camera.document.capture", {
      handle: async (_request, responder) => {
        const image = await captureDocument();
        responder.success({ image });
      },
    });
  });

  onDestroy(() => host?.detach());
</script>

<iframe bind:this={iframeEl} src={journeyUrl} allow="camera" />
```

## Server-side rendering (Next.js, Remix, SvelteKit)

`BridgeHost` requires `window`, `MessageEvent`, and DOM access — none of which exist on the server. The SDK must run client-side only.

### Next.js (App Router)

Mark the journey component with `"use client"` and let it render only on the client:

```tsx theme={null}
// app/journey/JourneyClient.tsx
"use client";

import { BridgeHostProvider } from "@gbg/go-bridge-web-react";
import { useState } from "react";

export function JourneyClient({ journeyUrl }: { journeyUrl: string }) {
  const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);
  return (
    <BridgeHostProvider
      iframe={iframe}
      allowedOrigins={[new URL(journeyUrl).origin]}
      hostVersion="1.0.0"
      capabilities={{ "camera.document": { supported: true, version: "1.0" } }}
    >
      <iframe ref={setIframe} src={journeyUrl} allow="camera" />
    </BridgeHostProvider>
  );
}
```

```tsx theme={null}
// app/journey/page.tsx (server component)
import { JourneyClient } from "./JourneyClient";

export default async function JourneyPage() {
  const journeyUrl = await getJourneyUrl(); // server-side: fetch tokenized URL
  return <JourneyClient journeyUrl={journeyUrl} />;
}
```

The server component handles the [token exchange](/docs/go-v2/developer-integration/sdks/web/journey-url) (calling `@gbg/go-core` server-side), and only the journey URL crosses to the client.

### Next.js (Pages Router) and other SSR frameworks

If you can't mark a component as client-only, gate the bridge setup on a `useEffect` so it never runs during server render:

```tsx theme={null}
import { useEffect, useState } from "react";

export function JourneyView({ journeyUrl }: { journeyUrl: string }) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);

  if (!mounted) return null; // skip SSR

  return <ActualJourney journeyUrl={journeyUrl} />;
}
```

For Vue / Nuxt / SvelteKit, use the equivalent client-only gate (`<ClientOnly>` in Nuxt, `onMount` in SvelteKit).

## Lifecycle considerations

`BridgeHostProvider` handles most of this for you in React. For non-React hosts, you own the lifecycle. Either way, these rules apply.

### Set up handlers before the journey requests them

If a request arrives before its handler is registered, it goes to `pendingRequests`. The journey will be left waiting (or time out) until you respond manually via `host.respond(...)`. Register handlers before the iframe loads — in React, mount the handler components inside the provider; in vanilla JS, call `registerHandler` immediately after `new BridgeHost(...)`.

### Detach when the iframe is removed

Call `host.detach()` (or unmount the provider) when the iframe goes away. Failing to detach leaves the `window` `message` listener attached and the `BridgeHost` instance reachable from a closure — a small leak that compounds across navigation.

### Config snapshot at attach time

`BridgeHostProvider` snapshots `allowedOrigins`, `hostVersion`, and `capabilities` when the underlying host is built. To apply runtime changes:

* Use a `key`-based remount to discard and rebuild.
* For capabilities specifically, use `host.registerCustomCapability(id, version, handler)` via `useBridgeHost()` — it adds to the effective capability map at runtime without rebuilding the host.

### iframe.src and outbound message timing

`HostIframeChannel` resolves `targetOrigin` from `iframe.src` at send time. If `iframe.src` is empty when you call `host.sendEvent(...)` or `host.respond(...)`, the outbound is dropped with a `console.warn`. In React this is normally automatic (the iframe renders with `src` set). In vanilla JS, set `iframe.src` before the first outbound send.

## Next steps

* [Messaging](/docs/go-v2/developer-integration/sdks/web/messaging) — Send events, handle requests, and observe the message flow.
* [Capability Handling](/docs/go-v2/developer-integration/sdks/web/capability-handling) — Declare capabilities, register handlers, and handle dynamic capability state.
* [Security](/docs/go-v2/developer-integration/sdks/web/security) — CSP, origin allowlist, transport, and token exchange.
* [API Reference](/docs/go-v2/developer-integration/sdks/web/api-reference) — Detailed reference for `BridgeHostProvider`, hooks, and `BridgeHost`.
