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

@smileid/usesmileid

v12.0.0

Published

Official Smile ID React Native (Expo) SDK for identity verification: selfie capture, liveness checks, and document verification powered by on-device ML.

Downloads

271

Readme

Smile ID React Native (Expo) SDK

The UseSmileID React Native SDK lets you embed identity verification flows into your Expo or bare React Native app using a type-safe DSL builder. Compose screens like LEGO bricks — no predefined product flows, no inheritance.

SDK Size

Sizes are measured on every push to main and updated automatically by CI.

| Package | Download Size | Install Size | | ---------------------------------------- | ------------- | ------------ | | @smileid/usesmileid | — | — | | @smileid/usesmileid_bridge | — | — | | @smileid/usesmileid_platform_interface | — | — | | @smileid/usesmileid_mlkit_face | — | — | | @smileid/usesmileid_mlkit_document | — | — | | @smileid/usesmileid_huawei_face | — | — | | @smileid/usesmileid_huawei_document | — | — | | @smileid/usesmileid_vision_face | — | — | | @smileid/usesmileid_vision_document | — | — | | Sample app (iOS) | — | — | | Sample app (Android) | — | — |

Requirements

  • React Native 0.83+ or Expo SDK 55+
  • Node 20.18+
  • React 19.2+
  • Xcode (latest stable) — iOS 15.0+ deployment target
  • Android Gradle Plugin 8.x — compileSdk 37, minSdk 26 (the com.usesmileid:* 12.0.0 AARs are built with compileSdk 37 and enforce it as the consumer minimum — set android.compileSdkVersion=37 in gradle.properties if your project pins a lower level. minSdk 26 is required because VisionCamera's Frame.toArrayBuffer(), which the SDK's frame streaming depends on, is compiled out below it — set android.minSdkVersion=26)

Installation

Add the SDK, its native peer dependencies, and the analyzer providers that match your target platforms:

pnpm add @smileid/usesmileid \
         react-native-quick-crypto react-native-quick-base64 react-native-nitro-modules \
         @smileid/usesmileid_mlkit_face @smileid/usesmileid_mlkit_document \
         @smileid/usesmileid_vision_face @smileid/usesmileid_vision_document

(Use @smileid/usesmileid_huawei_{face,document} instead of mlkit on no-GMS Android targets.)

react-native-quick-crypto, react-native-quick-base64, and react-native-nitro-modules are required peer dependencies — the SDK uses them for native request signing (PBKDF2/HMAC). They must be installed in your app directly: React Native autolinking only links native modules declared in the app's own package.json, not a library's transitive ones, so the SDK cannot pull them in for you.

The provider packages each have native code (Android AAR / iOS SPM). Expo prebuild picks them up automatically via expo-module.config.json. For bare React Native, run npx pod-install after adding the packages and let Gradle resolve the AARs on the next Android build.


Quick Start

Mount <UseSmileIDBuilder> anywhere in your component tree. It is a regular React component — use it like any other.

import { CaptureType, JobType, UseSmileIDBuilder } from "@smileid/usesmileid";
import { faceAnalyzer } from "./src/providers";

export default function VerificationScreen() {
  return (
    <UseSmileIDBuilder
      builder={(b) => {
        b.network((n) => {
          n.config((c) => {
            c.jobType = JobType.smartSelfieEnrollment;
            c.token = "your-v3-token";
            c.partnerConfig((p) => {
              p.partnerId = "your-partner-id";
            });
          });
        });

        b.ml((m) => {
          m.analyzers((a) => {
            a.forCaptureType(CaptureType.selfie, (face) =>
              face.add(faceAnalyzer),
            );
          });
        });

        b.screens((screens) => {
          screens.instructions();
          screens.capture((c) => {
            c.captureType = CaptureType.selfie;
          });
          screens.preview();
          screens.processing();
        });

        b.onResult = (result) => {
          switch (result.status) {
            case "success":
              console.log("Captured:", result.value);
              break;
            case "failure":
              console.error("Error:", result.error);
              break;
          }
        };
      }}
    />
  );
}

./src/providers is a pair of platform-suffix files (providers.ios.ts and providers.android.ts) that select the right native analyzer per platform. See Federated providers below.


Full Builder Reference

useSmileIDBuilder takes a callback that receives a mutable builder. Top-level mutable properties (consentInformation, enhancedKYCParams, biometricKYCParams, documentVerificationParams, enhancedDocumentVerificationParams, userDetails, localizations, onResult, onAnalyticsEvent) are property-assigned. Nested blocks (config, theme, network, ml, screens) take closures.

The server issues userId and jobId and returns them in the submission response — partners receive both via onResult. Don't construct them client-side.

config — Global settings

b.config((c) => {
  c.enableDebugMode = false;
  c.allowOfflineMode = false;
  c.enableCrashReporting = true;
});

| Property | Type | Default | Description | | ---------------------- | --------- | ------- | ------------------------------------------------------------------------------------------ | | enableDebugMode | boolean | false | Shows a validation error screen on build failure | | allowOfflineMode | boolean | false | Allows the flow to run without a network connection | | enableCrashReporting | boolean | true | Enables Sentry crash reporting for the SDK. Set to false if your app runs its own Sentry |


theme — UI customisation

Each color slot accepts an AdaptiveColor {light, dark} pair via the color({...}) helper. The SDK resolves the active variant at mount time using the device's color scheme — partners supply both values up front.

b.theme((t) => {
  t.primaryColor = t.color("#1A73E8", "#4DA3FF");
  t.primaryForeground = t.color("#FFFFFF", "#000000");
  t.secondaryColor = t.color("#5F6368", "#9AA0A6");
  t.accentColor = t.color("#34A853", "#81C995");
  t.fontFamily = "Inter";
  t.buttonShape = 12;
  t.cardShape = 16;
});

| Property | Type | Default | Description | | ------------------- | --------------- | ----------- | ------------------------------------------------------ | | primaryColor | AdaptiveColor | SDK default | Main action colour (buttons, highlights) | | primaryForeground | AdaptiveColor | SDK default | Text/icons on primary colour | | secondaryColor | AdaptiveColor | SDK default | Secondary surface colour | | accentColor | AdaptiveColor | SDK default | Accent highlights | | fontFamily | string | SDK default | Font family loaded via expo-font or RN's font system | | buttonShape | number (dp) | SDK default | Corner radius for buttons | | cardShape | number (dp) | SDK default | Corner radius for cards |

Both defaultLightTheme and defaultDarkTheme are exported. They share typography / spacing / shapes and differ only in colors — partners can mix-and-match.

Localization

Every string rendered by the SDK can be overridden at the app level. Drop one translation JSON per locale, register once at app startup, and the SDK auto-resolves the device locale at flow mount.

import { UseSmileIDLocalizations } from "@smileid/usesmileid";
import frOverrides from "./l10n/fr.json";
import arOverrides from "./l10n/ar.json";

UseSmileIDLocalizations.register("fr", frOverrides);
UseSmileIDLocalizations.register("ar", arOverrides);
// l10n/fr.json
{
  "si_consent_allow": "Accepter",
  "si_consent_consent": "Je teste juste des trucs au hasard",
}

Match strategy: exact match ('pt-BR') → language-only fallback ('pt') → English defaults.

Override the device-locale resolver to plug in your own resolution (e.g. expo-localization):

import { getLocales } from "expo-localization";
UseSmileIDLocalizations.localeResolver = () =>
  getLocales()[0]?.languageCode ?? "en";

Per-flow override (wins over the registry) is still available via b.localizations = UseSmileIDLocalizations.fromOverrides(map).


network — API configuration

All fields are optional. Omit the network block entirely to use SDK defaults.

import { JobType, LogLevel } from "@smileid/usesmileid";

b.network((n) => {
  n.config((c) => {
    c.jobType = JobType.documentVerification;
    c.token = "your-v3-token";
    // Optional: refresh on 401 mid-flow. Invoked when the server returns
    // 401; return a fresh token and the SDK retries once.
    c.onTokenExpired = async (previous) => fetchFreshToken();
    c.partnerConfig((p) => {
      p.partnerId = "your-partner-id";
      p.callbackUrl = "https://example.com/callback";
      p.useSandbox = false;
    });
    c.logging((l) => {
      l.enabled = true;
      l.level = LogLevel.basic;
    });
  });
  n.timeouts((t) => {
    t.connect = 30;
    t.read = 60;
    t.write = 60;
    t.call = 120;
  });
  n.cache((c) => {
    c.enabled = true;
    c.maxSize = 100 * 1024 * 1024; // 100 MB
  });
  n.interceptors((i) => {
    i.add(i.gzip());
    i.add(
      i.logging((l) => {
        l.level = LogLevel.body;
      }),
    );
    i.add(myCustomInterceptor); // partner-supplied
  });
});

config block

| Property | Type | Default | Description | | ---------------- | ----------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | jobType | JobType | JobType.unknown | Job type sent with every request | | token | string | "" | Short-lived v3 auth token. Exchange your long-lived API key for one from your own backend (POST /v3/token) and supply it here. The SDK stamps it on every authed request. | | onTokenExpired | ((previousToken: string) => Promise<string>) \| undefined | undefined | Optional refresh callback. Invoked on a 401 Unauthorized response on an authed request. The SDK calls it with the token it was using, expects a fresh token back, and retries the failed request once. Concurrent 401s collapse to a single callback invocation. If the callback throws or the retry also returns 401, the original 401 surfaces unchanged. |

partnerConfig block

| Property | Type | Default | Description | | --------------- | ------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | | partnerId | string | "" | Your Smile ID partner ID | | callbackUrl | string | "" | Webhook URL for job results | | useSandbox | boolean | false | Route requests to the sandbox environment | | partnerParams | Record<string, string>? | undefined | Optional partner-defined key/value pairs forwarded as the partner_params JSON multipart part on Enhanced/Biometric KYC submissions |

logging block

| Property | Type | Default | Description | | --------- | ---------- | ---------------- | ------------------------------------- | | enabled | boolean | true | Enable network logging | | level | LogLevel | LogLevel.basic | none / basic / headers / body |

timeouts block (seconds)

| Property | Default | | --------- | ------- | | connect | 60 | | read | 60 | | write | 60 | | call | 120 |

cache block

| Property | Type | Default | | --------- | ---------------- | -------------------- | | enabled | boolean | true | | maxSize | number (bytes) | 52_428_800 (50 MB) |


ml — Federated machine-learning analyzers

Pick the analyzer that matches your platform / device fleet:

| Platform | Face | Document | Native dep | | ---------------- | --------------------------------- | ------------------------------------- | --------------------- | | iOS | @smileid/usesmileid_vision_face | @smileid/usesmileid_vision_document | Apple Vision (SPM) | | Android (GMS) | @smileid/usesmileid_mlkit_face | @smileid/usesmileid_mlkit_document | Google ML Kit (Maven) | | Android (no GMS) | @smileid/usesmileid_huawei_face | @smileid/usesmileid_huawei_document | Huawei HMS (Maven) |

import { CaptureType } from "@smileid/usesmileid";
import { faceAnalyzer, documentAnalyzer } from "./src/providers";

b.ml((m) => {
  m.analyzers((a) => {
    a.forCaptureType(CaptureType.selfie, (face) => face.add(faceAnalyzer));
    a.forCaptureType(CaptureType.document, (doc) => doc.add(documentAnalyzer));
  });
});

Important: each provider package's TS index calls requireNativeModule at module-load. Importing @smileid/usesmileid_mlkit_face on iOS (or @smileid/usesmileid_vision_face on Android) crashes the bundle at evaluation time. Use platform-suffix files so Metro picks the right one and the wrong-platform package never loads:

// providers.ios.ts
import { useSmileIDVisionFace } from "@smileid/usesmileid_vision_face";
import { useSmileIDVisionDocument } from "@smileid/usesmileid_vision_document";
export const faceAnalyzer = useSmileIDVisionFace;
export const documentAnalyzer = useSmileIDVisionDocument;

// providers.android.ts
import { useSmileIDMlkitFace } from "@smileid/usesmileid_mlkit_face";
import { useSmileIDMlkitDocument } from "@smileid/usesmileid_mlkit_document";
export const faceAnalyzer = useSmileIDMlkitFace;
export const documentAnalyzer = useSmileIDMlkitDocument;

Add "moduleSuffixes": [".ios", ".android", ""] to your tsconfig.json so tsc resolves ./src/providers correctly. See sample-expo/ for a complete reference.


screens — Flow composition

Call screen functions directly inside the screens closure. The flow navigates through them in the order they are declared.

import { CaptureType } from "@smileid/usesmileid";

b.screens((screens) => {
  screens.consent((c) => {
    c.partnerName = "Acme Corp";
    c.partnerIcon = <Image source={require("./assets/logo.png")} style={{ width: 36, height: 36 }} />;
    c.partnerPrivacyPolicyUrl = "https://acme.com/privacy";
    c.showAttribution = true;
    c.onConsentGranted = (info) => console.log("Consent:", info);
  });
  screens.instructions((i) => {
    i.title = "Verify your identity";
    i.description = "You will need a selfie and a document.";
    i.showAttribution = true;
  });
  screens.permission((p) => {
    p.cameraRationale = "Camera access required.";
  });
  screens.capture((cap) => {
    cap.captureType = CaptureType.selfie;
    cap.selfie((sel) => {
      sel.allowAgentMode = false;
      sel.enableEnhancedLiveness = true;
    });
  });
  screens.capture((cap) => {
    cap.captureType = CaptureType.document;
    cap.document((doc) => {
      doc.autoCapture = true;
      doc.autoCaptureTimeout = 10;
      doc.allowGalleryUpload = false;
      doc.captureBothSides = true;
      doc.allowSkipBack = false;
      doc.knownIdAspectRatio = 1.586; // CR-80 card ratio (optional)
    });
  });
  screens.preview((p) => {
    p.allowRetake = true;
  });
  screens.processing((p) => {
    p.showProgressPercentage = true;
  });
});

Screen types

| Screen | How to add | Key properties | | ---------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Consent | screens.consent((c) => ...) | partnerName, partnerIcon, partnerPrivacyPolicyUrl, showAttribution, onConsentGranted, allowButton, denyButton (partnerIcon and partnerPrivacyPolicyUrl are required) | | Instructions | screens.instructions((i) => ...) | title, description, showAttribution, continueButton | | Permission | screens.permission((p) => ...) | cameraRationale, continueButton | | Selfie capture | screens.capture((cap) => { cap.captureType = CaptureType.selfie; cap.selfie(...); }) | allowAgentMode, enableEnhancedLiveness | | Document capture | screens.capture((cap) => { cap.captureType = CaptureType.document; cap.document(...); }) | autoCapture, allowGalleryUpload, captureBothSides, allowSkipBack, knownIdAspectRatio | | Preview | screens.preview((p) => ...) | allowRetake, continueButton, cancelButton | | Processing | screens.processing((p) => ...) | showProgressPercentage, continueButton, retryButton, exitButton |

Button slots receive { onPress: () => void; isEnabled: boolean } and return any React node, so partners can ship fully custom buttons without losing the orchestrator's wiring.

Custom screens & button slots — useFlowNavigation()

Inside any partner component rendered under <UseSmileIDBuilder> (custom button slot, fully replaced screen, partner widget), call useFlowNavigation() to drive the flow without prop-drilling.

import { useFlowNavigation } from "@smileid/usesmileid";
import { Pressable, Text } from "react-native";

function ExitButton() {
  const nav = useFlowNavigation();
  return (
    <Pressable onPress={() => nav.exit()}>
      <Text>Cancel</Text>
    </Pressable>
  );
}

The hook returns the active FlowNavigationManager:

| Method | Behaviour | | --------------- | --------------------------------------------------------------------------------------------------------------- | | next() | Advance to the next screen. From the last step, calls onResult({ status: 'success', ... }). | | back() | Decrement. From the first step, calls onResult({ status: 'failure', error: ... }) with cancelled semantics. | | exit(reason?) | Exit immediately — useful for "abort" buttons. Defaults to cancelled. | | captured | Snapshot of artifacts collected so far (selfiePath, livenessPaths, frontDocumentPath, etc). |

Calling useFlowNavigation() outside <UseSmileIDBuilder> throws — the misuse is loud at first render rather than silently no-op-ing.


Top-level builder callbacks

| Property | Type | Description | | ------------------ | ----------------------------------------------------------- | --------------------------------------------------------- | | onResult | (result: UseSmileIDResult<JobSubmissionResponse>) => void | Called once when the flow finishes (success or failure) | | onAnalyticsEvent | ((event: UseSmileIDAnalyticsEvent) => void) \| undefined | Optional. Called for each analytics event during the flow |


Job-specific parameters

Each job type that needs identity fields reads them from a top-level builder property. The shared user_details part (b.userDetails) is required by every KYC and document job; country is the ISO 3166-1 alpha-2 code.

| Job type | Builder property | Required fields | Notes | | -------------------------------------- | -------------------------------------- | ------------------------------- | ------------------------------------------------------------------------ | | JobType.enhancedKyc | b.enhancedKYCParams | idType, idNumber, country | Optional bankCode, mobileOperator | | JobType.biometricKyc | b.biometricKYCParams | idType, idNumber, country | useEnrolledImage toggles the enrolled-image path | | JobType.documentVerification | b.documentVerificationParams | country | idType optional — omit it to let the backend classify the document | | JobType.enhancedDocumentVerification | b.enhancedDocumentVerificationParams | country, idType | idType required — drives the ID-authority cross-check |

// Document Verification — idType optional
b.documentVerificationParams = { country: "KE" };
b.userDetails = {
  givenNames: "Jane",
  lastName: "Doe",
  email: "[email protected]",
};

// Enhanced Document Verification — idType required
b.enhancedDocumentVerificationParams = { country: "KE", idType: "NATIONAL_ID" };
b.userDetails = {
  givenNames: "Jane",
  lastName: "Doe",
  phoneNumber: "+254700000000",
};

user_details requires non-blank givenNames and lastName plus at least one of email / phoneNumber. Pre-flight any payload with b.validateDocumentVerificationParams(...), b.validateEnhancedDocumentVerificationParams(...), or b.validateUserDetails(...) before assigning.


Handling results

Set b.onResult before (or during) the builder block. It is called once when the flow finishes.

b.onResult = (result) => {
  switch (result.status) {
    case "success":
      console.log(`Job ${result.value.jobId} status=${result.value.status}`);
      break;
    case "failure":
      console.error("flow failed:", result.error);
      break;
  }
};

The success branch carries a JobSubmissionResponse with jobId, userId, status, message, and an optional createdAt. Partner-supplied inputs (captured frames, identity fields, consent) are not echoed back — keep references at the call site if you need them after the flow.

UseSmileIDResult<T> is a discriminated union — result.status narrows it, no try / catch needed.

Forwarding failures to a crash reporter

Every exception the SDK surfaces extends UseSmileIDException, which carries:

  • errorCode — a stable UPPER_SNAKE_CASE string (e.g. "NETWORK_TIMEOUT", "BUILDER_VALIDATION_ERROR") suitable for grouping. Stable across SDK releases, so Sentry pivots and analytics dashboards keyed on it keep working after upgrades.
  • message — partner-safe message; never contains PII.
  • suggestedFix — concrete remediation hint for triage.
  • cause — the wrapped underlying error (AxiosError / Error) for stack trace forwarding.

Forward failures to Sentry, Bugsnag, or Crashlytics from inside the failure branch of onResult:

import * as Sentry from "@sentry/react-native";
import { UseSmileIDException } from "@smileid/usesmileid";

b.onResult = (result) => {
  if (result.status === "success") {
    myAnalytics.recordSuccess(result.value.jobId);
    return;
  }
  const error = result.error;
  if (error instanceof UseSmileIDException) {
    Sentry.captureException(error.cause ?? error, {
      tags: { "smileid.error_code": error.errorCode ?? "unknown" },
      extra: { "smileid.suggested_fix": error.suggestedFix },
    });
  }
};

The wrapped cause carries the original stack trace so crash-reporter grouping keys off the underlying transport / IO failure, not the SDK's wrapper class. errorCode rides as a tag so you can filter the crash board by failure category.


Analytics

Set b.onAnalyticsEvent to receive a stream of events fired at key moments in the flow. Each event carries a flat Record<string, string> that you can forward directly to any analytics backend.

import * as Analytics from "expo-firebase-analytics";

b.onAnalyticsEvent = (event) => {
  // Forward to Firebase, Mixpanel, Segment, or your own backend
  Analytics.logEvent(event.type, event.extras);
};

Every event automatically includes session_id and timestamp (epoch milliseconds) so you can correlate events across a single flow run without any extra bookkeeping.

Event reference

| Event | When fired | Key extras | | ------------------- | ---------------------------------- | --------------------------------------------------------------------------------- | | flow_started | Flow initialises | job_type | | screen_viewed | Each screen becomes active | screen_name | | consent_captured | User grants consent | decision: "granted" | | selfie_captured | Selfie + liveness captured | liveness_image_count | | document_captured | Document image captured | document_side: "front" or "both" | | retake_requested | User navigates back to redo a step | — | | job_submitted | API submission begins | job_type, attempt | | flow_completed | Flow finishes | result: "success" or "failure", job_id (success), error_message (failure) |

onAnalyticsEvent is undefined by default — omit it and no events are delivered.


Pre-supplied consent

If the user already consented in a previous session, skip the consent screen by supplying consentInformation before building:

import {
  UseSmileIDBuilder,
  type ConsentInformation,
} from "@smileid/usesmileid";

<UseSmileIDBuilder
  builder={(b) => {
    const consentInfo: ConsentInformation = {
      /* … */
    };
    b.consentInformation = consentInfo;
    b.screens((screens) => {
      // do not add a consent screen when consentInformation is set —
      // the builder will reject it
      screens.instructions();
      screens.capture((c) => {
        c.captureType = CaptureType.selfie;
      });
    });
  }}
/>;

If consentInformation is malformed (missing required fields, invalid timestamps), <UseSmileIDBuilder> routes the failure through onResult.failure carrying a typed BuilderValidationException — see Validation below.


Validation

<UseSmileIDBuilder> validates eagerly: it surfaces every missing or invalid field the moment the closure returns. Partners receive a typed BuilderValidationException via onResult.failureerror.validationState.issues carries every rule that failed, not just the first.

<UseSmileIDBuilder
  builder={(b) => {
    // …
    b.onResult = (result) => {
      if (
        result.status === "failure" &&
        result.error instanceof BuilderValidationException
      ) {
        for (const issue of result.error.validationState.issues) {
          console.error("[SmileID]", issue.errorCode, issue.message);
        }
      }
    };
  }}
/>

Triggers cover the validation rules — empty screens, ordering invariants, duplicate non-capture screens, capture without ML config, processing without network config, unknown job type at processing time, double-source consent, malformed consentInformation, blank KYC params. Set b.config((c) => { c.enableDebugMode = true; }) to additionally render ValidationErrorScreen on-device with every failed rule during development. In production the screen does not mount — partners only see the failure via onResult.failure.


Full example — Document verification

import { Alert, StyleSheet } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import {
  CaptureType,
  JobType,
  LogLevel,
  UseSmileIDBuilder,
} from "@smileid/usesmileid";
import { faceAnalyzer, documentAnalyzer } from "./src/providers";

export function DocumentVerificationScreen({
  onComplete,
}: {
  onComplete: () => void;
}) {
  return (
    <SafeAreaView style={styles.container} edges={["top", "bottom"]}>
      <UseSmileIDBuilder
        builder={(b) => {
          // Document Verification requires its params + userDetails.
          b.documentVerificationParams = {
            country: "KE",
            idType: "NATIONAL_ID",
          };
          b.userDetails = {
            givenNames: "Jane",
            lastName: "Doe",
            email: "[email protected]",
          };

          b.config((c) => {
            c.enableDebugMode = false;
            c.enableCrashReporting = true;
          });

          b.theme((t) => {
            t.primaryColor = t.color("#1A73E8", "#4DA3FF");
            t.buttonShape = 12;
          });

          b.network((n) => {
            n.config((c) => {
              c.jobType = JobType.documentVerification;
              c.token = "your-v3-token";
              c.partnerConfig((p) => {
                p.partnerId = "your-partner-id";
                p.callbackUrl = "https://example.com/callback";
                p.useSandbox = true;
              });
              c.logging((l) => {
                l.enabled = true;
                l.level = LogLevel.basic;
              });
            });
            n.timeouts((t) => {
              t.connect = 30;
              t.call = 120;
            });
          });

          b.ml((m) => {
            m.analyzers((a) => {
              a.forCaptureType(CaptureType.selfie, (face) =>
                face.add(faceAnalyzer),
              );
              a.forCaptureType(CaptureType.document, (doc) =>
                doc.add(documentAnalyzer),
              );
            });
          });

          b.screens((screens) => {
            screens.consent((c) => {
              c.partnerName = "Acme Corp";
              c.partnerPrivacyPolicyUrl = "https://acme.com/privacy";
            });
            screens.instructions((i) => {
              i.title = "Verify your identity";
              i.description = "You will need a selfie and a document.";
            });
            screens.capture((cap) => {
              cap.captureType = CaptureType.document;
              cap.document((doc) => {
                doc.autoCapture = true;
                doc.captureBothSides = true;
                doc.allowGalleryUpload = false;
              });
            });
            screens.preview((p) => {
              p.allowRetake = true;
            });
            screens.processing();
          });

          b.onResult = (result) => {
            switch (result.status) {
              case "success":
                Alert.alert("Done", JSON.stringify(result.value));
                break;
              case "failure":
                Alert.alert("Failed", String(result.error));
                break;
            }
            onComplete();
          };

          b.onAnalyticsEvent = (event) => {
            console.log(
              `[SmileID] ${event.type} — session: ${event.extras["session_id"]}`,
              event.extras,
            );
          };
        }}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#FFFFFF" },
});