npm package: @get-id/react-native-sdk
Description: GetID react native SDK
Author: GetID
Latest version: 0.2.25

# Getting Started with the GetID React Native SDK

> **Note**: Please note that the SDK does not work with React Native apps created with Expo because this project uses the react-native-camera-kit plugin for the camera, which is not supported by Expo.

## Overview

The SDK offers a comprehensive toolkit for capturing identity documents and performing selfie and liveness checks. Once captured, the data is sent to the GetID server for verification.
The result can either be returned by the SDK, or you can rely on webhooks from the backoffice, or a combination of both.

The essential parts to the implementation are to:

- Generate a token for the session (via your backend)
- Initialise the SDK with the needed configuration (flow, token)
- Trigger documentscan of front side
- If needed, trigger documentscan of back side
- Trigger selfie and liveness (if configured)
- Trigger verification of the session

## Getting started

### Obtaining an SDK key

Start by fetching the `SDK KEY` and `API URL`:

- Access these from your GetID Dashboard.
- Alternatively, contact our [integration support](mailto:support@getid.ee?subject=[GitHub]%20Obtaining%20GetID%20credentials).

> **Security Reminder**: Your GetID Dashboard provides both `API KEY` and `SDK KEY`. While the `API KEY` allows for public and SDK API calls, the `SDK KEY` is exclusive to SDK API calls. It's safer to use `SDK KEY` when using the SDK.

### Camera Permissions

Ensure the SDK can access the device camera.

Add the following usage descriptions to your Info.plist (usually found at: ios/PROJECT_NAME/)

```
<key>NSCameraUsageDescription</key>
<string>For taking photos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>For saving photos</string>
```

Add the following uses-permission to your AndroidManifest.xml (usually found at: android/src/main/)

```
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

## Installation

```bash
yarn add https://cdn.getid.cloud/sdk/react-native/[version]/get-id-react-native-sdk.tgz
yarn add react-native-vision-camera@4.0.5
yarn add react-native-worklets-core@1.2.0
yarn add react-native-svg@15.2.0
yarn add react-native-photo-manipulator@1.5.0
yarn add @get-id/react-native-fast-tflite@https://cdn.getid.cloud/sdk/react-native/getid-react-native-fast-tflite/1.2.0/get-id-react-native-fast-tflite.tgz
yarn add react-native-image-picker@7.1.2
yarn add vision-camera-resize-plugin@https://github.com/mrousavy/vision-camera-resize-plugin.git#ff3b6d25c035e96d9d68fd17a131abd14d33f9a1
yarn add lottie-react-native@5.1.6
yarn add lottie-ios@3.4.0
cd ios; pod install; cd ..
```

## Usage

Unless a flow is already created, use the GetID Admin Panel in the backoffice to create a new flow (Flows > Add new flow).

### Importing the Getid SDK library in your app

Here's an example of how you can import the GetID SDK in your React Native app:

```tsx
// App.tsx
import React from 'react';
import { GetIdSdk } from '@get-id/react-native-sdk';

const App = () => {
  return (
    // Your component here
  );
};
```

### Initialising the SDK

There are two ways to initialise the SDK: using the `SDK KEY` or using a `JWT`. We recommend using JWT in the production environment. But during the development, you can use `SDK KEY`, because it's a little bit more convenient.

#### Using SDK KEY

```javascript
import { GetIdSdk } from '@get-id/react-native-sdk';
const sdk = await GetIdSdk.initialiseSDK({
  apiUrl: '[YOUR_END_POINT]',
  sdkKey: '[SDK_KEY]',
  flow: '[YOUR_FLOW]',
});
```

#### Using a `JWT`

To start the verification flow using a `JWT`, your app should obtain the token from your backend.
Your backend should have the `SDK KEY` to request the token from GetID server. Don't store the `SDK KEY` inside the app in a production environment.

To obtain a `JWT` make a POST request on your `API URL` with `SDK KEY` in the header:

```bash
$ curl -H "Content-Type: application/json" -H "x-sdk-key: SDK_KEY" -X POST API_URL/sdk/v2/token
```

Then pass the received token to `GetIdSdk.initialiseSDK` method:

```javascript
import { GetIdSdk } from '@get-id/react-native-sdk';
const sdk = await GetIdSdk.initialiseSDK({
  apiUrl: '[YOUR_END_POINT]',
  token: '[JWT_TOKEN]',
  flow: '[YOUR_FLOW]',
});
```

### How to use GetID SDK

Once the SDK is initialized, you need to call the `initialiseFrontDocumentScan` function and other SDK functions to open up the camera feed and interact with the SDK.

<br>To do that, let's create another component that will act as the wrapper for the camera feed, and where you can add your custom UI at the top and trigger camera functions like `takePicture()`, `uploadDocument()`, and `capture()`.
<br>Let's create a component called `GetidDocScanWrapper.tsx` and pass the SDK instance that was created in the parent component.

```tsx
/// GetidDocScanWrapper.tsx
import { View, StyleSheet, Button, Text } from 'react-native';
import React, { memo, useCallback, useEffect, useState } from 'react';
import HintMessage from './HintMessage';
import { type GetIdSdk, GetidSdkTypes } from '@get-id/react-native-sdk';
import { Alert } from 'react-native';

type DocScanProps = {
  sdk: GetIdSdk;
  docType: 'front' | 'back';
  onNextStep?: () => void;
  onComplete?: () => void;
};

type DocScanFunctions = {
  takePicture?: Function;
  capture?: Function;
  uploadDocument?: () => Promise<GetidSdkTypes.ScannedDocument>;
};

const GetidDocScanWrapper = ({
  sdk,
  onNextStep,
  onComplete,
  docType,
}: DocScanProps) => {
  const [GetIdDocScanCmp, setGetIdDocScanCmp] = useState<React.JSX.Element>();
  const [hintMessage, setHintMessage] = useState<string>('');
  const [sdkFunctions, setSdkFunctions] = useState<DocScanFunctions>({});

  const [uploadingDoc, setUploadingDoc] = useState(false);

  const onUserHintChange = useCallback((hint: any) => {
    console.log(`onUserHintChange: ${hint}`);
    setHintMessage(hint);
  }, []);

  const onDocumentScanServerValidationStarted = useCallback(() => {
    console.log(
      'onDocumentScanServerValidationStarted: Document scan server validation started'
    );
  }, []);

  const handleScannedDocument = (
    scannedDocument: GetidSdkTypes.ScannedDocument
  ) => {
    console.log('Scanned Document:', scannedDocument);
    if (scannedDocument.conclusion === 'back-side-missing') {
      onNextStep?.();
    }
    if (scannedDocument.conclusion === 'ok') {
      // Once the document conclusion is ok, you can submit the document for verification
      onComplete?.();
    }

    if (scannedDocument.conclusion === 'unknown-sides') {
      Alert.alert(
        'unknown-sides',
        'Cant recognize the document side, please try again'
      );
      setUploadingDoc(false);
    }
  };

  useEffect(() => {
    const initDocScan = async () => {
      const initialiseDocumentScan =
        docType === 'front'
          ? sdk.initialiseFrontDocumentScan
          : sdk.initialiseBackDocumentScan;

      const { component, ...functions } = await initialiseDocumentScan({
        onDocumentScanServerValidationStarted,
        onUserHintChange,
      });

      setGetIdDocScanCmp(() => component);
      setSdkFunctions(functions);
    };

    initDocScan();
  }, [sdk, onDocumentScanServerValidationStarted, onUserHintChange, docType]);

  return (
    <View style={styles.containerFullPage}>
      {GetIdDocScanCmp}

      <Text style={styles.titleDescription}>
        Please scan your {docType} document
      </Text>

      {uploadingDoc && <Text style={styles.textLoading}>Scanning...</Text>}
      {!uploadingDoc && <HintMessage message={hintMessage} />}

      <View style={styles.btnTakePicture}>
        <Button
          title="Capture"
          disabled={uploadingDoc}
          onPress={() => {
            // subcribe to the capture promise, whenever the takePicture func is called, this promise will be resolved
            sdkFunctions.capture?.().then((scannedDocument: any) => {
              handleScannedDocument(scannedDocument);
            });
          }}
        />
        <Button
          title="Take Picture"
          onPress={() => {
            setUploadingDoc(true);

            sdkFunctions.takePicture?.();
          }}
        />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  containerFullPage: {
    flex: 1,
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    justifyContent: 'flex-end',
  },
  btnTakePicture: {
    color: 'white',
    textAlign: 'center',
    marginBottom: 50,
    marginHorizontal: 10,
    // flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: 'rgba(0, 0, 0, 0.9)',
  },
  textLoading: {
    color: 'white',
    fontSize: 24,
    textAlign: 'center',
    fontWeight: 'bold',
    marginBottom: 50,
  },
  titleDescription: {
    color: 'white',
    fontSize: 16,
    textAlign: 'center',
    fontWeight: 'bold',
    marginBottom: 'auto',
    marginTop: 80,
    alignSelf: 'center',
  },
});

export default memo(GetidDocScanWrapper);
```

Then, we can use that component in our main root component. Here is the full code of `App.tsx`:

```tsx
// App.tsx
import { SafeAreaView, Button, StyleSheet, Text } from 'react-native';
import React, { useState } from 'react';
import { GetIdSdk } from '@get-id/react-native-sdk';
import GetidDocScanWrapper from './components/GetidDocScanWrapper';
import Config from 'react-native-config';

const API_URL = Config.API_URL || '';
const SDK_KEY = Config.SDK_KEY || '';

export default function App() {
  const [getidSdkInstance, setGetidSdkInstance] = useState<GetIdSdk>();
  const [step, setStep] = useState(0);
  const [verificationCompleted, setVerificationCompleted] = useState(false);
  const [loading, setLoading] = useState(false);

  const startGetIdSdk = () => {
    GetIdSdk.initialiseSDK({
      sdkKey: SDK_KEY,
      flow: 'doc-only',
      apiUrl: API_URL,
    })
      .then((sdk) => {
        setGetidSdkInstance(sdk);
      })
      .catch((error) => {
        console.error('Error:', error);
      });
  };

  const nextStep = () => setStep(step + 1);

  const onComplete = async () => {
    try {
      setLoading(true);
      await getidSdkInstance?.submitForVerification();
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setVerificationCompleted(true);
      setLoading(false);
    }
  };

  if (!getidSdkInstance) {
    return (
      <SafeAreaView style={styles.container}>
        <Text style={styles.header}> Klarna APP </Text>
        <Button title="Start GetId SDK" onPress={startGetIdSdk} />
      </SafeAreaView>
    );
  }

  if (verificationCompleted || loading) {
    return (
      <SafeAreaView style={styles.container}>
        <Text style={styles.header}>
          {loading ? 'Loading...' : 'Verification Completed'}
        </Text>
      </SafeAreaView>
    );
  }
  return (
    <SafeAreaView style={styles.container}>
      {step === 0 && (
        <GetidDocScanWrapper
          sdk={getidSdkInstance}
          onNextStep={nextStep}
          docType="front"
        />
      )}
      {step === 1 && (
        <GetidDocScanWrapper
          sdk={getidSdkInstance}
          onNextStep={nextStep}
          docType="back"
          onComplete={() => onComplete()}
        />
      )}
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  header: {
    fontSize: 30,
    fontWeight: 'bold',
    textAlign: 'center',
    margin: 10,
    fontFamily: 'monospace',
    marginVertical: 50,
  },
});
```

In the above code, we first check if the getidSdkInstance is available. If not, we display a "Loading" message. Once the instance is available, we render the GetidDocScanWrapper component and pass the sdk instance and an onComplete callback function that will be called when the front document scan is completed.

## Accessing config

You can find the full flow config on the `sdk` instance in `sdk.config.configuration`. Example:

```json
{
  ...
  "flow": [
    {
      "allowFromGallery": true,
      "allowSkippingValidation": 3,
      "component": "document",
      "countriesDocumentTypes": [],
      "requiredFields": [],
      "showDocumentChoice": false,
      "showGuide": true,
      "useNFC": false
    },
    {
      "component": "selfie",
      "showGuide": true
    },
    {
      "component": "liveness",
    }
  ],
  "name": "klarna-native",
  "responseCode": 200,
  ...
}
```

You can get the flow screens dynamically as follows:

```javascript
const screens = sdk.config.configuration.flow.map((s) => s.component); // ['document', 'selfie', 'liveness'];
```

You can get the document scan config as follows:

```javascript
const docConfig = sdk.config.configuration.flow.find(
  (s) => s.component === 'document'
);
const { allowFromGallery, useNFC } = docConfig;
```