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

@maiguard-hq/verify-react-native

v0.1.4

Published

MaiGuard Verify SDK for React Native — guided face capture, provider liveness, and facial verification

Readme

@maiguard-hq/verify-react-native

MaiGuard Verify for React Native provides a branded face-capture experience, continuous face framing and lighting guidance, reference-image-agnostic 1:1 face matching, and an integration boundary for provider-attested liveness.

The integrating business supplies a customer reference, verification purpose, user consent, and trusted portrait. MaiGuard controls the capture experience, verification policy, and final decision.

Choose the correct mode

| Environment | Liveness | Intended use | | --- | --- | --- | | Sandbox | Built-in blink and head-turn challenge | UI development and integration testing only | | Production | Approved provider adapter through providerLivenessHandler | Real identity verification |

The sandbox challenge confirms continuous face tracking and user movement, but it is not provider-attested presentation-attack detection. The SDK rejects the sandbox challenge when a pk_live_* key or environment: 'live' is used.

Production integrations must be provisioned for a MaiGuard-supported liveness provider. The provider adapter performs the official native capture and returns an opaque session ID. Do not build a production decision from the client-side blink and turn signals.

Requirements

| Requirement | Minimum | | --- | --- | | React | 18 | | React Native | 0.73 | | iOS | 15.5, or the higher minimum required by the host framework | | Android | API 26 | | react-native-svg | 15 | | react-native-vision-camera | 4.5 | | react-native-vision-camera-face-detector | 1.8 | | react-native-worklets-core | 1.5 |

  • Use a physical device for camera and liveness testing.
  • Expo Go is not supported because this package contains native code. Use an Expo development build, EAS build, or bare React Native build.
  • The microphone is not required.
  • Installing or upgrading the SDK requires a new native build. Reloading Metro is not sufficient.

Before integrating, obtain:

  1. A MaiGuard public key (pk_test_* for sandbox or pk_live_* for production).
  2. A stable customer reference from your system.
  3. A trusted reference portrait or an opaque reference held by your backend.
  4. Explicit biometric consent from the user.
  5. For production, the provider adapter and server-side authorization configured during MaiGuard onboarding.

Public keys may be bundled in a mobile application. Never bundle a secret key, provider credential, or privileged backend credential.

Expo installation

Install the SDK and compatible native dependencies:

npm install @maiguard-hq/verify-react-native
npx expo install react-native-svg react-native-vision-camera \
  react-native-vision-camera-face-detector react-native-worklets-core \
  expo-image-manipulator expo-build-properties

Add camera permissions, native platform minimums, and frame-processor support to app.json:

{
  "expo": {
    "plugins": [
      [
        "react-native-vision-camera",
        {
          "cameraPermissionText": "Allow $(PRODUCT_NAME) to use the camera for identity verification.",
          "enableMicrophonePermission": false,
          "enableFrameProcessors": true
        }
      ],
      [
        "expo-build-properties",
        {
          "android": { "minSdkVersion": 26 }
        }
      ]
    ]
  }
}

Enable Worklets in babel.config.js:

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['react-native-worklets-core/plugin'],
  };
};

Generate and run the native project:

npx expo prebuild
npx expo run:ios
# or
npx expo run:android

After changing the Babel configuration, restart Metro with npx expo start --clear.

Bare React Native installation

Install the SDK and peer dependencies:

npm install @maiguard-hq/verify-react-native \
  react-native-svg \
  react-native-vision-camera \
  react-native-vision-camera-face-detector \
  react-native-worklets-core

Then:

  1. Add NSCameraUsageDescription to ios/YourApp/Info.plist.
  2. Set the iOS deployment target to 15.5 or later.
  3. Add android.permission.CAMERA to android/app/src/main/AndroidManifest.xml.
  4. Set Android minSdkVersion to 26 or later.
  5. Add react-native-worklets-core/plugin to babel.config.js as shown above.
  6. Run npx pod-install and rebuild both native applications.

No microphone permission or manual React Native module linking is required.

Complete sandbox example

This example uses an already compressed reference portrait and compresses the captured selfie before submission. Keep the component inside a parent with a defined size; a full-screen container should use flex: 1.

import { useMemo } from 'react';
import { Text, View } from 'react-native';
import { manipulateAsync, SaveFormat } from 'expo-image-manipulator';
import {
  MaiGuardFaceVerification,
  type FaceVerificationResult,
  type PhotoEncoder,
} from '@maiguard-hq/verify-react-native';

const publicKey = process.env.EXPO_PUBLIC_MAIGUARD_VERIFY_KEY;

const encodeCapturedPhoto: PhotoEncoder = async (path) => {
  const uri = path.startsWith('file://') ? path : `file://${path}`;
  const image = await manipulateAsync(
    uri,
    [{ resize: { width: 720 } }],
    { base64: true, compress: 0.72, format: SaveFormat.JPEG },
  );

  if (!image.base64) throw new Error('The captured image could not be encoded.');
  return `data:image/jpeg;base64,${image.base64}`;
};

type Props = {
  customerId: string;
  referenceImageDataUrl: string;
  consentGranted: boolean;
  onClose: () => void;
  onVerified: (result: FaceVerificationResult) => void;
};

export function IdentityVerificationScreen({
  customerId,
  referenceImageDataUrl,
  consentGranted,
  onClose,
  onVerified,
}: Props) {
  const request = useMemo(() => ({
    customerId,
    verificationPurpose: 'onboarding' as const,
    referenceSource: 'custom' as const,
    referenceImage: {
      type: 'data_url' as const,
      dataUrl: referenceImageDataUrl,
    },
    country: 'NG',
    consent: {
      granted: true as const,
      basis: 'explicit_consent',
      scopes: ['biometric'],
    },
  }), [customerId, referenceImageDataUrl]);

  if (!publicKey) return <Text>MaiGuard Verify is not configured.</Text>;
  if (!consentGranted) return <Text>Biometric consent is required.</Text>;

  const handleComplete = (result: FaceVerificationResult) => {
    if (result.status === 'verified') onVerified(result);
    // `onComplete` also receives review, rejection, high-risk, and pending outcomes.
    // Keep those states distinct and apply your approved business workflow.
  };

  return (
    <View style={{ flex: 1 }}>
      <MaiGuardFaceVerification
        config={{
          apiKey: publicKey,
          allowActiveChallengeLiveness: true,
        }}
        request={request}
        photoEncoder={encodeCapturedPhoto}
        onComplete={handleComplete}
        onError={(error) => {
          console.warn('MaiGuard Verify failed', error.code, error.message);
        }}
        onCancel={onClose}
      />
    </View>
  );
}

EXPO_PUBLIC_* values are compiled into the application and are not secrets. Put only the MaiGuard public key there.

Country routing

Set request.country to the subject's ISO-2 country for non-Nigerian journeys (for example, country: 'GH'). The SDK normalizes explicit codes to uppercase and rejects empty, malformed, or conflicting subject jurisdictions before transport. If the country is omitted, a declared subject jurisdiction takes precedence, then a document issuing country; only an entirely unspecified jurisdiction keeps the legacy NG fallback. The providerLivenessHandler receives the resolved country. MaiGuard's API still determines whether the tenant and selected checks are available there; an SDK country value is not a coverage claim.

Reference portraits

MaiGuard does not dictate where the trusted portrait originates. It may come from BVN, NIN, a passport, a driver's licence, an internal profile, or a previous verified identity.

| Input | Use | | --- | --- | | string | An image data URL | | { type: 'data_url', dataUrl } | An explicit image data URL | | { type: 'uri', uri } | A local device image URI | | { type: 'secure_url', url } | A short-lived HTTPS URL readable without privileged headers | | { type: 'server_reference', referenceId } | An opaque reference resolved by your trusted verificationHandler |

For direct public-API verification, both the reference portrait and captured selfie are submitted inline:

  • Use JPEG unless your verification policy requires another supported format.
  • Resize images to approximately 720 pixels wide.
  • Aim for 350 KB or less per encoded image.
  • The total serialized verification request must remain below 1 MiB.
  • The face should be front-facing, sharp, unobstructed, and large enough to identify.

If your reference image requires authorization headers or must never enter JavaScript, keep it on your backend and use server_reference with verificationHandler.

Production integration

Production liveness uses two boundaries:

  • providerLivenessHandler opens the approved native provider capture and returns provider-attested evidence.
  • verificationHandler is required when the provider-selected live frame or reference portrait remains on your server.

1. Create the MaiGuard client

import {
  MaiGuardFaceVerification,
  VerifyApiClient,
  VerifyError,
  type FaceVerificationResult,
  type ProviderLivenessHandler,
  type VerificationHandler,
} from '@maiguard-hq/verify-react-native';

const verifyClient = new VerifyApiClient({
  apiKey: process.env.EXPO_PUBLIC_MAIGUARD_VERIFY_KEY!,
});

2. Bridge the approved provider adapter

createLivenessSession asks MaiGuard to select the provider configured for your tenant. Treat provider and params as opaque routing material and pass them only to the approved adapter.

const providerLivenessHandler: ProviderLivenessHandler = async ({
  customerId,
  verificationPurpose,
}) => {
  const startedAt = Date.now();
  const session = await verifyClient.createLivenessSession({
    country: 'NG',
    subject: { customerId, verificationPurpose },
  });

  const adapter = approvedLivenessAdapters[session.provider];
  if (!adapter) {
    throw new VerifyError(
      `No approved adapter is installed for ${session.provider}.`,
      'CHECK_UNAVAILABLE',
      { recoverable: false },
    );
  }

  const capture = await adapter.start({
    sessionId: session.sessionId,
    params: session.params,
  });

  return {
    sessionId: session.sessionId,
    durationMs: Date.now() - startedAt,
    // Include this only when the approved adapter returns the selected frame.
    capturedImageDataUrl: capture.capturedImageDataUrl,
  };
};

approvedLivenessAdapters represents the provider package supplied or approved during MaiGuard onboarding. Provider credentials must be short-lived and obtained from your authenticated backend; never hardcode them in the application.

3. Add server-mediated verification when required

When the provider-selected frame or trusted reference stays on the server, submit the opaque references to your authenticated backend. The handler must return FaceVerificationResult.

const verificationHandler: VerificationHandler = async (submission) => {
  const response = await fetch('https://your-api.example.com/identity/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${await getSignedInUserToken()}`,
    },
    body: JSON.stringify(submission),
  });

  if (!response.ok) throw new Error('Identity verification could not be completed.');
  return response.json() as Promise<FaceVerificationResult>;
};

Your backend must authenticate the user, validate that the customer and reference IDs belong to that user, resolve the provider-selected frame by livenessSessionId, call MaiGuard from the trusted server context, and return the normalized result. Do not accept arbitrary filesystem paths or unrestricted URLs from the application.

4. Render the production component

<MaiGuardFaceVerification
  config={{
    environment: 'live',
    providerLivenessHandler,
    verificationHandler,
  }}
  request={{
    customerId,
    verificationPurpose: 'account_recovery',
    referenceSource: 'nin',
    referenceImage: {
      type: 'server_reference',
      referenceId: authorityPortraitId,
    },
    country: 'NG',
    consent: {
      granted: true,
      basis: 'explicit_consent',
      scopes: ['biometric'],
    },
  }}
  onComplete={(result) => {
    if (result.status === 'verified') finishVerification(result);
    else handleNonVerifiedDecision(result);
  }}
  onError={handleVerificationError}
  onCancel={closeVerification}
/>

If the provider adapter returns capturedImageDataUrl and the reference image is an inline data URL, you may omit verificationHandler and supply the public apiKey; the SDK will submit directly to MaiGuard. Server-held frames and server_reference always require verificationHandler.

Results and component lifecycle

onComplete runs for every completed API decision, not only successful verification.

type FaceVerificationResult = {
  verificationId: string;
  status: string;
  confidenceScore: number | null;
  riskLevel: string | null;
  signals: string[];
  checks: Array<{
    check: string;
    status: string;
    confidence: number | null;
    signals: string[];
  }>;
  liveness: {
    status: 'pass' | 'fail' | 'not_attested';
    assurance: 'active_challenge' | 'provider_attested';
    challenges: Array<'blink' | 'turn'>;
    durationMs: number;
  };
};
  • Authorize the protected action only when your approved policy accepts the returned status and checks.
  • A sandbox result has liveness.status: 'not_attested' even when face matching succeeds.
  • The component displays its result until the host closes or unmounts it. Pass onCancel; after completion it powers the Done button.
  • Use onPhaseChange for host analytics or navigation guards. Never log images or provider evidence.
  • Failed flows show Try again and reset to the idle state. Use error.recoverable to decide whether the surrounding application should also offer another attempt.

Error handling

Every SDK failure is a VerifyError with code, message, recoverable, and an optional HTTP status.

| Code | Typical action | | --- | --- | | INVALID_CONFIG | Correct the key, URL, request, or adapter configuration | | CONSENT_REQUIRED | Collect explicit biometric consent before mounting the flow | | CAMERA_PERMISSION_DENIED | Explain how to enable camera permission in device settings | | CAMERA_UNAVAILABLE | Ask the user to use a supported physical device | | CHECK_UNAVAILABLE | Confirm production provider provisioning and native adapter setup | | LIVENESS_FAILED | Allow a policy-approved retry or send the case for review | | CAPTURE_FAILED | Retry capture after confirming the camera is available | | CAPTURE_ENCODING_FAILED | Verify the file URI and custom photoEncoder | | PAYLOAD_TOO_LARGE | Resize and compress both images before retrying | | REQUEST_TIMEOUT | Check connectivity and retry when appropriate | | AUTHENTICATION_FAILED | Check the public key and its environment | | RATE_LIMITED | Wait before retrying | | VERIFICATION_FAILED | Preserve the failed decision and follow your review policy | | NETWORK_ERROR | Check connectivity without converting the failure into approval | | UNKNOWN | Record non-biometric diagnostics and contact MaiGuard support |

Never convert an SDK, network, or provider failure into a fabricated verified result.

Headless API client

Use VerifyApiClient when you provide your own capture interface. The headless client accepts image data URLs; resolve local URIs before calling it.

const client = new VerifyApiClient({ apiKey: publicKey });

const result = await client.verifyFace(
  {
    customerId,
    verificationPurpose: 'transaction',
    referenceSource: 'previous_verification',
    referenceImage: referenceImageDataUrl,
    consent: { granted: true, basis: 'explicit_consent' },
  },
  {
    faceImage: capturedImageDataUrl,
    livenessSessionId,
    assurance: 'provider_attested',
    durationMs,
  },
);

Security and privacy boundary

  • Green alignment means exactly one continuously tracked face is inside the oval with acceptable measured lighting.
  • Face loss, multiple faces, or leaving the oval revokes alignment. A brief detector dropout during the instructed head turn is tolerated to avoid interrupting normal movement.
  • Lighting is measured in the native camera pipeline. Android GPU-backed frames are never locked for JavaScript reading.
  • Only the provider session can attest production liveness.
  • The SDK keeps biometric images only in component memory for the active flow.
  • Do not store reference portraits, selfies, or provider evidence in AsyncStorage, logs, analytics, or crash reports.
  • Server-held portraits require an authenticated verificationHandler and ownership checks.
  • Only a verified server decision receives the green success treatment.

Troubleshooting

The app says a native module is missing or crashes immediately after installation

Rebuild the native application. Expo Go and a Metro-only reload cannot load the SDK's native modules.

A Worklets or frame-processor error appears

Confirm react-native-worklets-core/plugin is present, restart Metro with a cleared cache, and rebuild the application.

The camera is unavailable

Use a physical device, confirm camera permission is declared, and check that another application is not holding the camera.

The request is too large

Resize both images to approximately 720 pixels wide and reduce JPEG quality until each encoded image is around 350 KB or less.

A live key reports that provider liveness is required

Install and configure the approved production provider adapter, then pass providerLivenessHandler. The sandbox gesture flow cannot run with a live key.

The flow completes but the screen remains visible

Close or unmount the verification screen from onComplete, or pass onCancel and let the user press Done.

Public exports

  • MaiGuardFaceVerification — complete capture and result UI.
  • VerifyApiClient — public-key transport for liveness-session bootstrap and headless face verification.
  • VerifyError — stable SDK error type and codes.
  • defaultPhotoEncoder — default local/HTTPS image-to-data-URL encoder.
  • evaluateLiveness and initialLivenessTracker — sandbox state-machine utilities.
  • Request, result, liveness-session, adapter, phase, photo-encoder, and theme types.

Support

License

MIT