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

# Messaging

> How messages flow between your iOS app and the web journey.

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:

| Type         | Direction        | Purpose                                                            |
| ------------ | ---------------- | ------------------------------------------------------------------ |
| **Request**  | Web → Native     | The web journey asks the host to do something. Expects a response. |
| **Response** | Native → Web     | The host returns the result of a request.                          |
| **Event**    | Either direction | An 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.

```swift theme={null}
// 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 Action       | When to Send                        | Example Data      |
| ------------------ | ----------------------------------- | ----------------- |
| `host.ready`       | After host initialization completes | `{ timestamp }`   |
| `host.background`  | App enters background               | `{}`              |
| `host.foreground`  | App returns to foreground           | `{}`              |
| `journey.cancel`   | User taps a cancel/back button      | `{ reason }`      |
| `host.orientation` | Device 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`.

### Using Typed Capability Slots (Recommended)

For capture operations, use the typed slots on `BridgeHost`. Setting a handler declares support and routes requests automatically:

```swift theme={null}
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:

```swift theme={null}
// 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](/docs/go-v2/developer-integration/sdks/ios/capability-handling) for the full SwiftUI integration pattern.

### Using Custom Capabilities or BridgeCapabilityHandler

For non-camera capabilities, use `registerCustomCapability()` or implement `BridgeCapabilityHandler`:

```swift theme={null}
// 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`.

```swift expandable theme={null}
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:

```swift theme={null}
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.

```swift theme={null}
// 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

```swift theme={null}
responder.respond(
    status: .success,
    data: [
        "imageBase64": .string("..."),
        "width": .number(1920),
        "height": .number(1080)
    ],
    error: nil
)
```

### Error with Details

```swift theme={null}
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

```swift theme={null}
responder.respond(
    status: .cancelled,
    data: nil,
    error: nil
)
```

### Unsupported Action

```swift theme={null}
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.

```swift theme={null}
// 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.

```swift theme={null}
// 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:

```swift theme={null}
host.$lastError
    .compactMap { $0 }
    .sink { error in
        print("[Bridge Error] \(error)")
    }
    .store(in: &cancellables)
```

## Next Steps

* [Capability Handling Guide](/docs/go-v2/developer-integration/sdks/ios/capability-handling) — Build handlers for camera, NFC, and other features
* [Embedding Guide](/docs/go-v2/developer-integration/sdks/ios/embedding) — SwiftUI and UIKit integration patterns
* [API Reference](/docs/go-v2/developer-integration/sdks/ios/api-reference) — Detailed method signatures and parameters
