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 checksdocument.nfc: Reading the chip in a passport
Three Ways to Declare Capabilities
GBGBridge provides three approaches, listed from most to least recommended:
All three can be used together. Typed slots take precedence over custom capabilities with the same ID.
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
The capture slots are covered below.
host.nfcRead takes a different request and result type: see Passport Chip Reading.
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.
Passport Chip Reading
Passport chip reading needs GBGBridge 1.2.0 or later.
host.nfcRead is the typed slot for reading the chip in a passport. It advertises document.nfc and answers document.nfc.read.
What Your App Does and What the SDK Does
Your handler runs a chip reader and hands back what it read, as anNfcReadResult. The SDK does the rest:
- Checks the request carries the document number, date of birth and date of expiry, and answers
MISSING_MRZ_DATAif one is missing. - Answers
BUSYwhile a read is running. - Answers
UNSUPPORTEDwhen there is no handler,isEnabledisfalse, ordeviceCanReadreturnsfalse. - Refuses a read where the chip never opened (
NFC_AUTH_FAILED), or where DG1 held no readable MRZ (NFC_MRZ_UNAVAILABLE). - Drops DG16 and builds the response the web journey expects.
NfcReadRequest with documentNumber, dateOfBirth and dateOfExpiry. Dates are YYMMDD. Check digits are not sent: the reader derives them.
App Setup
Chip reading uses Core NFC, so your app needs:- The Near Field Communication Tag Reading capability in Signing & Capabilities. This adds the
com.apple.developer.nfc.readersession.formatsentitlement with theTAGformat. - These Info.plist entries. Without the select identifier, iOS can’t select the passport application and the read fails.
- A
deviceCanReadcheck. The SDK doesn’t link Core NFC, so it can’t tell whether the device has an NFC reader.deviceCanReaddefaults tofalse, which reports chip reading as unsupported. Set it:
NFCTagReaderSession.readingAvailable is false on iPads and the Simulator. To try the flow on the Simulator with a mock reader, set deviceCanRead to { true }.
Presenting the Reader
Use the same pattern as the capture slots: the handler waits onawaitCompletion(), your view shows the reader while activeRequest is set, and calls complete(_:) with the result.
ChipReaderView is your own view that runs the reader. It hands back one of:
awaitCompletion(), the SDK keeps the result and returns it when the handler waits.
Using the SmartCapture Ozone Reader
GBG SmartCapture includes a passport chip reader, Ozone (OzoneNFC.xcframework), which you get from your GBG account representative alongside the camera frameworks. Its OzoneNFCViewController shows the reading screen. Build it from the request:
NfcReadResult: the files from getRawDataGroup(dgId:), and the checks from the BAC, PACE, chip authentication and active authentication statuses plus passportDataValid.
Error Codes
These are the codes the web journey can receive. The SDK sends most of them itself. Your result sendsMISSING_MRZ_DATA or NFC_READ_FAILED through .failed(_:message:), and CANCELLED through .cancelled(reason:).
UNSUPPORTED is a hard stop: chip reading has no web fallback, so the journey asks the person to use a different device.
Moving From a Custom Capability
An app that answers chip reads withregisterCustomCapability("document.nfc", actionId: "document.nfc.read") keeps working while host.nfcRead.handler is nil. Once you set the slot’s handler, the slot answers instead, so remove the custom capability when you move.
Custom Capability Registration
For capabilities that don’t have a typed slot, for example, biometrics, useregisterCustomCapability():
capability.query responses as supported unless you pass isSupported:. The SDK asks isSupported on every query, so pass it when the device may lack the hardware or the feature can be switched off. Requests still reach the handler either way, so the handler should answer unsupported itself when it can’t help.
Pass actionId: when the request action differs from the capability ID. 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:
When a web journey includes a chip-reading step but the host can’t read chips, the journey needs to know before reaching that step.
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:
deviceCanRead on the slot. It defaults to false, so the slot reports chip reading as unsupported until you set it:
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.document.nfc is unsupported, the journey asks the person to use a different device.
The host app doesn’t need to do anything special — the web journey handles fallback and routing decisions based on the capability query response.
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