npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nafsi/idv-react-native-sdk

v1.0.1

Published

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

Readme

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

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

Peer Dependencies

Install required peer dependencies:

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

Quick Start

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:

{
  "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:

{
  "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

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

Landing Page

<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

<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

<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

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:

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:

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:

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:

{
  "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

<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:

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 for comprehensive guide.

Enable auto-start in example app:

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

Deploy demo to web:

cd example
npx expo export:web
vercel --prod

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

Embedding in Marketing Portal

<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

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

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Changelog

See CHANGELOG.md for release notes.