> ## 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.

# Concepts

> Architecture, message protocol, capability model, and design rationale behind GBGBridge for Android.

This document explains the architecture and design principles behind the GBGBridge Android SDK. Understanding these concepts will help you integrate effectively and make informed decisions about how to structure your host application.

## Overview

GBGBridge solves a fundamental problem: web-based identity verification journeys need access to native device capabilities (camera, NFC, biometrics) that are not available — or are severely limited — in a browser context. Rather than building separate native UIs for every journey variation, GBGBridge lets a single web journey drive the user experience while delegating capability-intensive operations to the native host.

## Key Terminology

| Term                 | Definition                                                                                                                                                                      |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Journey**          | A web-based identity verification flow (e.g., document capture + face match). The journey runs as HTML/JavaScript inside a WebView.                                             |
| **Host**             | The native Android application that embeds the journey WebView. The host provides native capabilities.                                                                          |
| **Bridge**           | The communication layer between the web journey and the native host. Comprises the `window.GBGBridge` JavaScript namespace on the web side and `BridgeHost` on the native side. |
| **Capability**       | A native feature that the host can provide (e.g., `camera.document`, `camera.selfie`). Capabilities can be declared via typed slots (recommended) or in configuration.          |
| **Typed Slot**       | A built-in `CaptureCapability` property on `BridgeHost` (e.g., `documentCapture`, `selfieCapture`). Setting a handler on a slot declares the capability as supported.           |
| **Handler**          | A native Kotlin object implementing `BridgeCapabilityHandler` that fulfills requests for a specific action, or a suspend lambda set on a typed slot.                            |
| **Permission State** | Metadata about the native permission status for a capability (e.g., `granted`, `denied`, `notDetermined`). Reported in capability query responses.                              |
| **Message**          | A structured JSON envelope exchanged between the web journey and the native host. Every message has a type (request, response, or event), a correlation ID, and a payload.      |
| **Action**           | A string identifier for the operation a message relates to (e.g., `"camera.document.capture"`, `"capability.query"`).                                                           |
| **Responder**        | A callback object (`BridgeResponder`) provided to handlers, used to send a response back to the web journey.                                                                    |
| **Bootstrap Script** | A JavaScript snippet injected as each page starts loading that initializes the `window.GBGBridge` namespace and `receive()` function.                                           |

## Architecture

GBGBridge is organized as a small set of collaborating components: a message router (`BridgeHost`), capability handlers, and WebView configuration utilities that wire the native side to the web journey's JavaScript.

### Component Diagram

```mermaid theme={null}
graph TB
    subgraph "Android Host Application"
        BH[BridgeHost<br/>Message Router]
        DC[documentCapture<br/>CaptureCapability Slot]
        SC[selfieCapture<br/>CaptureCapability Slot]
        CH2[Custom Capability<br/>Handler]
        CQH[CapabilityQueryHandler<br/>Built-in]
        DEL[BridgeHostDelegate<br/>Observer]
        CD[CameraDetector<br/>Permission Helper]
    end

    subgraph "WebView Layer"
        BWVC[BridgeWebViewConfigurator<br/>Setup Utility]
        WVC[BootstrapInjectingWebViewClient<br/>Bootstrap Injection]
        WV[WebView]
    end

    subgraph "Web Journey (JavaScript)"
        JS[Journey Code]
        GB[window.GBGBridge]
    end

    DC --> BH
    SC --> BH
    CH2 --> BH
    CQH --> BH
    DEL -.-> BH
    CD -.-> DC
    CD -.-> SC
    BH --> WV
    BWVC --> WV
    WVC --> WV
    WV --> GB
    GB -->|postMessage via JavascriptInterface| BH
    BH -->|evaluateJavascript receive| GB
    JS --> GB
```

Unlike iOS, there is no `BridgeWebView` wrapper type and no WebView factory. You create a standard `WebView` yourself (or via `AndroidView { WebView(it) }` in Compose) and call `host.attach(webView)` — the host configures the WebView internally.

### Layers

1. **Capability Layer** — Typed slots (`CaptureCapability`) and custom capabilities declare what the host supports. `CameraDetector` provides hardware and permission detection.
2. **Routing Layer** — `BridgeHost` receives messages from the WebView, decodes them, and routes requests to typed slot handlers, custom capability handlers, or `BridgeCapabilityHandler` implementations.
3. **Handler Layer** — Typed slots handle capture requests via suspend lambdas and return strongly-typed `CaptureResult` values. The `BridgeCapabilityHandler` interface supports arbitrary request handling.
4. **Transport Layer** — `BridgeWebViewConfigurator` and `BootstrapInjectingWebViewClient` configure the WebView, inject the bootstrap script, and wire up the message channels via a JavaScript interface.
5. **Observation Layer** — `BridgeHostDelegate` lets you observe all inbound and outbound messages, handle unrouted requests, and receive error notifications.

## Message Protocol

All communication uses structured JSON envelopes. The protocol version is `1.0`, and the wire format is identical to the iOS SDK — a web journey that speaks the bridge protocol works unchanged on both platforms.

### Message Envelope

```json theme={null}
{
  "version": "1.0",
  "correlationId": "uuid-string",
  "type": "request | response | event",
  "timestamp": 1700000000000,
  "payload": {
    "action": "camera.document.capture",
    "data": { },
    "status": "success | error | cancelled | unsupported | acknowledged",
    "error": {
      "code": "CAMERA_DENIED",
      "message": "User denied camera access",
      "recoverable": true
    }
  }
}
```

The `timestamp` is epoch milliseconds. Decoding is lenient: fractional timestamp values (as produced by some senders) are accepted and rounded.

### Message Types

| Type       | Direction        | Purpose                                                         |
| ---------- | ---------------- | --------------------------------------------------------------- |
| `request`  | Web → Native     | The web journey asks the host to perform an action.             |
| `response` | Native → Web     | The host sends the result of a request back to the web journey. |
| `event`    | Either direction | An asynchronous notification with no expected response.         |

### Message Flow

```mermaid theme={null}
sequenceDiagram
    participant Web as Web Journey
    participant Bridge as BridgeHost
    participant Handler as CapabilityHandler

    Web->>Bridge: request (capability.query)
    Bridge->>Handler: route to CapabilityQueryHandler
    Handler->>Bridge: respond(success, capabilities)
    Bridge->>Web: response (capability.query, success)

    Web->>Bridge: request (camera.capture)
    Bridge->>Handler: route to CameraHandler
    Note over Handler: Present camera UI,<br/>capture document
    Handler->>Bridge: respond(success, imageData)
    Bridge->>Web: response (camera.capture, success)

    Bridge->>Web: event (journey.progress)
    Note over Web: Update UI based on event
```

### Correlation IDs

Every request has a `correlationId`. When the host responds, it includes the same `correlationId` so the web journey can match responses to their original requests. Events generate their own unique correlation IDs (on Android these take the form `android-event-{uuid}`).

### Response Statuses

| Status         | Meaning                                                              |
| -------------- | -------------------------------------------------------------------- |
| `success`      | The request completed successfully. Check `data` for the result.     |
| `error`        | The request failed. Check `error` for details.                       |
| `cancelled`    | The user cancelled the operation (e.g., dismissed a capture screen). |
| `unsupported`  | The requested capability is not available on this host.              |
| `acknowledged` | The request was received and is being processed asynchronously.      |

## Request Routing

When a message arrives from the web journey:

1. The raw JSON arrives on the WebView render thread and is posted to the main looper before any further processing.
2. `BridgeHost` decodes the JSON envelope into a `BridgeMessage`.
3. The message is appended to `receivedMessages` (observable; the buffer retains the most recent 200 messages).
4. The delegate's `onMessage(host, message)` is called.
5. If the message type is `request`:
   * If a handler is registered for the message's `action`, the handler's `handle(request, responder)` is called.
   * If no handler is registered, the message is added to `pendingRequests` and the delegate's `onUnhandledRequest(host, request)` is called. You can respond later via the lookup `respond(to, status, data, error)` overload.
6. If the message type is `response` or `event`, no routing occurs — it is stored and reported to the delegate.

Handler exceptions are caught by the host: the error is routed to `delegate.onError`, and if the handler had not yet responded, an `error` response with code `HANDLER_FAILURE` is dispatched automatically so the web journey is never left waiting.

```mermaid theme={null}
flowchart TD
    A[Message arrives from WebView] --> B[Post to main looper]
    B --> C[Decode to BridgeMessage]
    C --> D[Append to receivedMessages]
    D --> E[Notify delegate: onMessage]
    E --> F{Message type?}
    F -->|request| G{Handler registered?}
    F -->|response/event| H[Done — stored and observed]
    G -->|Yes| I[Call handler.handle]
    I --> J[Handler calls responder]
    J --> K[Response sent to WebView]
    G -->|No| L[Add to pendingRequests]
    L --> M[Notify delegate: onUnhandledRequest]
```

## Typed Capability Slots

GBGBridge provides typed capability slots as the recommended way to declare and handle well-known capabilities. A typed slot is a `CaptureCapability` instance exposed as a property on `BridgeHost`.

### Built-in Slots

| Slot             | Property               | Capability ID     | Action ID                 |
| ---------------- | ---------------------- | ----------------- | ------------------------- |
| Document Capture | `host.documentCapture` | `camera.document` | `camera.document.capture` |
| Selfie Capture   | `host.selfieCapture`   | `camera.selfie`   | `camera.selfie.capture`   |

### Handler-as-Declaration

Setting a `handler` on a typed slot simultaneously declares support and provides the implementation:

```kotlin theme={null}
// This single line declares the capability AND provides the handler
host.documentCapture.handler = { request ->
  host.documentCapture.awaitCompletion()
}
```

When `handler` is set and `isEnabled` is `true`, the slot reports `isSupported = true`. This means the capability query response automatically reflects the current state — no separate configuration step is needed.

### Automatic Features

Typed slots provide several features automatically:

* **Capability query integration** — Supported slots appear in `capability.query` responses with version and permission state metadata.
* **Result encoding** — Handlers return strongly-typed `CaptureResult` values. The SDK encodes them into the bridge protocol format (base64 image data, dimensions, etc.).
* **Busy rejection** — If a request arrives while another is already active, the slot responds with an error (code `BUSY`, recoverable) automatically.
* **Compose reactivity** — `activeRequest` is a `StateFlow`, so UI can react to capture requests: call `collectAsState()` in Compose, or `launchIn` a lifecycle scope in the View system.

### Permission State

Each typed slot has a `permissionState` property that can be populated using `CameraDetector`:

```kotlin theme={null}
val camera = CameraDetector.check(context)
host.documentCapture.permissionState = camera.permissionState
host.selfieCapture.permissionState = camera.permissionState
```

This information is included in the `capability.query` response, allowing the web journey to detect permission issues before attempting capture.

Note that `CameraDetector` reports only `GRANTED` or `NOT_DETERMINED` — Android cannot distinguish a never-asked permission from a permanently-denied one without integrator-held state. After running your own permission flow, set the richer `DENIED` or `RESTRICTED` states on the slot yourself.

## Capability Negotiation

Before attempting to use a native feature, the web journey sends a `capability.query` request. GBGBridge includes a built-in handler (`CapabilityQueryHandler`) that responds automatically.

When using the `BridgeHost(hostVersion)` convenience constructor, the response is built dynamically from typed slots and runtime-registered custom capabilities. When using `BridgeHost(configuration, capabilitiesProvider)`, the static `BridgeConfiguration` map — or the dynamic `capabilitiesProvider`, which is re-evaluated on every read — is merged in as well. On collision, typed slots with a handler take precedence over configuration entries, which take precedence over runtime-registered custom capabilities.

This pattern allows the web journey to:

* **Adapt its UI** — Show or hide steps based on available capabilities.
* **Prevent errors** — Avoid requesting capabilities that aren't supported.
* **Check permissions** — Detect whether native permissions have been granted before attempting operations.
* **Degrade gracefully** — Fall back to alternative flows when capabilities are missing.

### Query Response Format

```json theme={null}
{
  "environment": "android",
  "hostVersion": "1.0.0",
  "capabilities": {
    "camera.document": {
      "supported": true,
      "version": "1.0",
      "permissionState": "granted"
    },
    "camera.selfie": {
      "supported": true,
      "version": "1.0",
      "permissionState": "notDetermined"
    }
  }
}
```

For each capability, `supported` and `version` are always emitted (`version` is JSON `null` when unset). The `permissionState` field is included only when the typed slot or configuration provides it.

See the [Capability Handling Guide](/docs/go-v2/developer-integration/sdks/android/capability-handling) for detailed patterns including typed slots, custom capabilities, and environment-specific behavior.

## Transport Mechanism

The bridge uses Android's two standard WebView communication channels: a JavaScript interface for inbound messages and `evaluateJavascript` for outbound messages.

### Web → Native (Incoming)

The web journey calls:

```javascript theme={null}
window.GBGBridge.postMessage(JSON.stringify(message));
```

The `GBGBridge` object is installed by `BridgeHost` via `addJavascriptInterface` when you call `attach()`. The interface accepts a JSON string (not an object).

This is the load-bearing platform difference: on iOS the same call is `window.webkit.messageHandlers.gbgBridge.postMessage(messageObject)`. Web code targeting both platforms must branch, for example:

```javascript theme={null}
if (window.GBGBridge?.postMessage) {
  // Android
  window.GBGBridge.postMessage(JSON.stringify(message));
} else if (window.webkit?.messageHandlers?.gbgBridge) {
  // iOS
  window.webkit.messageHandlers.gbgBridge.postMessage(message);
}
```

Incoming messages arrive on the WebView render thread and are posted to the main looper before any handler or delegate code runs. Stale messages from a detached or replaced attach session are dropped.

### Native → Web (Outgoing)

The host evaluates JavaScript on the WebView:

```javascript theme={null}
window.GBGBridge.receive(json);
```

The `receive()` function is established by the bootstrap script that `BootstrapInjectingWebViewClient` injects when each page starts loading.

### Bootstrap Script

The default bootstrap script is:

```javascript theme={null}
window.GBGBridge = window.GBGBridge || {};
window.GBGBridge.receive = window.GBGBridge.receive || function(){};
```

This creates a no-op `receive()` function alongside the native `postMessage` interface. The web journey replaces `receive()` with its own implementation once loaded.

You can supply a custom bootstrap script via `BridgeConfiguration.bootstrapScript` if your web journey requires additional initialization. If you pass your own `BootstrapInjectingWebViewClient` subclass to `attach()`, the configuration's script is ignored — the client owns its script, so pass it to the subclass constructor instead, and call `super.onPageStarted` to keep the injection.

Bootstrap timing diverges from iOS: iOS uses `WKUserScript` at document start, which is guaranteed to run before any page JavaScript. Android injects via `evaluateJavascript` in `onPageStarted` (main frame only), which is best-effort — a head script that synchronously calls `window.GBGBridge.receive` could in principle race the injection.

## Threading Model

`BridgeHost` is a main-thread-only class (the Android analogue of iOS's `@MainActor`). This is enforced in three layers:

* **`@MainThread` annotations** — Lint flags off-main calls at build time.
* **Main-looper posting** — Inbound JavaScript messages arrive on the WebView render thread and are posted to the main looper before any decoding, handler, or delegate code runs.
* **Runtime assertion** — Every public state-mutating method asserts the main thread at runtime and throws `IllegalStateException` when called off-main, so violations fail loudly at the call site.

Two further rules apply to handlers and responders:

* `BridgeCapabilityHandler.handle(request, responder)` is **synchronous** (unlike iOS's `async handle`). For asynchronous work, retain the responder, do the work, hop back to the main thread, then call `responder.respond(...)`.
* `BridgeResponder.respond()` is main-thread-only and should be called exactly once — subsequent calls are no-ops. To hop back to the main thread use `withContext(Dispatchers.Main)`, `Handler(Looper.getMainLooper()).post { }`, or `runOnUiThread { }`. (This diverges from iOS, where the responder may be called from any thread.)

Typed slot handlers are suspend lambdas launched on `Dispatchers.Main`, so they can suspend freely (e.g., on `awaitCompletion()`) and resume on the main thread.

## Host Lifecycle

Unlike iOS, where ARC tears the bridge down automatically, Android requires explicit lifecycle calls on `BridgeHost`. There are three: `attach()`, `detach()`, and `dispose()`.

### Attach

`attach(webView, client = null)` configures the WebView (JavaScript and DOM storage enabled, bootstrap-injecting WebViewClient installed), registers the `GBGBridge` JavaScript interface, and starts routing messages. Attaching calls the configurator internally — do not call `BridgeWebViewConfigurator.configure()` yourself beforehand, and set any custom `WebChromeClient` after `attach()`.

### Detach

`detach()` cancels any in-flight typed-slot captures, removes the JavaScript interface, and clears the per-session state: the response dedupe tracking and the `pendingRequests` and `receivedMessages` buffers. This diverges from iOS, which preserves the message buffers across detach — Android clears them so a re-attach starts with clean observable lists rather than ghost entries in Compose UIs. `detach()` is idempotent.

Sending while detached also diverges from iOS: `respond` and `sendEvent` with no WebView attached still fire `delegate.onMessageSent` (as an intent trace) and then drop the message silently at the transport — no `lastError` is recorded, whereas iOS sets `lastError = "WebView not attached"`.

### Dispose

`dispose()` is an Android-only terminal teardown with no iOS equivalent: it detaches and cancels the typed slots' coroutine scopes. After `dispose()`, state-mutating methods (`attach`, `register`, `unregister`, `registerCustomCapability`, both `respond` overloads, `sendEvent`) throw `IllegalStateException`. Still safe after dispose: `detach()`, `dispose()` itself, `clearError()`, all getters (which return empty or null), and setting `delegate`.

Call `dispose()` from `onDestroy` or `onCleared`, and wrap it defensively — `removeJavascriptInterface` can throw on a WebView that is already shutting down:

```kotlin theme={null}
override fun onCleared() {
  try {
    host.dispose()
  } catch (e: Exception) {
    Log.w("Bridge", "Bridge teardown failed", e)
  }
  super.onCleared()
}
```

## Design Rationale

### Why a message protocol instead of direct method calls?

A message protocol decouples the web journey from the native host. The web journey doesn't need to know whether it's running in an Android app, an iOS app, or an iframe — it sends the same messages regardless. This also allows the protocol to evolve without breaking existing integrations.

### Why declare capabilities upfront?

Capability declaration serves two purposes: it lets the web journey adapt before hitting a dead end, and it gives the host application explicit control over what features are exposed. A host app might have camera hardware but choose not to expose camera capture for a particular journey.

### Why typed capability slots?

Typed slots eliminate a class of integration errors. With the configuration-based approach, integrators had to keep capability declarations and handler registrations in sync manually — using matching string keys. Typed slots make this impossible to get wrong: setting a handler declares support in a single step. They also provide automatic result encoding, busy rejection, and permission state reporting.

### Why is BridgeHost main-thread-only?

Android WebView APIs must be accessed on the main thread. Since `BridgeHost` interacts directly with the WebView for both receiving and sending messages, constraining it to the main thread — with lint, looper posting, and runtime assertions — eliminates a class of threading bugs and makes violations fail loudly with `IllegalStateException` instead of corrupting WebView state.

### Why is the delegate weakly referenced?

`BridgeHost.delegate` is backed by a `WeakReference`, so the host never keeps your delegate (and anything it captures — an Activity, Fragment, or Compose scope) alive. The trade-off: you must hold a strong reference to the delegate yourself. An inline `host.delegate = SomeImpl()` with no other strong reference will silently stop firing after garbage collection.

### Why a read-only capabilities map and a capabilitiesProvider?

iOS exposes a mutable `capabilities` dictionary; Android instead exposes `capabilities` as a read-only merged snapshot of typed slots, configuration, and runtime-registered custom capabilities — writing to a merged view would be ambiguous. For dynamic capability state, pass a `capabilitiesProvider` lambda to the constructor: it is re-evaluated on every read, so capability state stays current without polling.

### Why JsonElement instead of a custom JSON type?

iOS defines a `JSONValue` enum because Foundation lacks a typed JSON model. Kotlin already has one: `kotlinx.serialization.json.JsonElement`, which integrates directly with the SDK's `@Serializable` message models. Using it means there is no parallel JSON type to learn or convert — construct values with `JsonPrimitive(...)` or `buildJsonObject { }`, and read them with `data["key"]?.jsonPrimitive?.contentOrNull`.

### Why these dependencies?

Unlike the iOS SDK, which has zero external dependencies, the Android SDK depends on three ubiquitous libraries: `androidx.annotation` (the `@MainThread` lint contract), `kotlinx-serialization-json` (message encoding and the `JsonElement` model), and `kotlinx-coroutines-android` — the latter exposed as an `api` dependency because coroutine types appear on the public surface of `CaptureCapability`.

## Next Steps

* [API Reference](/docs/go-v2/developer-integration/sdks/android/api-reference) — Detailed documentation for every public symbol
* [Embedding Guide](/docs/go-v2/developer-integration/sdks/android/embedding) — Compose and View-system integration patterns
* [Messaging Guide](/docs/go-v2/developer-integration/sdks/android/messaging) — Sending events and handling requests
