# Selfie Module Guide

This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation [here](https://developer.incode.com/docs/web-sdk-reference). Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade.

The Selfie module captures a user's face with ML-powered liveness detection to prevent spoofing.

Follows the [camera-capture pattern](https://developer.incode.com/docs/web-sdk-2-module-patterns#2-camera-capture-modules). See that page for the shared manager lifecycle, capture sub-states, and skeleton; the rest of this page covers Selfie-specific config, detection statuses, and methods.

## Tag

`<incode-selfie>` is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.

```ts
import '@incodetech/web/selfie';
import '@incodetech/web/selfie/styles.css';
```

## Properties

Set these as JavaScript properties on the element (not as HTML attributes):

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `config` | `SelfieConfig` | ❌ | Configuration options (validation flags, modes) |
| `onFinish` | `() => void` | ❌ | Called when capture completes successfully |
| `onError` | `(error: string) => void` | ❌ | Called when an error occurs |

## WASM Requirements

The Selfie module uses WebAssembly for face detection and liveness analysis. Pre-warm WASM during `setup()` so models are ready before the user reaches the camera step:

```typescript
await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
  wasm: { pipelines: ['selfie'] },
});
```

See [WASM Configuration](https://developer.incode.com/docs/web-sdk-2-wasm) for self-hosted paths and the lower-level `warmupWasm()` API.

## Usage

### Vanilla HTML / TypeScript

```html
<incode-selfie></incode-selfie>

<script type="module">
  import { setup } from '@incodetech/core';
  import '@incodetech/web/selfie';
  import '@incodetech/web/selfie/styles.css';

await setup({
    apiURL: 'https://demo-api.incodesmile.com',
    token: 'your-session-token',
    wasm: { pipelines: ['selfie'] },
  });

const selfie = document.querySelector('incode-selfie');
  selfie.onFinish = () => console.log('Selfie captured!');
  selfie.onError = (err) => console.error('Selfie error:', err);
</script>
```

### React

```tsx
import { useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import type { SelfieConfig } from '@incodetech/core/selfie';
import '@incodetech/web/selfie';
import '@incodetech/web/selfie/styles.css';

type SelfieElement = HTMLElement & {
  config?: SelfieConfig;
  onFinish: () => void;
  onError: (error: string) => void;
};

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
  wasm: { pipelines: ['selfie'] },
});

export function SelfieCapture() {
  const ref = useRef<SelfieElement>(null);

useEffect(() => {
    const el = ref.current;
    if (!el) return;
    el.onFinish = () => console.log('Selfie captured!');
    el.onError = (err) => console.error('Selfie error:', err);
  }, []);

return <incode-selfie ref={ref} />;
}
```

## Headless Mode

For complete UI control, use the `createSelfieManager` from `@incodetech/core/selfie`.

### Quick Start

```tsx
import { setup } from '@incodetech/core';
import { createSelfieManager } from '@incodetech/core/selfie';
import { warmupWasm } from '@incodetech/core/wasm';

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
});

await warmupWasm({
  wasmPath: '/wasm/webLib.wasm',
  glueCodePath: '/wasm/webLib.js',
  modelsBasePath: '/wasm/models',
  pipelines: ['selfie'],
});

const manager = createSelfieManager({
  config: {
    showTutorial: true,
    autoCaptureTimeout: 10,
    validateLenses: true,
    validateFaceMask: true,
  },
});

manager.subscribe((state) => {
  console.log('Status:', state.status);

if (state.status === 'capture') {
    console.log('Detection:', state.detectionStatus);
    console.log('Stream ready:', !!state.stream);
  }

if (state.status === 'finished') {
    console.log('Selfie captured!', state.processResponse);
    manager.stop();
  }
});

manager.load();
```

### State Machine Flow

```
load

nextStep

granted

upload done

success

idle

tutorial

permissions

capture

processing

finished
```

### States Reference

| Status | Description | Key Properties |
| --- | --- | --- |
| `idle` | Initial state, waiting for `load()` | – |
| `loading` | Checking permissions (when no tutorial) | – |
| `tutorial` | Showing tutorial. Entered when `showTutorial` is `true` **or**`ageAssurance` is `true` | `ageAssurance` (boolean; mirrors `config.ageAssurance`) |
| `permissions` | Camera permission handling | `permissionStatus` |
| `capture` | Camera active, detecting face | `stream`, `captureStatus`, `detectionStatus`, `attemptsRemaining` |
| `processing` | Server-side processing of the captured selfie | – |
| `finished` | Capture complete | `processResponse?` |
| `closed` | User closed the flow | – |
| `error` | Fatal error occurred | `error` |

### Capture State Properties

| Property | Type | Description |
| --- | --- | --- |
| `stream` | `CameraStream` | Camera stream for `<video>` element |
| `captureStatus` | `string` | Current status of the capture |
| `detectionStatus` | `DetectionStatus` | Face detection feedback |
| `attemptsRemaining` | `number` | Remaining capture attempts |
| `uploadError` | `string?` | Error message if upload failed |
| `assistedOnboarding` | `boolean` | Whether assisted onboarding mode is active |
| `debugFrame` | `ImageData?` | Latest processed frame (for debugging) |

### Detection Status Values

| Status | User Instruction |
| --- | --- |
| `idle` | "Preparing camera..." |
| `detecting` | "Detecting face..." |
| `noFace` | "Position your face in the frame" |
| `tooManyFaces` | "Only one face should be visible" |
| `tooClose` | "Move back" |
| `tooFar` | "Move closer" |
| `blur` | "Hold still, image is blurry" |
| `dark` | "Improve lighting conditions" |
| `faceAngle` | "Face your camera directly" |
| `headWear` | "Remove head coverings" |
| `lenses` | "Remove glasses or lenses" |
| `eyesClosed` | "Open your eyes" |
| `faceMask` | "Remove face mask" |
| `centerFace` | "Center your face" |
| `getReady` | "Get ready..." |
| `getReadyFinished` | "Hold still..." |
| `capturing` | "Capturing photo..." |
| `manualCapture` | "Tap to capture" |
| `offline` | "No network connection" |

### API Methods

| Method | Description | When to Use |
| --- | --- | --- |
| `load()` | Starts the selfie flow | Always call first |
| `nextStep()` | Advances from tutorial to permissions | When `tutorial` |
| `requestPermission()` | Requests camera access | When `permissions.idle` or `permissions.learnMore` |
| `capture()` | Manual capture trigger | When `detectionStatus === 'manualCapture'` |
| `close()` | Close the flow | Anytime |
| `stop()` | Cleanup resources | When unmounting |
| `getState()` | Returns current state | Anytime |
| `subscribe(callback)` | Subscribe to state changes | Returns unsubscribe function |

### Capture-only flow

`createSelfieCaptureOnlyManager` exposes the same state machine and API surface as `createSelfieManager`, but bypasses Incode's `/omni/add/face` upload. Instead of submitting the captured frame and waiting on server-side processing, the manager invokes a customer-supplied `onCapture(response)` callback with the raw face image.

The config is `SelfieConfig` plus a required `onCapture` callback — enforced at compile time:

```typescript
import { setup } from '@incodetech/core';
import { initializeSession } from '@incodetech/core/session';
import { createSelfieCaptureOnlyManager, type SelfieCaptureOnlyConfig, type FaceCaptureOnlyResponse } from '@incodetech/core/selfie';

const config: SelfieCaptureOnlyConfig = {
  showTutorial: true,
  showPreview: false,
  captureAttempts: 3,
  validateLenses: true,
  onCapture: async (response: FaceCaptureOnlyResponse) => {
    const { image } = response;
    await uploadToMyBackend(image.blob);
  },
};

const manager = createSelfieCaptureOnlyManager({ config });
manager.load();
```

### Configuration Options

`SelfieConfig` is `FlowModuleConfig['SELFIE'] & BaseFaceCaptureConfig`. Here are some options you can configure:

| Option | Type | Required | Description |
| --- | --- | --- | --- |
| `showTutorial` | `boolean` | ✅ | Show tutorial before capture |
| `showPreview` | `boolean` | ✅ | Show preview after capture |
| `enableFaceRecording` | `boolean` | ✅ | Enable video recording of the capture |
| `autoCaptureTimeout` | `number` | ✅ | Seconds before auto-capture triggers |
| `captureAttempts` | `number` | ✅ | Maximum capture attempts |
| `validateLenses` | `boolean` | ✅ | Reject captures with glasses/lenses |
| `validateFaceMask` | `boolean` | ✅ | Reject captures with face mask |
| `validateBrightness` | `boolean` | ✅ | Reject captures with poor lighting |
| `deepsightLiveness` | `'SINGLE_FRAME' | 'MULTIMODAL' | 'VIDEOLIVENESS'` | ✅ | Liveness detection mode |

### Troubleshooting

#### Face Not Detected
- Ensure good lighting (avoid backlighting)
- Remove glasses or hats if possible
- Position face within the outline

#### Camera Issues
- Ensure HTTPS (camera requires secure context)
- Check browser permissions
