Skip to main content

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.

This guide explains how to send events to the web journey, handle incoming requests, and build request-response flows.

Message Types Overview

GBGBridge uses three message types:
TypeDirectionPurpose
RequestWeb β†’ NativeThe web journey asks the host to do something. Expects a response.
ResponseNative β†’ WebThe host returns the result of a request.
EventEither directionAn asynchronous notification. No response expected.
All messages share the same envelope structure (BridgeMessage) with a correlationId for pairing requests and responses.

Sending Events to the Web Journey

Events are fire-and-forget messages from the native host to the web journey. Use them to notify the web journey of state changes, user actions, or lifecycle transitions.
// Send a simple event
host.send(event: "host.ready", data: [
    "timestamp": .number(Date().timeIntervalSince1970)
])

// Send an event with structured data
host.send(event: "user.action", data: [
    "action": .string("tapped_help"),
    "screen": .string("document_capture")
])

Common Event Patterns

Event ActionWhen to SendExample Data
host.readyAfter host initialization completes{ timestamp }
host.backgroundApp enters background{}
host.foregroundApp returns to foreground{}
journey.cancelUser taps a cancel/back button{ reason }
host.orientationDevice orientation changes{ orientation }

Handling Incoming Requests

When the web journey sends a request, GBGBridge routes it to a registered handler or adds it to pendingRequests. For capture operations, use the typed slots on BridgeHost. Setting a handler declares support and routes requests automatically:
let host = BridgeHost(hostVersion: "1.0.0")

host.documentCapture.handler = { [weak host] request in
    guard let host else { return .cancelled(reason: "Host deallocated") }
    return await host.documentCapture.awaitCompletion()
}
Complete the request from your camera UI callback:
// Success
host.documentCapture.complete(.document(DocumentCaptureResult(
    imageData: imageData, width: 1920, height: 1080
)))

// Cancellation
host.documentCapture.complete(.cancelled(reason: "User dismissed"))

// Failure
host.documentCapture.complete(.failed(
    code: "CAMERA_DENIED", message: "Permission denied", recoverable: true
))
The SDK encodes CaptureResult into the bridge protocol format automatically. See the Capability Handling Guide for the full SwiftUI integration pattern.

Using Custom Capabilities or BridgeCapabilityHandler

For non-camera capabilities, use registerCustomCapability() or implement BridgeCapabilityHandler:
// Custom capability (closure-based)
host.registerCustomCapability("nfc.read") { request, responder in
    responder.respond(status: .success, data: ["chipRead": .bool(true)], error: nil)
}

// BridgeCapabilityHandler protocol (class-based)
host.register(handler: NFCReadHandler())

Handling Requests via the Delegate

For requests that don’t map to a single handler β€” or when you want centralized request handling β€” use BridgeHostDelegate.
class JourneyCoordinator: BridgeHostDelegate {
    func bridgeHost(_ host: BridgeHost, unhandledRequest message: BridgeMessage) {
        switch message.payload.action {
        case "journey.complete":
            let result = message.payload.data?["result"]
            handleJourneyComplete(result: result)

            host.respond(
                to: message.correlationId,
                status: .acknowledged
            )

        default:
            host.respond(
                to: message.correlationId,
                status: .unsupported,
                error: BridgeErrorPayload(
                    code: "UNSUPPORTED_ACTION",
                    message: "Action '\(message.payload.action)' is not supported",
                    recoverable: false
                )
            )
        }
    }

    func bridgeHost(_ host: BridgeHost, didReceive message: BridgeMessage) {
        // Log all messages for debugging
        print("[Bridge] \(message.type.rawValue): \(message.payload.action)")
    }

    private func handleJourneyComplete(result: JSONValue?) {
        // Navigate to results screen, etc.
    }
}
Set the delegate on your host:
let coordinator = JourneyCoordinator()
host.delegate = coordinator

Responding to Pending Requests Manually

If a request arrives with no registered handler, it is stored in pendingRequests. You can respond to it later.
// In a SwiftUI view observing the host
ForEach(host.pendingRequests) { request in
    HStack {
        Text(request.payload.action)
        Button("Approve") {
            host.respond(
                to: request.correlationId,
                status: .success,
                data: ["approved": .bool(true)]
            )
        }
        Button("Deny") {
            host.respond(
                to: request.correlationId,
                status: .cancelled
            )
        }
    }
}

Response Patterns

When responding to a request, you supply a status, optional data, and an optional error payload. The patterns below cover the three response shapes you’ll use most: success-with-data, error, and cancellation.

Success with Data

responder.respond(
    status: .success,
    data: [
        "imageBase64": .string("..."),
        "width": .number(1920),
        "height": .number(1080)
    ],
    error: nil
)

Error with Details

responder.respond(
    status: .error,
    data: nil,
    error: BridgeErrorPayload(
        code: "PERMISSION_DENIED",
        message: "Camera permission was denied by the user",
        recoverable: true  // User can grant permission in Settings
    )
)

User Cancellation

responder.respond(
    status: .cancelled,
    data: nil,
    error: nil
)

Unsupported Action

responder.respond(
    status: .unsupported,
    data: nil,
    error: BridgeErrorPayload(
        code: "NOT_AVAILABLE",
        message: "NFC is not available on this device",
        recoverable: false
    )
)

Acknowledged (Async Processing)

Use .acknowledged when the operation will take time and you want to inform the web journey that the request was received.
// First, acknowledge receipt
responder.respond(
    status: .acknowledged,
    data: ["estimatedDuration": .number(5000)],
    error: nil
)

// Later, send the actual result as an event
host.send(event: "camera.capture.complete", data: [
    "imageBase64": .string("..."),
    "correlationId": .string(request.correlationId)
])

Observing All Messages

Use BridgeHostDelegate.bridgeHost(_:didReceive:) to observe every message, or subscribe to the receivedMessages publisher via Combine.
// Combine approach
host.$receivedMessages
    .sink { messages in
        print("Total messages: \(messages.count)")
    }
    .store(in: &cancellables)

// Filter for specific actions
host.$receivedMessages
    .map { messages in
        messages.filter { $0.payload.action == "journey.progress" }
    }
    .sink { progressMessages in
        // Update progress UI
    }
    .store(in: &cancellables)

Error Handling

Bridge messaging can fail in three distinct places: when GBGBridge can’t decode an incoming message, when no WebView is attached to receive an outgoing one, and when JavaScript evaluation in the WebView itself errors out. Each surfaces through lastError so you can react in your UI.

Encoding/Decoding Errors

If GBGBridge cannot decode an incoming message, lastError is set with a description. The message is not added to receivedMessages.

WebView Not Attached

If you call send(event:data:) or respond(to:...) before attaching a WebView, lastError is set to "WebView not attached".

JavaScript Evaluation Errors

If the JavaScript evaluation fails (e.g., the web page has navigated away or the receive() function is not defined), lastError is set with the WebKit error description. Monitor errors reactively:
host.$lastError
    .compactMap { $0 }
    .sink { error in
        print("[Bridge Error] \(error)")
    }
    .store(in: &cancellables)

Next Steps