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 + NFC read). The journey runs as HTML/JavaScript inside a WebView. |
| Host | The native iOS application that embeds the journey WebView. The host provides native capabilities. |
| Bridge | The communication layer between the web journey and the native host. Comprises JavaScript stubs on the web side and BridgeHost on the native side. |
| Capability | A native feature that the host can provide (e.g., camera.document, nfc.read). 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 Swift object conforming to BridgeCapabilityHandler that fulfills requests for a specific action, or a closure 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 provided to handlers, used to send a response back to the web journey. |
| Bootstrap Script | A JavaScript snippet injected at document start that initializes the window.GBGBridge namespace and receive() function. |
Architecture
Component Diagram
Layers
- Capability Layer — Typed slots (
CaptureCapability) and custom capabilities declare what the host supports.CameraDetectorprovides hardware and permission detection. - Routing Layer —
BridgeHostreceives messages from the WebView, decodes them, and routes requests to typed slot handlers, custom capability handlers, orBridgeCapabilityHandlerimplementations. - Handler Layer — Typed slots handle capture requests via closures and return strongly-typed
CaptureResultvalues. TheBridgeCapabilityHandlerprotocol supports arbitrary request handling. - Transport Layer —
BridgeWebViewConfiguratorandBridgeWebViewmanage the WebView, inject the bootstrap script, and wire up the message channels. - Observation Layer —
BridgeHostDelegatelets you observe all messages and handle unrouted requests.
Message Protocol
All communication uses structured JSON envelopes. The protocol version is1.0.
Message Envelope
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
Correlation IDs
Every request has acorrelationId. 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.
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 camera sheet). |
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:BridgeHostdecodes the JSON envelope into aBridgeMessage.- The message is appended to
receivedMessages(observable). - The delegate’s
bridgeHost(_:didReceive:)is called. - If the message type is
request:- If a handler is registered for the message’s
action, the handler’shandle(request:responder:)is called. - If no handler is registered, the message is added to
pendingRequestsand the delegate’sbridgeHost(_:unhandledRequest:)is called.
- If a handler is registered for the message’s
- If the message type is
responseorevent, no routing occurs — it is stored and reported to the delegate.
Typed Capability Slots
GBGBridge provides typed capability slots as the recommended way to declare and handle well-known capabilities. A typed slot is aCaptureCapability 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 ahandler on a typed slot simultaneously declares support and provides the implementation:
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.queryresponses with version and permission state metadata. - Result encoding — Handlers return strongly-typed
CaptureResultvalues. 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 automatically.
- SwiftUI reactivity —
activeRequestis@Published, so views can react to capture requests (e.g., present a camerafullScreenCover).
Permission State
Each typed slot has apermissionState property that can be populated using CameraDetector:
capability.query response, allowing the web journey to detect permission issues before attempting capture.
Capability Negotiation
Before attempting to use a native feature, the web journey sends acapability.query request. GBGBridge includes a built-in handler (CapabilityQueryHandler) that responds automatically.
When using init(hostVersion:), the response is built dynamically from typed slots and custom capabilities. When using init(configuration:), it uses the static BridgeConfiguration dictionary.
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
permissionState field is included when the typed slot or configuration provides it. It is omitted for capabilities that don’t report permission state.
See the Capability Handling Guide for detailed patterns including typed slots, custom capabilities, and environment-specific behavior.
Transport Mechanism
Web → Native (Incoming)
The web journey calls:BridgeHost via the WKScriptMessageHandler protocol.
Native → Web (Outgoing)
The host evaluates JavaScript on the WebView:receive() function is established by the bootstrap script that BridgeWebViewConfigurator injects at document start.
Bootstrap Script
The default bootstrap script is:receive() function. The web journey replaces receive() with its own implementation once loaded. Because injection happens at document start, the namespace is guaranteed to exist before any web code runs.
You can supply a custom bootstrap script via BridgeConfiguration.bootstrapScript if your web journey requires additional initialization.
Threading Model
BridgeHostis annotated@MainActor. All property access and method calls must happen on the main thread.BridgeCapabilityHandler.handle(request:responder:)isasync, allowing handlers to perform asynchronous work (e.g., presenting a camera sheet, waiting for NFC scan completion).BridgeResponder.respond()is safe to call from any thread — it dispatches back to the main actor internally.BridgeWebViewis a SwiftUIUIViewRepresentableand follows standard SwiftUI threading rules.
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 iOS app, an Android 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 @MainActor on BridgeHost?
WebKit APIs (WKWebView, WKUserContentController) must be accessed on the main thread. Since BridgeHost interacts directly with WebKit for both receiving and sending messages, constraining it to the main actor eliminates a class of threading bugs.
Why no external dependencies?
GBGBridge is a foundational integration layer. Adding dependencies would increase binary size, create version conflicts with consumer apps, and introduce supply-chain risk. The framework uses only Apple system frameworks.Next Steps
- API Reference — Detailed documentation for every public symbol
- Embedding Guide — SwiftUI and UIKit integration patterns
- Messaging Guide — Sending events and handling requests