# React Native

> A hardened react-native-webview wrapper (RN 0.74+) with a component, a modal and a promise API.

Source: https://docs.okyc.stream/mobile/react-native

## Install

Peer dependencies: `react` ≥ 18, `react-native` ≥ 0.74, `react-native-webview` ≥ 13, and optionally
`expo-screen-capture` ≥ 6 (lets the SDK block screenshots and screen recording).

```bash title="Terminal"
npm install @okyc/react-native react-native-webview
npx expo install expo-screen-capture   # optional: screenshot blocking
cd ios && pod install
```

## Permissions

iOS needs camera (and, for uploads, photo library) usage strings:

```xml title="Info.plist"
<key>NSCameraUsageDescription</key>
<string>The camera is used to capture your ID document and a selfie for identity verification.</string>
```

Android needs `INTERNET` and `CAMERA`:

```xml title="AndroidManifest.xml"
<uses-permission android:name="android.permission.INTERNET" />
<!-- Document + selfie capture: react-native-webview asks for the runtime permission when the page calls getUserMedia. -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
```

When the page asks for the camera, `react-native-webview` requests the runtime permission. If the user refuses,
the flow shows its own "camera blocked" state with an upload fallback where the workflow allows one. File
inputs work on both platforms without extra code.

## Start the flow

Create the session on your backend and pass its `url` to the app.

```tsx title="verify.tsx"
export function makeConfig(sessionUrl: string): OkycConfig {
  return {
    sessionUrl, // the `url` returned by POST /v1/sessions
    locale: 'en', // optional; defaults to the session's language
    theme: 'system', // 'light' | 'dark' | 'system'
    allowedHosts: [], // extra hosts, only when self-hosting
    blockScreenshots: true, // needs expo-screen-capture
    autoCloseDelay: 2000, // ms the result screen stays visible after `complete`
  };
}
```

```tsx title="verify.tsx"
export function App() {
  return (
    <OkycProvider>
      <VerifyButton />
    </OkycProvider>
  );
}

function VerifyButton() {
  const start = async () => {
    const sessionUrl = await fetchSessionUrlFromYourBackend();
    const result = await Okyc.start(makeConfig(sessionUrl), { onEvent });
    handleResult(result);
  };
  return <Button title="Verify identity" onPress={start} />;
}
```

Three ways to present it:

| API                                                                |                                                                                    |
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| `<OkycVerification config onComplete onCancel onError onEvent />`  | Inline component; you own the screen.                                              |
| `<OkycModal visible config … />`                                   | Full-screen modal with a toolbar; close and Android back ask "Exit verification?". |
| `OkycProvider` + `useOkyc().start(config)` or `Okyc.start(config)` | Promise that resolves to an `OkycResult`. One verification at a time.              |

The modal form:

```tsx title="verify.tsx"
export function VerifyScreen({ sessionUrl }: { sessionUrl: string }) {
  const [visible, setVisible] = useState(true);
  const done = (result: OkycResult) => {
    setVisible(false);
    handleResult(result);
  };
  return (
    <View style={{ flex: 1 }}>
      <Text>Verification</Text>
      <OkycModal
        visible={visible}
        config={makeConfig(sessionUrl)}
        onEvent={onEvent}
        onComplete={done}
        onCancel={done}
        onError={done}
      />
    </View>
  );
}
```

### Options

| Option             | Type                          | Default    | Description                                                                                                                                                                |
| ------------------ | ----------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sessionUrl`       | `string`                      | required   | The `url` returned by `POST /v1/sessions`. Never embed a secret API key in an app.                                                                                         |
| `locale`           | `string`                      | `null`     | Flow language (`en`, `hi`); must be one of the workflow's languages. Sent as `?locale=`.                                                                                   |
| `theme`            | `light` \| `dark` \| `system` | `"system"` | Colour scheme of the flow. Sent as `?theme=`.                                                                                                                              |
| `allowedHosts`     | `string[]`                    | `[]`       | Extra hosts the WebView may load (self-hosting).                                                                                                                           |
| `blockScreenshots` | `boolean`                     | `true`     | Android: FLAG\_SECURE. iOS: cover the flow in the app switcher and while the screen is recorded.                                                                           |
| `autoCloseDelay`   | `duration_ms`                 | 2000 ms    | How long the result screen stays visible after `complete` before the SDK closes and returns `completed`. `0` or `null` = return the result immediately (no result screen). |

## Results and events

```tsx title="verify.tsx"
function handleResult(result: OkycResult) {
  switch (result.type) {
    case 'completed':
      // UX only: confirm the decision on your backend (webhook or GET /v1/sessions/:id).
      console.log('finished', result.sessionId, result.status);
      break;
    case 'cancelled':
      console.log('user closed the flow');
      break;
    case 'failed':
      console.warn('verification failed', result.code, result.message);
      break;
  }
}
```

```tsx title="verify.tsx"
function onEvent(event: OkycEvent) {
  // Payloads carry ids and statuses only — never personal data.
  track(`okyc_${event.type}`, { sessionId: event.sessionId, ...event.payload });
}
```

`OkycResult` is `{ type: 'completed', sessionId, status }`, `{ type: 'cancelled', sessionId? }` or
`{ type: 'failed', sessionId?, code, message }`. Event payloads are flat and PII-free (for example
`{ step: 'pan' }`).

Failure codes in React Native:

| Code                  | Meaning                                                                    |
| --------------------- | -------------------------------------------------------------------------- |
| `invalid_config`      | Missing or malformed session URL or configuration.                         |
| `load_failed`         | The flow page failed to load (HTTP error or blank page).                   |
| `network_error`       | No connection or DNS/TLS failure while loading.                            |
| `navigation_blocked`  | Repeated navigation to non-allowlisted hosts.                              |
| `permission_denied`   | The camera permission was permanently denied and the flow cannot continue. |
| `flow_error`          | The flow reported an unrecoverable error.                                  |
| `webview_unavailable` | The WebView renderer crashed or is unavailable.                            |

## Theming and locale

`theme: 'light' | 'dark'` forces the flow's colour scheme before the page loads; `system` follows the device
and your dashboard branding. The flow language comes from the session: pass `locale` when you create it
(`POST /v1/sessions`); the applicant can also switch it in the flow.

## Security defaults

* Only the flow origin, DigiLocker and `allowedHosts` load; `target=_blank` https links open in the system
  browser; other hosts, http downgrades, `file:`, `intent:` and `javascript:` are blocked.
* Bridge messages are accepted only from the session's own page (origin and session id; not DigiLocker or
  `allowedHosts`) and must be JSON with `source: 'okyc'`, `v: 1` and a known type; payloads are reduced to
  scalars and messages over 10 KB are dropped. `blob:`/`data:` URLs load only inside frames.
* Incognito WebView without cache, shared or third-party cookies, file URLs or mixed content; gestures and link
  previews off; debugging only in `__DEV__`.
* Screenshots and recording are blocked when `expo-screen-capture` is installed
  (`isScreenshotBlockingAvailable()` tells you).
* The session URL is never logged or included in error messages.

## Troubleshooting

| Symptom                                 | Fix                                                                                        |
| --------------------------------------- | ------------------------------------------------------------------------------------------ |
| `A verification is already in progress` | Only one `start()` at a time; wait for the result.                                         |
| Local http URL refused                  | HTTP is accepted only for `localhost`, `127.0.0.1`, `10.0.2.2`, `[::1]` and `*.localhost`. |
| Screenshots still possible              | Install `expo-screen-capture` (bare apps need expo-modules).                               |

## Going live

* Create sessions with a `sk_live_…` key on your backend; nothing changes in the app.
* Install `expo-screen-capture` so `blockScreenshots` takes effect.
* Decide on the [webhook](https://docs.okyc.stream/backend/webhooks.md), not on `completed`.
