npm package: @nafsi/idv-react-native-sdk
Description: JWT-based identity verification SDK for React Native with dynamic workflow support
Author: Nafsi
Latest version: 1.0.1

# Nafsi React Native SDK

JWT-based identity verification SDK for React Native with dynamic workflow support.

## Features

- 🔐 **JWT-Based Configuration** - Initialize SDK with a single JWT token containing all configuration
- 📸 **Automatic ID Cropping** - Precise geometric mapping to crop ID cards to exact dimensions (590x372px)
- 📷 **High-Quality Capture** - Maximum quality (95%) with accurate dimension detection for sharp images
- 🎯 **Accurate Cropping** - Real photo dimensions used for geometric mapping (no more hardcoded values)
- 🎭 **Dynamic Workflows** - Conditionally capture selfie based on backend workflow configuration
- 🎨 **Highly Customizable** - Customize colors, logos, images, messages, and button text
- 🌐 **Backend Branding Control** - Full branding control via `react_native_sdk_configs` in workflow
- 📦 **Compression Built-in** - Automatic payload compression using pako for bandwidth optimization
- ✅ **TypeScript Support** - Full TypeScript support with comprehensive type definitions
- ⚡ **Expo Compatible** - Works seamlessly with Expo-managed projects
- 🚀 **Auto-Start Demo Mode** - Perfect for marketing portals and demos

## Installation

```bash
npm install nafsi-react-native
# or
yarn add nafsi-react-native
```

### Peer Dependencies

Install required peer dependencies:

```bash
expo install expo-camera expo-image-manipulator expo-file-system react-native-svg
```

## Quick Start

```tsx
import React from 'react';
import {View} from 'react-native';
import NafsiVerification from 'nafsi-react-native';

export default function App() {
    const jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From your backend

    return (
        <View style={{flex: 1}}>
            <NafsiVerification
                token={jwtToken}
                onSuccess={(result) => {
                    console.log('Verification successful!', result);
                    // Navigate to success screen or handle result
                }}
                onError={(error) => {
                    console.error('Verification failed:', error);
                    // Show error message or retry
                }}
                onCancel={() => {
                    console.log('User cancelled verification');
                    // Navigate back
                }}
            />
        </View>
    );
}
```

## JWT Token Format

The SDK expects a JWT token with the following payload structure:

```json
{
  "workflowId": "your-workflow-id",
  "clientId": "your-client-id",
  "organisationId": 14,
  "apiUrl": "https://apisv2.windeal.co.ke/postdata",
  "refresh_token": "your-refresh-token",
  "config": "kenya_national_id",
  "iat": 1767688286,
  "exp": 1767691886
}
```

## Configuration Priority

The SDK uses a three-tier configuration system with the following priority order:

1. **Default values** (lowest priority) - Built-in SDK defaults
2. **Developer customization** (medium priority) - Props passed to `<NafsiVerification>`
3. **Workflow database config** (highest priority) - Configuration from `getWorkflowById` API response

This means that configuration from your workflow's `react_native_sdk_configs` or `web_sdk_configs` will **override** any
customization you provide via props.

### Workflow SDK Config Example

When you call `getWorkflowById`, the response may include SDK configuration:

```json
{
  "workflow": {
    "id": "workflow-123",
    "react_native_sdk_configs": {
      "logo": "https://example.com/logo.png",
      "title": "Welcome to Naweza Africa",
      "tagline": "Your leading Home Service Market in Africa!",
      "hero_image": "https://example.com/hero.png",
      "background_image": "https://example.com/background.jpg",
      "colors": {
        "primary": "#9333ea",
        "secondary": "#6b21a8",
        "text": "#1a1a1a",
        "background": "#dbeafe"
      }
    }
  }
}
```

This workflow config will automatically override your prop-based customization.

## Customization

### Theme Colors

```tsx
<NafsiVerification
    token={jwtToken}
    customization={{
        theme: {
            primaryColor: '#00b8ff',
            secondaryColor: '#17D27C',
            accentColor: '#fa764a',
            backgroundColor: '#ffffff',
            textColor: '#333333',
            buttonTextColor: '#ffffff',
        }
    }}
/>
```

### Landing Page

```tsx
<NafsiVerification
    token={jwtToken}
    customization={{
        landing: {
            backgroundImage: 'https://example.com/background.jpg',
            logoUrl: 'https://example.com/logo.png',
            heroMessage: 'Verify Your Identity',
            tagline: 'Quick and secure verification in minutes',
            getStartedButtonText: 'Start Verification',
        }
    }}
/>
```

### Messages

```tsx
<NafsiVerification
    token={jwtToken}
    customization={{
        messages: {
            idFrontTitle: 'Capture ID Front',
            idFrontSubtitle: 'Position your ID card within the frame',
            idBackTitle: 'Capture ID Back',
            idBackSubtitle: 'Position the back of your ID within the frame',
            selfieTitle: 'Take a Selfie',
            selfieSubtitle: 'Position your face within the oval',
            reviewTitle: 'Review Your Captures',
            startVerificationButton: 'Submit Verification',
            retakeButton: 'Retake',
            continueButton: 'Continue',
        }
    }}
/>
```

### Camera Settings

```tsx
<NafsiVerification
    token={jwtToken}
    customization={{
        camera: {
            idFrameColor: '#17D27C',
            selfieOutlineColor: '#17D27C',
            captureButtonColor: '#00b8ff',
        }
    }}
/>
```

## API Reference

### NafsiConfig Props

| Prop            | Type                                   | Required | Description                                         |
|-----------------|----------------------------------------|----------|-----------------------------------------------------|
| `token`         | `string`                               | Yes      | JWT token containing workflow and API configuration |
| `customization` | `NafsiCustomization`                   | No       | UI/UX customization options                         |
| `onSuccess`     | `(result: VerificationResult) => void` | No       | Callback fired when verification succeeds           |
| `onError`       | `(error: NafsiError) => void`          | No       | Callback fired when verification fails              |
| `onCancel`      | `() => void`                           | No       | Callback fired when user cancels verification       |
| `onStepChange`  | `(step: VerificationStep) => void`     | No       | Callback fired when user moves to a different step  |

### VerificationResult

```typescript
interface VerificationResult {
    status: 'success' | 'failed' | 'pending';
    verification_id?: string;
    extracted_data?: {
        full_name?: string;
        id_number?: string;
        date_of_birth?: string;
        place_of_birth?: string;
        gender?: string;
    };
    face_match?: {
        match: boolean;
        confidence: number;
    };
    liveness?: {
        is_live: boolean;
        confidence: number;
    };
    selfie_url?: string;
}
```

## Advanced Usage

### Using Hooks Directly

For custom flows, you can use the hooks directly:

```tsx
import {useNafsiFlow} from 'nafsi-react-native';

function CustomVerificationFlow() {
    const {
        currentStep,
        requiresSelfie,
        mergedCustomization, // Final merged configuration
        images,
        captureImage,
        submitVerification,
        isSubmitting,
    } = useNafsiFlow({
        token: jwtToken,
        onSuccess: handleSuccess,
        onError: handleError,
    });

    // Access the final merged configuration
    console.log('Using theme:', mergedCustomization.theme);
    console.log('Using landing config:', mergedCustomization.landing);

    // Custom rendering logic
    return <YourCustomUI/>;
}
```

### Understanding Configuration Merging

The SDK provides utilities to manually merge configurations if needed:

```typescript
import {mergeConfigurations, getWorkflowSdkConfig} from 'nafsi-react-native';

// Get SDK config from workflow
const workflowConfig = getWorkflowSdkConfig(workflow);

// Merge with your customization
const merged = mergeConfigurations(
    {
        theme: {primaryColor: '#00b8ff'},
        landing: {heroMessage: 'My Custom Message'}
    },
    workflowConfig
);

console.log('Merged config:', merged);
// Workflow config takes priority over your customization
```

### Using Services

Access low-level services for advanced use cases:

```typescript
import {
    ApiService,
    TokenService,
    WorkflowService,
    CompressionService,
} from 'nafsi-react-native';

const apiService = new ApiService('https://api.example.com');
const tokenService = new TokenService(apiService);
const workflowService = new WorkflowService(apiService);

// Decode JWT
const decoded = tokenService.decodeJWT(jwtToken);

// Refresh access token
const accessToken = await tokenService.refreshAccessToken(
    decoded.refresh_token,
    decoded.organisationId
);

// Fetch workflow
const workflow = await workflowService.fetchWorkflow(
    decoded.ss,
    decoded.clientId,
    accessToken,
    decoded.organisationId
);

// Check if selfie is required
const requiresSelfie = workflowService.requiresSelfie(workflow);

// Compress data
const compressed = CompressionService.compress({data: 'example'});
```

## Workflow Configuration

The SDK dynamically determines which steps to show based on the workflow fetched from your backend.

### Selfie Capture Conditions

Selfie capture is **conditionally shown** based on workflow steps:

- If workflow includes `face_matching`, `liveness_detection`, or `age_verification` → Selfie is required
- Otherwise → Selfie is skipped

Example workflow response:

```json
{
  "workflow": {
    "id": "workflow-123",
    "name": "KYC Verification",
    "steps": [
      {
        "name": "OCR Extraction",
        "order": 1,
        "required": true
      },
      {
        "name": "Face Matching",
        "order": 2,
        "required": false
      },
      {
        "name": "Liveness Detection",
        "order": 3,
        "required": false
      }
    ]
  }
}
```

In this example, **selfie would be captured** because `Face Matching` and `Liveness Detection` are present.

## Image Processing

### ID Card Cropping

- ✅ **Maximum quality capture** - 100% quality (quality: 1.0)
- ✅ **Actual dimension detection** - Uses real photo dimensions from camera
- ✅ **Accurate geometric mapping** - Maps screen overlay to exact photo coordinates
- ✅ **Automatic cropping** to green frame overlay
- ✅ **Output dimensions**: Exactly 590×372 pixels (ISO/IEC 7810 ID-1 standard)
- ✅ **High compression**: JPEG at 95% quality (COMPRESS_QUALITY: 0.95)

**How it works:**
1. Camera captures photo at native resolution (e.g., 4032×3024)
2. Photo dimensions are stored in state
3. Geometric algorithm maps green frame position to photo coordinates
4. Image cropped to exact frame area
5. Resized to standard 590×372px
6. Compressed at 95% quality

### Selfie Processing

- ✅ **Cropping to outline** - Crops to the oval outline shown on screen
- ✅ **Geometric mapping** - Same accurate algorithm as ID cards
- ✅ **Resizing** to 590px width (maintains aspect ratio)
- ✅ **High compression**: JPEG at 95% quality

**Selfie outline dimensions:**
- Screen overlay: 280×350 pixels (aspect ratio 0.8)
- Crops to this shape from full captured photo

### Base64 Encoding

All images are converted to base64 data URLs:

```
data:image/jpeg;base64,/9j/4AAQSkZJRg...
```

## Error Handling

```tsx
<NafsiVerification
    token={jwtToken}
    onError={(error) => {
        switch (error.code) {
            case 'INVALID_JWT':
                console.error('Invalid JWT token');
                break;
            case 'TOKEN_REFRESH_FAILED':
                console.error('Failed to refresh access token');
                break;
            case 'WORKFLOW_FETCH_FAILED':
                console.error('Failed to fetch workflow');
                break;
            case 'CAMERA_PERMISSION_DENIED':
                console.error('Camera permission denied');
                break;
            case 'VERIFICATION_FAILED':
                console.error('Verification submission failed');
                break;
            default:
                console.error('Unknown error:', error.message);
        }
    }}
/>
```

## API Integration

### Initialization Flow

1. **Decode JWT** → Extract `workflowId`, `clientId`, `apiUrl`, `organisationId`, `refresh_token`
2. **Refresh Access Token** → POST `/postdata` with `menu: 'auth_config', method: 'refreshAccessToken'`
3. **Fetch Workflow** → POST `/postdata` with `menu: 'flow', method: 'getWorkflowById'`
4. **Parse Steps** → Determine if selfie is required
5. **Show Landing** → Start verification flow

### Verification Submission

1. **Build Payload** → Include `client_id`, `work_flow_id`, ID images, optional selfie
2. **Compress Payload** → Use pako.deflateRaw (level 9) → base64 encode
3. **Submit** → POST `/postdata` with `menu: 'verification', method: 'submitVerification'`
4. **Handle Response** → Call `onSuccess` or `onError`

## TypeScript Support

The SDK is fully typed. Import types as needed:

```typescript
import type {
    NafsiConfig,
    VerificationResult,
    NafsiError,
    Workflow,
    DecodedJWT,
} from 'nafsi-react-native';
```

## Demo Mode & Marketing Portal

The SDK includes an auto-start demo mode perfect for marketing portals and sales presentations.

### Quick Demo Setup

See [`example/DEMO_SETUP.md`](./example/DEMO_SETUP.md) for comprehensive guide.

**Enable auto-start in example app:**

```tsx
// example/App.tsx
const AUTO_START_DEMO = true; // SDK starts automatically
const DEMO_TOKEN = `your-long-lived-demo-token`;
```

**Deploy demo to web:**

```bash
cd example
npx expo export:web
vercel --prod
```

**Result:** Instant live demo at `https://your-demo.vercel.app`

### Embedding in Marketing Portal

```html
<iframe
  src="https://nafsi-demo.vercel.app"
  style="width: 100%; height: 800px; border: 0;"
></iframe>
```

Perfect for showcasing SDK capabilities to prospects without requiring them to configure tokens.

## Documentation

- 📚 **[Distribution Guide](./DISTRIBUTION_GUIDE.md)** - Publishing to npm, CocoaPods, Maven Central
- 🎯 **[Demo Setup](./example/DEMO_SETUP.md)** - Marketing portal integration
- 🔄 **[SDK Initialization Flow](./SDK_INITIALIZATION_FLOW.md)** - Architecture deep dive
- 📖 **[API Documentation](https://docs.nafsi.ai)** - Full API reference (coming soon)
- 💡 **[Examples](./example)** - Complete working examples

## Requirements

- React Native >= 0.60.0
- Expo SDK >= 49.0.0 (if using Expo)
- iOS >= 13.0
- Android >= 6.0 (API level 23)

## License

MIT

## Support

- 📧 **Email**: support@nafsi.ai
- 🐛 **Issues**: [GitHub Issues](https://github.com/your-org/nafsi-react-native-sdk/issues)
- 💬 **Community**: [Slack Channel](https://nafsi.ai/slack)
- 📖 **Docs**: [docs.nafsi.ai](https://docs.nafsi.ai)

## Contributing

We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.

## Changelog

See [CHANGELOG.md](./CHANGELOG.md) for release notes.