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

@swan-admin/swan-native-component

v0.1.2

Published

swan's react native app

Downloads

18

Readme

@swan-admin/swan-native-component

The official React Native SDK for Swan's Body & Face Scanning suite. Drop-in components for onboarding, device calibration, and scanning — no WebView setup required.


Table of Contents


Installation

npm install @swan-admin/swan-native-component react-native-webview react-native-safe-area-context
# or
yarn add @swan-admin/swan-native-component react-native-webview react-native-safe-area-context

For iOS, install pods:

cd ios && pod install

Setup & Permissions

The SDK uses the device camera for scanning. You must add native permissions.

iOS (ios/<ProjectName>/Info.plist)

<key>NSCameraUsageDescription</key>
<string>We need camera access to perform the body scan measurements.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need microphone access to initialize the video stream.</string>

Note: NSMicrophoneUsageDescription is required by iOS to initialize the video stream, even if audio is not recorded.

Android (android/app/src/main/AndroidManifest.xml)

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.webkit.PermissionRequest" />

After editing permissions, rebuild your native app:

npx react-native run-ios   # or run-android

Quick Start

import { SwanWebView } from '@swan-admin/swan-native-component';

<SwanWebView
  component="Onboarding"
  config={{
    steps: [
      { type: 'gender' },
      { type: 'email' },
      { type: 'name' },
      { type: 'height' },
    ],
  }}
  onCallback={(name, data) => {
    if (name === 'onComplete') {
      console.log('User data:', data);
    }
  }}
/>

API Reference

<SwanWebView />

The single component exposed by this package. It handles all WebView setup, bridge communication, and lifecycle management internally.

| Prop | Type | Required | Description | |------|------|----------|-------------| | component | SwanComponentType | Yes | Which component to render. One of: "Onboarding", "FocalLength", "Educational", "BodyScan", "FaceScan" | | config | object | No | Props passed to the rendered component (see each component below) | | onCallback | (name: string, data: any) => void | No | Handler for events emitted by the component |


Components

Onboarding

Collects user info (email, name, gender, height) in a step-by-step flow.

Config:

| Field | Type | Required | Description | |-------|------|----------|-------------| | steps | Step[] | Yes | Steps to display | | config | Config | No | Styling/branding config |

Callbacks:

| Name | Payload | Description | |------|---------|-------------| | onComplete | Record<string, any> | All collected values: { email, name, gender, height } | | onDeviceDetected | FocalLengthOutput | Device info auto-detected during onboarding | | onStepComplete | { type, value } | Fired after each individual step |

Example:

<SwanWebView
  component="Onboarding"
  config={{
    steps: [
      { type: 'gender' },
      { type: 'email' },
      { type: 'name' },
      { type: 'height' },
    ],
    config: {
      logo: 'https://example.com/logo.png',
      language: 'English',
    },
  }}
  onCallback={(name, data) => {
    if (name === 'onComplete') {
      console.log('User data:', data);
    }
    if (name === 'onStepComplete') {
      console.log(`Step ${data.type} finished:`, data.value);
    }
  }}
/>

FocalLength

Detects the device camera's focal length, required for accurate body measurements.

Config:

| Field | Type | Required | Description | |-------|------|----------|-------------| | config | Config | No | Styling/branding config |

Callbacks:

| Name | Payload | Description | |------|---------|-------------| | onComplete | FocalLengthOutput | { brandName, modelName, focalLength } |

Example:

<SwanWebView
  component="FocalLength"
  config={{}}
  onCallback={(name, data) => {
    if (name === 'onComplete') {
      console.log('Focal length:', data.focalLength);
      console.log('Model:', data.modelName);
    }
  }}
/>

Educational

Shows tutorial/instructions before the scanning process.

Config:

| Field | Type | Required | Description | |-------|------|----------|-------------| | gender | "male" \| "female" | No | Customizes tutorial content | | sections | ("full" \| "body" \| "face")[] | No | Which sections to show | | config | Config | No | Styling/branding config | | isCustom | boolean | No | Custom mode flag |

Callbacks:

| Name | Payload | Description | |------|---------|-------------| | onComplete | none | User finished the tutorial |

Example:

<SwanWebView
  component="Educational"
  config={{
    gender: 'male',
    sections: ['body', 'face'],
  }}
  onCallback={(name) => {
    if (name === 'onComplete') {
      // Navigate to scan
    }
  }}
/>

BodyScan

The core body measurement camera interface. Supports two modes:

Active Scantoken and userDetails are required:

| Field | Type | Required | Description | |-------|------|----------|-------------| | token | string | Yes | Auth token | | userDetails | UserDetails | Yes | User info for scan | | config | Config | No | Styling/branding config | | isError | false | No | Must be false or omitted | | isSuccess | false | No | Must be false or omitted |

Terminal State — renders only the error/success UI, token and userDetails become optional:

| Field | Type | Required | Description | |-------|------|----------|-------------| | token | string | No | Auth token (optional in terminal state) | | userDetails | UserDetails | No | User info (optional in terminal state) | | config | Config | No | Styling/branding config | | isError | true | No | Show error UI | | isSuccess | true | No | Show success UI |

Callbacks:

| Name | Payload | Description | |------|---------|-------------| | onScanStart | none | Scan started | | onUploadStart | none | Video upload started | | onUploadEnd | none | Video upload finished | | onMeasurementSocketStart | none | Measurement WebSocket opened | | onMeasurementSocketClose | none | Measurement WebSocket closed | | onIntermediateScanSuccess | data | Intermediate result received | | onScanSuccess | data | Final scan success with measurements | | onScanError | error | Scan failed | | onRetry | none | User requested retry | | onComplete | none | Component flow finished |

Example (Active Scan):

<SwanWebView
  component="BodyScan"
  config={{
    token: 'your-auth-token',
    userDetails: {
      email: '[email protected]',
      userName: 'John',
      shopDomain: 'yourstore.myshopify.com',
      gender: 'male',
      heightInCm: 175,
      scanType: 'clothing_scan',
      deviceFocalLength: 26,
      deviceModelName: 'iPhone 14',
    },
    config: {
      logo: 'https://example.com/logo.png',
    },
  }}
  onCallback={(name, data) => {
    if (name === 'onScanSuccess') {
      console.log('Measurements:', data);
    } else if (name === 'onScanError') {
      console.error('Scan failed:', data);
    }
  }}
/>

Example (Terminal State — show success UI):

<SwanWebView
  component="BodyScan"
  config={{
    isSuccess: true,
  }}
/>

Example (Terminal State — show error UI):

<SwanWebView
  component="BodyScan"
  config={{
    isError: true,
  }}
/>

FaceScan

Facial analysis via the front camera. Supports two modes:

Active Scantoken and userDetails are required:

| Field | Type | Required | Description | |-------|------|----------|-------------| | token | string | Yes | Auth token | | userDetails | FaceScanMetaData | Yes | User info for scan | | config | Config | No | Styling/branding config | | isError | false | No | Must be false or omitted | | isSuccess | false | No | Must be false or omitted |

Terminal State — renders only the error/success UI, token and userDetails become optional:

| Field | Type | Required | Description | |-------|------|----------|-------------| | token | string | No | Auth token (optional in terminal state) | | userDetails | FaceScanMetaData | No | User info (optional in terminal state) | | config | Config | No | Styling/branding config | | isError | true | No | Show error UI | | isSuccess | true | No | Show success UI |

Callbacks:

| Name | Payload | Description | |------|---------|-------------| | onScanStart | none | Scan started | | onUploadStart | none | Video upload started | | onUploadEnd | none | Video upload finished | | onMeasurementSocketStart | none | Measurement WebSocket opened | | onMeasurementSocketClose | none | Measurement WebSocket closed | | onScanSuccess | data | Scan success | | onScanError | error | Scan failed | | onRetry | none | User requested retry |

Example (Active Scan):

<SwanWebView
  component="FaceScan"
  config={{
    token: 'your-auth-token',
    userDetails: {
      email: '[email protected]',
      shopDomain: 'yourstore.myshopify.com',
      gender: 'male',
      deviceFocalLength: 26,
    },
    config: {
      logo: 'https://example.com/logo.png',
    },
  }}
  onCallback={(name, data) => {
    if (name === 'onScanSuccess') {
      console.log('Face scan done:', data);
    } else if (name === 'onScanError') {
      console.error('Face scan failed:', data);
    }
  }}
/>

Example (Terminal State — show success UI):

<SwanWebView
  component="FaceScan"
  config={{
    isSuccess: true,
  }}
/>

Example (Terminal State — show error UI):

<SwanWebView
  component="FaceScan"
  config={{
    isError: true,
  }}
/>

Interfaces

UserDetails (for BodyScan)

| Field | Type | Required | Description | |-------|------|----------|-------------| | email | string | Yes | User email | | userName | string | No | User display name | | shopDomain | string | Yes | Shopify store domain | | gender | "male" \| "female" | Yes | User gender | | heightInCm | number | Yes | User height in centimeters | | scanType | string | Yes | e.g. "clothing_scan" | | deviceFocalLength | number | Yes | From FocalLength step | | deviceModelName | string | Yes | From FocalLength step | | callbackUrl | string | No | Webhook URL for results |

FaceScanMetaData (for FaceScan)

| Field | Type | Required | Description | |-------|------|----------|-------------| | email | string | Yes | User email | | shopDomain | string | Yes | Shopify store domain | | gender | "male" \| "female" | Yes | User gender | | deviceFocalLength | number | Yes | From FocalLength step | | callbackUrl | string | No | Webhook URL for results |

FocalLengthOutput

| Field | Type | Description | |-------|------|-------------| | brandName | string | Device brand | | modelName | string | Device model | | focalLength | number | Camera focal length |

Config

| Field | Type | Description | |-------|------|-------------| | style | StyleConfig | Custom styling | | logo | string | Logo URL | | loader | string | Custom loader URL | | language | string | One of: "English", "Hindi", "German", "French", "Spanish", "Arabic", "Italian" |

Step

| Field | Type | Required | Description | |-------|------|----------|-------------| | type | string | Yes | "email", "name", "height", or "gender" | | value | any | No | Pre-filled value | | order | number | No | Display order | | isVisible | boolean | No | Show/hide step |


Full Integration Example

The typical integration is a state machine that moves through the steps sequentially, passing collected data forward.

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { SwanWebView } from '@swan-admin/swan-native-component';

enum Step {
  START,
  ONBOARDING,
  FOCAL_LENGTH,
  EDUCATIONAL,
  BODY_SCAN,
  FACE_SCAN,
  FINISH,
}

export default function App() {
  const [step, setStep] = useState(Step.START);
  const [userData, setUserData] = useState<any>({});
  const [deviceData, setDeviceData] = useState<any>({});

  if (step === Step.START) {
    return (
      <View style={styles.center}>
        <Text style={styles.title}>Swan Demo</Text>
        <TouchableOpacity style={styles.button} onPress={() => setStep(Step.ONBOARDING)}>
          <Text style={styles.buttonText}>Start</Text>
        </TouchableOpacity>
      </View>
    );
  }

  if (step === Step.ONBOARDING) {
    return (
      <SwanWebView
        component="Onboarding"
        config={{
          steps: [
            { type: 'gender' },
            { type: 'email' },
            { type: 'name' },
            { type: 'height' },
          ],
        }}
        onCallback={(name, data) => {
          if (name === 'onComplete') {
            setUserData(data);
            setStep(Step.FOCAL_LENGTH);
          }
        }}
      />
    );
  }

  if (step === Step.FOCAL_LENGTH) {
    return (
      <SwanWebView
        component="FocalLength"
        config={{}}
        onCallback={(name, data) => {
          if (name === 'onComplete') {
            setDeviceData(data);
            setStep(Step.EDUCATIONAL);
          }
        }}
      />
    );
  }

  if (step === Step.EDUCATIONAL) {
    return (
      <SwanWebView
        component="Educational"
        config={{ gender: userData.gender }}
        onCallback={(name) => {
          if (name === 'onComplete') {
            setStep(Step.BODY_SCAN);
          }
        }}
      />
    );
  }

  if (step === Step.BODY_SCAN) {
    return (
      <SwanWebView
        component="BodyScan"
        config={{
          token: 'your-auth-token',
          userDetails: {
            email: userData.email,
            userName: userData.name,
            shopDomain: 'yourstore.myshopify.com',
            gender: userData.gender,
            heightInCm: userData.height,
            scanType: 'clothing_scan',
            deviceFocalLength: deviceData.focalLength,
            deviceModelName: deviceData.modelName,
          },
        }}
        onCallback={(name, data) => {
          if (name === 'onScanSuccess') {
            setStep(Step.FACE_SCAN);
          }
        }}
      />
    );
  }

  if (step === Step.FACE_SCAN) {
    return (
      <SwanWebView
        component="FaceScan"
        config={{
          token: 'your-auth-token',
          userDetails: {
            email: userData.email,
            shopDomain: 'yourstore.myshopify.com',
            gender: userData.gender,
            deviceFocalLength: deviceData.focalLength,
          },
        }}
        onCallback={(name, data) => {
          if (name === 'onScanSuccess') {
            setStep(Step.FINISH);
          }
        }}
      />
    );
  }

  return (
    <View style={styles.center}>
      <Text style={styles.title}>Process Complete!</Text>
      <TouchableOpacity style={styles.button} onPress={() => setStep(Step.START)}>
        <Text style={styles.buttonText}>Start Again</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  center: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff' },
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
  button: { backgroundColor: '#000', paddingHorizontal: 30, paddingVertical: 15, borderRadius: 30 },
  buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});

Recommended Flow

Onboarding -> FocalLength -> Educational -> BodyScan -> FaceScan

Each component's onComplete / onScanSuccess callback signals that you should render the next component.


Troubleshooting

Camera screen is black or "Permission Denied"

  • Check internet — components load assets remotely.
  • Check OS permissions — Go to Device Settings > Apps > [Your App] and toggle Camera ON.
  • Rebuild native app — If you edited Info.plist or AndroidManifest.xml, you must rebuild.

Video not loading on iOS

Ensure NSMicrophoneUsageDescription is in your Info.plist. iOS blocks the camera stream if the microphone permission description is missing, even if you don't record audio.

Callbacks not firing

  • Ensure your config object contains only JSON-serializable data (no functions).
  • Listen to events via onCallback, not by passing functions inside config.

License

MIT