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 helps diagnose and resolve common issues when integrating GBGBridge.

Diagnostic Tools

BridgeHost exposes several built-in diagnostic surfaces — error state, a message log, and a list of pending requests — that should be your first stops when something looks off. Combined with Safari Web Inspector for the web side, these cover most debugging scenarios.

Observing lastError

The first thing to check is BridgeHost.lastError. It captures the most recent error from message decoding, JavaScript evaluation, or WebView attachment.
// In SwiftUI
Text(host.lastError ?? "No errors")

// Via Combine
host.$lastError
    .compactMap { $0 }
    .sink { error in
        print("[Bridge Error] \(error)")
    }
    .store(in: &cancellables)

Message Log

BridgeHost.receivedMessages contains every successfully decoded message. Use it to verify message flow.
// Print all received messages
for message in host.receivedMessages {
    print("[\(message.type.rawValue)] \(message.payload.action)\(message.correlationId)")
}

Pending Request Inspection

BridgeHost.pendingRequests shows requests awaiting a response. If this list grows unexpectedly, it means requests are arriving for actions with no registered handler.
if !host.pendingRequests.isEmpty {
    print("Unhandled requests:")
    for req in host.pendingRequests {
        print("  - \(req.payload.action) (\(req.correlationId))")
    }
}

Safari Web Inspector

For inspecting the web side of the bridge:
  1. Enable Web Inspector on your device: Settings → Safari → Advanced → Web Inspector.
  2. Connect your device to a Mac.
  3. In Safari on Mac, go to Develop → [Your Device] → [Your App].
  4. Use the console to inspect window.GBGBridge and test message sending.
// In Safari Web Inspector console:
console.log(window.GBGBridge);  // Should show the bridge namespace
console.log(typeof window.GBGBridge.receive);  // Should be "function"

Common Issues

Most bridge problems fall into a small set of recurring categories — message routing, capability discovery, network/ATS, and SwiftUI state plumbing. Each entry below describes the symptoms, the likely causes, and the fix.

Messages Not Being Received by the Host

Symptoms: The web journey sends messages, but receivedMessages stays empty.

Possible causes

  1. Wrong message handler name. The web journey must use exactly:
    window.webkit.messageHandlers.gbgBridge.postMessage(message);
    
    Note the lowercase g in gbgBridge.
  2. WebView not configured. Ensure you used BridgeWebView or BridgeWebViewConfigurator.configure(_:host:). A plain WKWebView does not have the message handler registered.
  3. Bootstrap script not injected. If the web journey tries to send a message before the bootstrap script runs, window.webkit.messageHandlers.gbgBridge may not exist. The bootstrap script runs at document start, but if you’re configuring the WebView after navigation begins, messages may be lost.
Fix: Always configure the WebView before loading any content.

Messages Not Being Received by the Web Journey

Symptoms: send(event:data:) or respond(to:...) is called, but the web journey doesn’t receive the message.

Possible causes

  1. WebView not attached. Check lastError — it will say "WebView not attached" if the WebView reference was lost.
  2. window.GBGBridge.receive not defined. The web journey must replace the no-op receive() with its own implementation. Check from Safari Web Inspector:
    console.log(window.GBGBridge.receive.toString());
    
  3. JavaScript evaluation error. Check lastError for messages starting with "evaluateJavaScript failed:".
  4. Page navigated away. If the WebView navigated to a different page, the bridge context is lost. The bootstrap script re-runs on each page load, but the web journey must reinitialize its receive() function.
Fix: Ensure the web journey sets up window.GBGBridge.receive after page load and that the WebView is attached to the host.

Message Decoding Failures

Symptoms: lastError says "Failed to decode message: ...".

Possible causes

  1. Malformed JSON. The web journey is sending a message that doesn’t match the BridgeMessage structure.
  2. Missing required fields. Every message must have: version, correlationId, type, timestamp, and payload (with at least action).
  3. Wrong type for type field. Must be one of: "request", "response", "event".
Fix: Validate the message structure on the web side before sending. A valid message looks like:
{
  "version": "1.0",
  "correlationId": "unique-id",
  "type": "request",
  "timestamp": Date.now(),
  "payload": {
    "action": "camera.capture",
    "data": {}
  }
}

Capability Query Returns Unexpected Results

Symptoms: The web journey receives a capability.query response, but capabilities are missing or incorrect.

Possible causes

  1. Typed slot without a handler. When using init(hostVersion:), a slot only appears as supported if handler is set and isEnabled is true. If you forgot to set a handler, the slot won’t appear.
  2. Slot is disabled. Check that isEnabled hasn’t been set to false on the slot.
  3. Capabilities not declared (configuration-based init). When using init(configuration:), only capabilities in the BridgeConfiguration dictionary are reported.
  4. Custom capability not registered. If using registerCustomCapability(), ensure it was called before the query.
Fix: For typed slots, verify handler is set: host.documentCapture.isSupported should be true. For configuration-based init, review your BridgeConfiguration.

Permission State Not Appearing in Query Response

Symptoms: The permissionState field is missing from the capability query response.

Possible causes

  1. Not using typed slots. Permission state is only included automatically when using init(hostVersion:) with typed slots that have permissionState set.
  2. CameraDetector not called. The default permissionState is .notDetermined. Call CameraDetector.check() and assign the result.
  3. Using configuration-based init without permissionState. The BridgeCapabilityInfo permissionState field defaults to nil. Set it explicitly.
Fix:
let camera = CameraDetector.check()
host.documentCapture.permissionState = camera.permissionState

Handler Not Called for Requests

Symptoms: A request arrives (visible in receivedMessages) but the handler’s handle() method is never called. The request appears in pendingRequests.

Possible causes

  1. Typed slot handler not set. When using init(hostVersion:), ensure host.documentCapture.handler (or selfieCapture.handler) is set. Without a handler, requests for camera.document.capture go to pendingRequests.
  2. Action mismatch. For custom capabilities or BridgeCapabilityHandler, the action property must exactly match the request’s payload.action. Check for typos and case sensitivity.
  3. Handler not registered. For the register(handler:) path, ensure it was called before the request arrived.
  4. Slot is disabled. Check that isEnabled is true on the typed slot.
Fix: For typed slots, verify the handler is set and the slot is enabled. For protocol-based handlers, verify the action string matches:
// Typed slot — action is "camera.document.capture"
host.documentCapture.handler = { ... }

// Protocol-based handler — action must match exactly
let action = "camera.document.capture"  // Must match web journey's payload.action

App Transport Security Errors

Symptoms: The WebView shows a blank page or an error. The console shows ATS-related errors. Fix for local development:
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>
Fix for production: Use HTTPS for all journey URLs. Do not add NSAllowsArbitraryLoads.

WebView Shows Blank Page

Symptoms: The WebView renders but shows nothing.

Possible causes

  1. Invalid URL. Check that the URL is reachable.
  2. ATS blocking. See above.
  3. CORS issues. If the journey loads resources from other domains, check CORS headers.
  4. JavaScript error. Connect Safari Web Inspector to check for console errors.
Fix: Test the URL in Safari first to rule out web-side issues.

Memory Warnings or Crashes

Symptoms: App receives memory warnings or crashes when using the bridge.

Possible causes

  1. Large payloads. Sending very large Base64-encoded images through the bridge can cause memory pressure. Consider passing file paths instead of raw data.
  2. Message accumulation. receivedMessages grows without bound. If you’re in a long-running session, this array can become large.
Fix for large payloads:
// Instead of sending raw image data:
// data: ["imageBase64": .string(largeBase64String)]

// Send a file path:
responder.respond(
    status: .success,
    data: ["imagePath": .string(tempFilePath)],
    error: nil
)
Fix for message accumulation: Periodically clear old messages if your session is long-running.

SwiftUI View Not Updating

Symptoms: BridgeHost state changes (e.g., lastError, receivedMessages) don’t trigger view updates.

Possible causes

  1. Using @ObservedObject where @StateObject is needed. If the view owns the host, use @StateObject to prevent re-creation on re-renders.
  2. Host created outside the view hierarchy. Ensure the host is referenced via @StateObject or @ObservedObject.
Fix:
// Owner view — creates the host
struct ParentView: View {
    @StateObject private var host = BridgeHost(...)  // Use @StateObject
    var body: some View {
        ChildView(host: host)
    }
}

// Child view — receives the host
struct ChildView: View {
    @ObservedObject var host: BridgeHost  // Use @ObservedObject
    var body: some View { ... }
}

Typed Slot Returns “BUSY” Error

Symptoms: The web journey receives a BUSY error when sending a capture request. Cause: A previous capture request is still in progress on the same slot (activeRequest is non-nil). Fix: Ensure the previous request completes before sending another. Call host.documentCapture.complete(...) when the camera UI finishes or is dismissed. Check the onDismiss closure of your fullScreenCover:
.fullScreenCover(isPresented: $showCamera, onDismiss: {
    if host.documentCapture.activeRequest != nil {
        host.documentCapture.complete(.cancelled(reason: "Dismissed"))
    }
}) { ... }

Debugging Checklist

When something isn’t working, run through these checks in whichever order makes most sense for the symptom — they’re independent, not sequential:
  • Check host.lastError for error messages.
  • Check host.receivedMessages to see if messages are arriving.
  • Check host.pendingRequests for unhandled requests.
  • For typed slots: verify handler is set and isSupported is true.
  • For typed slots: check activeRequest isn’t stuck — i.e. a previous request didn’t complete.
  • Connect Safari Web Inspector to examine the web side.
  • Verify handler action strings match request actions exactly.
  • Ensure the WebView was configured via BridgeWebView or BridgeWebViewConfigurator.
  • Confirm the journey URL is reachable and uses HTTPS.
  • Check Info.plist for required permission descriptions.
  • Verify @StateObject vs @ObservedObject usage.

Next Steps