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.

Complete guide for using @gbgplc-internal/ggo-native-bridge-react in React applications.

Setup

Wrap your app (or the relevant subtree) with <BridgeProvider>:
import { BridgeProvider } from '@gbgplc-internal/ggo-native-bridge-react';

function App() {
  return (
    <BridgeProvider>
      <YourApp />
    </BridgeProvider>
  );
}
The provider creates the NativeBridge singleton, auto-detects the host environment, discovers capabilities on mount, and makes the bridge available to all child components via context.

useBridge() — Access the bridge

useBridge() is the main hook for reading bridge state and reaching the underlying NativeBridge instance. It returns the environment, capability map, and helpers for capability checks:
import { useBridge } from '@gbgplc-internal/ggo-native-bridge-react';

function MyComponent() {
  const {
    bridge,             // NativeBridge instance
    environment,        // 'ios' | 'android' | 'iframe' | 'standalone'
    isEmbedded,         // true if inside a host
    capabilities,       // CapabilityMap
    capabilitiesReady,  // true once discovery completes
    hasCapability,      // (id: string) => boolean
  } = useBridge();

  bridge.emit('journey.started', { timestamp: Date.now() });
}
Throws an Error if used outside a <BridgeProvider>.

Waiting for capabilities

function CapabilityGate({ children }: { children: React.ReactNode }) {
  const { capabilitiesReady } = useBridge();

  if (!capabilitiesReady) return <LoadingSpinner />;
  return <>{children}</>;
}

useCapability(id) — Check a capability

Reactively check whether a specific capability is supported:
import { useCapability } from '@gbgplc-internal/ggo-native-bridge-react';
import { CAPABILITY_IDS } from '@gbgplc-internal/ggo-native-bridge';

function NfcCard() {
  const { supported, ready, version } = useCapability(CAPABILITY_IDS.NFC_READ);

  if (!ready) return <p>Checking NFC support...</p>;

  if (!supported) {
    return <p>NFC is not available on this device.</p>;
  }

  return (
    <div>
      <p>NFC ready (v{version})</p>
      <button>Scan Passport</button>
    </div>
  );
}

useNativeCamera(type) — Camera capture

useNativeCamera() is a convenience hook that wraps the most common bridge request — a document or selfie capture — with the right capability check, action string, and timeout. It returns a flag for whether a native capture is available and a requestCapture() function you call when the user triggers capture:
import { useNativeCamera } from '@gbgplc-internal/ggo-native-bridge-react';
import { BridgeError, BridgeErrorCode } from '@gbgplc-internal/ggo-native-bridge';

function DocumentCapture() {
  const { isNativeAvailable, requestCapture } = useNativeCamera('document');

  async function handleCapture() {
    if (!isNativeAvailable) {
      openWebCamera();
      return;
    }

    try {
      const response = await requestCapture({
        documentType: 'passport',
        side: 'front',
      });
      processImage(response.data);
    } catch (error) {
      if (error instanceof BridgeError) {
        if (error.code === BridgeErrorCode.CANCELLED) return;
        if (error.recoverable) {
          showRetryPrompt();
          return;
        }
      }
      showErrorMessage('Capture failed');
    }
  }

  return (
    <button onClick={handleCapture}>
      {isNativeAvailable ? 'Capture with Native Camera' : 'Capture with Web Camera'}
    </button>
  );
}

Camera type mapping

Each type value maps to a specific bridge capability and capture action:
typeCapability checkedAction sent
'document'camera.documentcamera.document.capture
'selfie'camera.selfiecamera.selfie.capture
useNativeCamera works even without a <BridgeProvider>, returning isNativeAvailable: false. This makes it safe to use in components that may or may not have bridge access.

useBridgeEvent(action, callback) — Listen for host events

import { useBridgeEvent } from '@gbgplc-internal/ggo-native-bridge-react';

function HostEventListener() {
  useBridgeEvent('host.navigation.back', (data) => {
    navigateBack();
  });

  useBridgeEvent('host.theme.update', (data) => {
    const theme = data.theme as Record<string, string>;
    for (const [key, value] of Object.entries(theme)) {
      document.documentElement.style.setProperty(key, value);
    }
  });

  return null;
}
Key behaviours:
  • Subscribes on mount, unsubscribes on unmount
  • You do not need to wrap the callback in useCallback — the hook uses a ref internally
  • Resubscribes only if the action string changes

Emitting events

function JourneyTracker() {
  const { bridge } = useBridge();

  useEffect(() => {
    bridge.emit('journey.started', { journeyId: 'abc-123', timestamp: Date.now() });
    return () => {
      bridge.emit('journey.completed', { journeyId: 'abc-123', timestamp: Date.now() });
    };
  }, []);

  return null;
}

Combining hooks

function VerificationStep() {
  const { environment, hasCapability, bridge } = useBridge();
  const { supported: hasDocCamera, ready } = useCapability('camera.document');
  const { isNativeAvailable, requestCapture } = useNativeCamera('document');

  useBridgeEvent('host.navigation.back', () => goToPreviousStep());

  if (!ready) return <LoadingSpinner />;

  return (
    <div>
      <p>Running in: {environment}</p>
      {isNativeAvailable ? (
        <button onClick={() => requestCapture({ side: 'front' })}>
          Capture with Native Camera
        </button>
      ) : (
        <WebCamera />
      )}
    </div>
  );
}

Testing

Use the environment and channel options to mock the bridge in tests:
import { render, screen } from '@testing-library/react';
import { BridgeProvider } from '@gbgplc-internal/ggo-native-bridge-react';
import { NativeBridge } from '@gbgplc-internal/ggo-native-bridge';
import type { BridgeMessage, MessageChannel } from '@gbgplc-internal/ggo-native-bridge';

function createMockChannel() {
  let onMsg: ((msg: BridgeMessage) => void) | null = null;
  const sent: BridgeMessage[] = [];

  return {
    channel: {
      send(msg: BridgeMessage) { sent.push(msg); },
      onMessage(cb: (msg: BridgeMessage) => void) { onMsg = cb; },
      destroy() { onMsg = null; },
    } satisfies MessageChannel,
    sent,
    simulateResponse(msg: BridgeMessage) { onMsg?.(msg); },
  };
}

it('shows native camera button when capability exists', async () => {
  const mock = createMockChannel();

  render(
    <BridgeProvider options={{ environment: 'ios', channel: mock.channel }}>
      <DocumentCapture />
    </BridgeProvider>,
  );

  mock.simulateResponse({
    version: '1.0',
    correlationId: mock.sent[0].correlationId,
    type: 'response',
    timestamp: Date.now(),
    payload: {
      action: 'capability.query',
      status: 'success',
      data: {
        capabilities: {
          'camera.document': { supported: true, version: '2.0' },
        },
      },
    },
  });

  expect(await screen.findByText('Capture with Native Camera')).toBeTruthy();
});

afterEach(() => {
  NativeBridge.resetInstance();
});