@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
- Setup & Permissions
- Quick Start
- API Reference
- Components
- Interfaces
- Full Integration Example
- Troubleshooting
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-contextFor iOS, install pods:
cd ios && pod installSetup & 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:
NSMicrophoneUsageDescriptionis 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-androidQuick 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 Scan — token 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 Scan — token 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 -> FaceScanEach 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.plistorAndroidManifest.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
configobject contains only JSON-serializable data (no functions). - Listen to events via
onCallback, not by passing functions insideconfig.
License
MIT
