What is a Capability?
A capability is a native device feature that the host app can provide to the web journey. Examples include:camera.document: Document photographycamera.selfie: Facial capture for liveness checksnfc.read: Reading NFC chips on identity documents
Three Ways to Declare Capabilities
GBGBridge provides three approaches, listed from most to least recommended:| Approach | Best For | Init Method |
|---|---|---|
| Typed slots | Document/selfie capture | init(hostVersion:) |
| Custom capability | NFC, biometrics, or any non-camera capability | init(hostVersion:) + registerCustomCapability() |
| Configuration-based | Full manual control | init(configuration:) + register(handler:) |
Typed Capability Slots (Recommended)
Typed slots are the recommended way to declare capture capabilities. Setting ahandler on a slot simultaneously declares support and provides the implementation.
Basic Setup
Available Slots
| Property | Capability ID | Action ID |
|---|---|---|
host.documentCapture | camera.document | camera.document.capture |
host.selfieCapture | camera.selfie | camera.selfie.capture |
Handler-as-Declaration
With typed slots, there is no separate “declaration” step. A slot’sisSupported property is computed as handler != nil && isEnabled. When the web journey sends a capability.query request, only slots with handlers appear as supported.
Returning Results
Typed slot handlers returnCaptureResult values. The SDK encodes them into the bridge protocol format automatically — no manual JSONValue dictionary construction needed.
SwiftUI Integration with awaitCompletion()
TheawaitCompletion() / complete() pattern bridges async handlers with SwiftUI’s declarative presentation:
- Web journey sends
camera.document.capturerequest. - Handler runs, sets
activeRequest, callsawaitCompletion()— suspends. - SwiftUI detects
activeRequestchange, presents camerafullScreenCover. - Camera completes, view calls
host.documentCapture.complete(.document(...)). - Handler resumes with the result, SDK encodes and sends the response.
activeRequestresets tonil, cover dismisses.
Busy Rejection
If a request arrives while another is already active on the same slot, the SDK automatically responds with an error:Permission State
Each typed slot has apermissionState property. Populate it using CameraDetector so the web journey can check permissions before attempting capture:
capability.query response:
Enable/Disable at Runtime
ToggleisEnabled to temporarily disable a slot without removing the handler:
isSupported = false in capability queries.
Custom Capability Registration
For capabilities that don’t have a typed slot, for example, NFC and biometrics, useregisterCustomCapability():
capability.query responses as supported. If a typed slot exists for the same ID, the typed slot takes precedence.
Configuration-Based Approach (Legacy)
The configuration-based approach gives you full manual control. Use it when you need explicit capability dictionaries with constraints.BridgeCapabilityHandler for each action:
Handler Lifecycle
- Registration — Call
host.register(handler:)before the web journey sends requests. - Request routing — When a matching request arrives,
handle(request:responder:)is called in an async task. - Response — Call
responder.respond(...)exactly once with the result. - Unregistration — Call
host.unregister(action:)to remove a handler.
Capability Negotiation
How It Works
The web journey sends acapability.query request. GBGBridge’s built-in CapabilityQueryHandler 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.
Query Response
The web journey receives:permissionState field appears when the capability provides permission metadata (typed slots with permissionState set, or BridgeCapabilityInfo with permissionState non-nil).
Environment-Specific Behavior
The Problem
Not all environments support the same capabilities:| Capability | iOS Native | Web (iframe) | Android Native |
|---|---|---|---|
| Camera capture | Yes | Limited | Yes |
| NFC chip read | Yes (iPhone 7+) | No | Yes (varies) |
| Face ID / Touch ID | Yes | No | Fingerprint/Face Unlock |
Detecting Environment from the Web Journey
Thecapability.query response includes an environment field ("ios", "android", or "web" for iframe hosts). The web journey uses both the environment and the capability flags to make routing decisions.
Runtime Hardware Detection on iOS
UseCameraDetector for camera hardware and permission detection:
Graceful Degradation Patterns
When a capability isn’t available, your integration has two main routes: fall back to a web-based equivalent, or check upfront and prevent the user from starting a journey that won’t complete. The patterns below show both.Pattern 1: Fall Back to Web or Skip
The web journey checks capabilities and adapts its flow. If a web-based fallback exists for the capability, the journey uses it. If there is no web equivalent, the step is skipped entirely.Pattern 2: Check Permissions Before Starting
With permission state in the capability query, the web journey can detect permission issues and prompt the user:Pattern 3: Respond with Unsupported Status
If the web journey sends a request for a capability the host doesn’t support, typed slots automatically respond with.unsupported when no handler is set. For custom capabilities, respond explicitly:
Dynamic Capability Updates
With typed slots, capability state is inherently dynamic:- Set or clear
handlerto change support status. - Toggle
isEnabledto temporarily disable a slot. - Update
permissionStatewhen permissions change (e.g., after returning from Settings).
Next Steps
- Security Guide — Transport security and content policies
- Messaging Guide — Request/response patterns
- Troubleshooting Guide — Diagnosing capability-related issues