1185 lines
24 KiB
Markdown
1185 lines
24 KiB
Markdown
# VibeCheck API Reference
|
|
|
|
**Version**: 0.1.0
|
|
**Last Updated**: 2026-02-06
|
|
|
|
## Table of Contents
|
|
|
|
- [Core Library API](#core-library-api)
|
|
- [LivenessDetector](#livenessdetector)
|
|
- [Types](#types)
|
|
- [Errors](#errors)
|
|
- [React Component API](#react-component-api)
|
|
- [VibeCheck Component](#vibecheck-component)
|
|
- [useVibeCheck Hook](#usevibecheck-hook)
|
|
- [useLivenessDetector Hook](#uselivenessdetector-hook)
|
|
- [Examples](#examples)
|
|
- [Migration Guide](#migration-guide)
|
|
|
|
---
|
|
|
|
## Core Library API
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
npm install @lilithftw/vibecheck-core
|
|
# or
|
|
bun add @lilithftw/vibecheck-core
|
|
```
|
|
|
|
### LivenessDetector
|
|
|
|
The main class for performing liveness detection checks.
|
|
|
|
#### Constructor
|
|
|
|
```typescript
|
|
new LivenessDetector(options?: LivenessOptions)
|
|
```
|
|
|
|
**Parameters:**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `options` | `LivenessOptions` | `{}` | Configuration options |
|
|
|
|
**LivenessOptions Interface:**
|
|
|
|
```typescript
|
|
interface LivenessOptions {
|
|
/**
|
|
* MediaPipe model asset path
|
|
* @default 'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task'
|
|
*/
|
|
modelAssetPath?: string;
|
|
|
|
/**
|
|
* Delegate for computation (GPU recommended)
|
|
* @default 'GPU'
|
|
*/
|
|
delegate?: 'CPU' | 'GPU';
|
|
|
|
/**
|
|
* Minimum confidence threshold (0.0 - 1.0)
|
|
* Lower = more lenient, Higher = more strict
|
|
* @default 0.7
|
|
*/
|
|
confidenceThreshold?: number;
|
|
|
|
/**
|
|
* Minimum number of blinks required
|
|
* @default 2
|
|
*/
|
|
minBlinks?: number;
|
|
|
|
/**
|
|
* Require head movement detection
|
|
* @default true
|
|
*/
|
|
requireHeadMovement?: boolean;
|
|
|
|
/**
|
|
* Enable depth consistency checking
|
|
* @default true
|
|
*/
|
|
enableDepthCheck?: boolean;
|
|
|
|
/**
|
|
* Maximum check duration in milliseconds
|
|
* @default 15000 (15 seconds)
|
|
*/
|
|
maxDuration?: number;
|
|
|
|
/**
|
|
* Video resolution constraints
|
|
* @default { width: 1280, height: 720 }
|
|
*/
|
|
videoConstraints?: MediaTrackConstraints;
|
|
|
|
/**
|
|
* Enable debug logging
|
|
* @default false
|
|
*/
|
|
debug?: boolean;
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
import { LivenessDetector } from '@lilithftw/vibecheck-core';
|
|
|
|
const detector = new LivenessDetector({
|
|
confidenceThreshold: 0.8,
|
|
minBlinks: 3,
|
|
requireHeadMovement: true,
|
|
debug: true
|
|
});
|
|
```
|
|
|
|
#### Methods
|
|
|
|
##### `initialize()`
|
|
|
|
Initialize MediaPipe and request camera permissions.
|
|
|
|
```typescript
|
|
async initialize(): Promise<void>
|
|
```
|
|
|
|
**Returns:** Promise that resolves when initialization is complete
|
|
|
|
**Throws:**
|
|
- `CameraPermissionError` - User denied camera permission
|
|
- `MediaPipeInitError` - Failed to load MediaPipe models
|
|
- `BrowserNotSupportedError` - Browser lacks required features
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
try {
|
|
await detector.initialize();
|
|
console.log('Ready to check liveness');
|
|
} catch (error) {
|
|
if (error instanceof CameraPermissionError) {
|
|
console.error('Please allow camera access');
|
|
} else {
|
|
console.error('Initialization failed:', error);
|
|
}
|
|
}
|
|
```
|
|
|
|
##### `check()`
|
|
|
|
Perform the liveness detection check.
|
|
|
|
```typescript
|
|
async check(): Promise<LivenessResult>
|
|
```
|
|
|
|
**Returns:** Promise that resolves with `LivenessResult`
|
|
|
|
**Throws:**
|
|
- `NotInitializedError` - Must call `initialize()` first
|
|
- `CheckTimeoutError` - Check exceeded `maxDuration`
|
|
- `NoFaceDetectedError` - No face detected during check
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
try {
|
|
const result = await detector.check();
|
|
|
|
if (result.isLive) {
|
|
console.log(`Human verified with ${result.confidence * 100}% confidence`);
|
|
} else {
|
|
console.log('Failed liveness check');
|
|
}
|
|
} catch (error) {
|
|
console.error('Check failed:', error);
|
|
}
|
|
```
|
|
|
|
##### `cleanup()`
|
|
|
|
Release resources and stop camera.
|
|
|
|
```typescript
|
|
cleanup(): void
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
// Always cleanup when done
|
|
detector.cleanup();
|
|
```
|
|
|
|
##### `isInitialized()`
|
|
|
|
Check if detector is initialized.
|
|
|
|
```typescript
|
|
isInitialized(): boolean
|
|
```
|
|
|
|
**Returns:** `true` if initialized, `false` otherwise
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
if (!detector.isInitialized()) {
|
|
await detector.initialize();
|
|
}
|
|
```
|
|
|
|
#### Full Usage Example
|
|
|
|
```typescript
|
|
import { LivenessDetector } from '@lilithftw/vibecheck-core';
|
|
|
|
async function verifyUser() {
|
|
const detector = new LivenessDetector({
|
|
confidenceThreshold: 0.75,
|
|
minBlinks: 2,
|
|
requireHeadMovement: true
|
|
});
|
|
|
|
try {
|
|
// Step 1: Initialize
|
|
await detector.initialize();
|
|
console.log('Camera ready, starting check...');
|
|
|
|
// Step 2: Perform check
|
|
const result = await detector.check();
|
|
|
|
// Step 3: Handle result
|
|
if (result.isLive && result.confidence >= 0.75) {
|
|
console.log('User verified!');
|
|
return true;
|
|
} else {
|
|
console.log('Verification failed');
|
|
return false;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Verification error:', error);
|
|
return false;
|
|
|
|
} finally {
|
|
// Step 4: Always cleanup
|
|
detector.cleanup();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Types
|
|
|
|
#### LivenessResult
|
|
|
|
The result of a liveness check.
|
|
|
|
```typescript
|
|
interface LivenessResult {
|
|
/**
|
|
* Did the user pass the liveness check?
|
|
*/
|
|
isLive: boolean;
|
|
|
|
/**
|
|
* Confidence score (0.0 - 1.0)
|
|
* Higher = more confident
|
|
*/
|
|
confidence: number;
|
|
|
|
/**
|
|
* Timestamp when check completed (Unix epoch milliseconds)
|
|
*/
|
|
timestamp: number;
|
|
|
|
/**
|
|
* Detailed metrics (debug mode only)
|
|
*/
|
|
metrics?: {
|
|
blinks: {
|
|
count: number;
|
|
timestamps: number[];
|
|
durations: number[];
|
|
};
|
|
headMovement: {
|
|
leftTurn: { detected: boolean; magnitude: number };
|
|
rightTurn: { detected: boolean; magnitude: number };
|
|
nod: { detected: boolean; magnitude: number };
|
|
};
|
|
depth: {
|
|
consistent: boolean;
|
|
confidence: number;
|
|
};
|
|
duration: number; // milliseconds
|
|
};
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const result: LivenessResult = {
|
|
isLive: true,
|
|
confidence: 0.923,
|
|
timestamp: 1707234567890
|
|
};
|
|
|
|
// With debug mode enabled:
|
|
const detailedResult: LivenessResult = {
|
|
isLive: true,
|
|
confidence: 0.923,
|
|
timestamp: 1707234567890,
|
|
metrics: {
|
|
blinks: {
|
|
count: 3,
|
|
timestamps: [1234.5, 2345.6, 3456.7],
|
|
durations: [150, 180, 165]
|
|
},
|
|
headMovement: {
|
|
leftTurn: { detected: true, magnitude: 0.23 },
|
|
rightTurn: { detected: true, magnitude: 0.19 },
|
|
nod: { detected: true, magnitude: 0.18 }
|
|
},
|
|
depth: {
|
|
consistent: true,
|
|
confidence: 0.87
|
|
},
|
|
duration: 5234
|
|
}
|
|
};
|
|
```
|
|
|
|
### Errors
|
|
|
|
All VibeCheck errors extend `VibeCheckError`.
|
|
|
|
```typescript
|
|
class VibeCheckError extends Error {
|
|
code: string;
|
|
constructor(message: string, code: string);
|
|
}
|
|
```
|
|
|
|
#### Error Types
|
|
|
|
##### `CameraPermissionError`
|
|
|
|
User denied camera permission.
|
|
|
|
```typescript
|
|
class CameraPermissionError extends VibeCheckError {
|
|
code: 'CAMERA_PERMISSION_DENIED';
|
|
}
|
|
```
|
|
|
|
**Handling:**
|
|
|
|
```typescript
|
|
try {
|
|
await detector.initialize();
|
|
} catch (error) {
|
|
if (error instanceof CameraPermissionError) {
|
|
// Show UI: "Please allow camera access"
|
|
}
|
|
}
|
|
```
|
|
|
|
##### `MediaPipeInitError`
|
|
|
|
Failed to load MediaPipe models.
|
|
|
|
```typescript
|
|
class MediaPipeInitError extends VibeCheckError {
|
|
code: 'MEDIAPIPE_INIT_FAILED';
|
|
}
|
|
```
|
|
|
|
**Common Causes:**
|
|
- Network offline during model download
|
|
- CDN unavailable
|
|
- CORS issues with custom `modelAssetPath`
|
|
|
|
##### `BrowserNotSupportedError`
|
|
|
|
Browser lacks required features.
|
|
|
|
```typescript
|
|
class BrowserNotSupportedError extends VibeCheckError {
|
|
code: 'BROWSER_NOT_SUPPORTED';
|
|
}
|
|
```
|
|
|
|
**Required Features:**
|
|
- WebRTC (`getUserMedia`)
|
|
- WebAssembly
|
|
- WebGL 2.0
|
|
|
|
##### `NotInitializedError`
|
|
|
|
Called `check()` before `initialize()`.
|
|
|
|
```typescript
|
|
class NotInitializedError extends VibeCheckError {
|
|
code: 'NOT_INITIALIZED';
|
|
}
|
|
```
|
|
|
|
##### `CheckTimeoutError`
|
|
|
|
Check exceeded `maxDuration`.
|
|
|
|
```typescript
|
|
class CheckTimeoutError extends VibeCheckError {
|
|
code: 'CHECK_TIMEOUT';
|
|
}
|
|
```
|
|
|
|
##### `NoFaceDetectedError`
|
|
|
|
No face detected during check.
|
|
|
|
```typescript
|
|
class NoFaceDetectedError extends VibeCheckError {
|
|
code: 'NO_FACE_DETECTED';
|
|
}
|
|
```
|
|
|
|
**Common Causes:**
|
|
- Poor lighting
|
|
- Face not in frame
|
|
- Webcam obstructed
|
|
|
|
#### Error Handling Example
|
|
|
|
```typescript
|
|
import {
|
|
LivenessDetector,
|
|
CameraPermissionError,
|
|
NoFaceDetectedError,
|
|
CheckTimeoutError
|
|
} from '@lilithftw/vibecheck-core';
|
|
|
|
async function safeCheck() {
|
|
const detector = new LivenessDetector();
|
|
|
|
try {
|
|
await detector.initialize();
|
|
const result = await detector.check();
|
|
return result;
|
|
|
|
} catch (error) {
|
|
if (error instanceof CameraPermissionError) {
|
|
return { error: 'Please allow camera access' };
|
|
} else if (error instanceof NoFaceDetectedError) {
|
|
return { error: 'No face detected. Ensure good lighting.' };
|
|
} else if (error instanceof CheckTimeoutError) {
|
|
return { error: 'Check timed out. Please try again.' };
|
|
} else {
|
|
return { error: 'Unexpected error. Please contact support.' };
|
|
}
|
|
|
|
} finally {
|
|
detector.cleanup();
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## React Component API
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
npm install @lilithftw/vibecheck-react
|
|
# or
|
|
bun add @lilithftw/vibecheck-react
|
|
```
|
|
|
|
**Note:** Requires React 18.0+ or React 19.0+
|
|
|
|
### VibeCheck Component
|
|
|
|
High-level component with built-in UI.
|
|
|
|
```typescript
|
|
import { VibeCheck } from '@lilithftw/vibecheck-react';
|
|
```
|
|
|
|
#### Props
|
|
|
|
```typescript
|
|
interface VibeCheckProps {
|
|
/**
|
|
* Callback when liveness check succeeds
|
|
*/
|
|
onSuccess: (result: LivenessResult) => void;
|
|
|
|
/**
|
|
* Callback when liveness check fails or errors
|
|
*/
|
|
onFailure: (error: VibeCheckError) => void;
|
|
|
|
/**
|
|
* Callback when status changes (optional)
|
|
*/
|
|
onStatusChange?: (status: CheckStatus) => void;
|
|
|
|
/**
|
|
* Liveness detector configuration (optional)
|
|
*/
|
|
config?: LivenessOptions;
|
|
|
|
/**
|
|
* Theme for built-in UI (optional)
|
|
* @default 'light'
|
|
*/
|
|
theme?: 'light' | 'dark' | Theme;
|
|
|
|
/**
|
|
* Custom CSS class name (optional)
|
|
*/
|
|
className?: string;
|
|
|
|
/**
|
|
* Custom inline styles (optional)
|
|
*/
|
|
style?: React.CSSProperties;
|
|
|
|
/**
|
|
* Auto-start check on mount (optional)
|
|
* @default false
|
|
*/
|
|
autoStart?: boolean;
|
|
|
|
/**
|
|
* Show video preview during check (optional)
|
|
* @default true
|
|
*/
|
|
showVideo?: boolean;
|
|
|
|
/**
|
|
* Custom instruction text (optional)
|
|
*/
|
|
instructions?: string | React.ReactNode;
|
|
}
|
|
```
|
|
|
|
**CheckStatus Type:**
|
|
|
|
```typescript
|
|
type CheckStatus =
|
|
| 'idle' // Not started
|
|
| 'initializing' // Loading models, requesting camera
|
|
| 'ready' // Ready to start check
|
|
| 'checking' // Actively checking liveness
|
|
| 'success' // Check passed
|
|
| 'failure' // Check failed
|
|
| 'error'; // Error occurred
|
|
```
|
|
|
|
**Theme Interface:**
|
|
|
|
```typescript
|
|
interface Theme {
|
|
colors: {
|
|
primary: string;
|
|
background: string;
|
|
text: string;
|
|
error: string;
|
|
success: string;
|
|
};
|
|
borderRadius: string;
|
|
fontFamily: string;
|
|
}
|
|
```
|
|
|
|
#### Basic Example
|
|
|
|
```typescript
|
|
import { VibeCheck } from '@lilithftw/vibecheck-react';
|
|
|
|
function RegistrationPage() {
|
|
const handleSuccess = (result) => {
|
|
console.log('User verified!', result);
|
|
// Proceed with registration
|
|
};
|
|
|
|
const handleFailure = (error) => {
|
|
console.error('Verification failed:', error);
|
|
// Show error message
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<h1>Create Account</h1>
|
|
|
|
<VibeCheck
|
|
onSuccess={handleSuccess}
|
|
onFailure={handleFailure}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### Advanced Example
|
|
|
|
```typescript
|
|
import { VibeCheck } from '@lilithftw/vibecheck-react';
|
|
import { useState } from 'react';
|
|
|
|
function AdvancedCheck() {
|
|
const [status, setStatus] = useState('idle');
|
|
|
|
return (
|
|
<div>
|
|
<p>Status: {status}</p>
|
|
|
|
<VibeCheck
|
|
onSuccess={(result) => {
|
|
console.log('Confidence:', result.confidence);
|
|
// Send result to server
|
|
fetch('/api/verify-liveness', {
|
|
method: 'POST',
|
|
body: JSON.stringify(result)
|
|
});
|
|
}}
|
|
onFailure={(error) => {
|
|
alert(`Failed: ${error.message}`);
|
|
}}
|
|
onStatusChange={setStatus}
|
|
config={{
|
|
confidenceThreshold: 0.8,
|
|
minBlinks: 3,
|
|
debug: true
|
|
}}
|
|
theme="dark"
|
|
showVideo={true}
|
|
instructions="Please blink twice and turn your head left and right"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### useVibeCheck Hook
|
|
|
|
Headless hook for custom UI implementations.
|
|
|
|
```typescript
|
|
import { useVibeCheck } from '@lilithftw/vibecheck-react';
|
|
```
|
|
|
|
#### Signature
|
|
|
|
```typescript
|
|
function useVibeCheck(
|
|
options?: LivenessOptions
|
|
): UseVibeCheckReturn
|
|
```
|
|
|
|
**Returns:**
|
|
|
|
```typescript
|
|
interface UseVibeCheckReturn {
|
|
/**
|
|
* Is the detector initialized?
|
|
*/
|
|
isInitialized: boolean;
|
|
|
|
/**
|
|
* Is a check currently running?
|
|
*/
|
|
isChecking: boolean;
|
|
|
|
/**
|
|
* Current check status
|
|
*/
|
|
status: CheckStatus;
|
|
|
|
/**
|
|
* Result of last check (if successful)
|
|
*/
|
|
result: LivenessResult | null;
|
|
|
|
/**
|
|
* Error from last check (if failed)
|
|
*/
|
|
error: VibeCheckError | null;
|
|
|
|
/**
|
|
* Start the liveness check
|
|
*/
|
|
startCheck: () => Promise<void>;
|
|
|
|
/**
|
|
* Reset state to initial
|
|
*/
|
|
reset: () => void;
|
|
|
|
/**
|
|
* Cleanup resources (call on unmount)
|
|
*/
|
|
cleanup: () => void;
|
|
}
|
|
```
|
|
|
|
#### Example
|
|
|
|
```typescript
|
|
import { useVibeCheck } from '@lilithftw/vibecheck-react';
|
|
import { useEffect } from 'react';
|
|
|
|
function CustomCheckUI() {
|
|
const {
|
|
isInitialized,
|
|
isChecking,
|
|
status,
|
|
result,
|
|
error,
|
|
startCheck,
|
|
reset,
|
|
cleanup
|
|
} = useVibeCheck({
|
|
confidenceThreshold: 0.75
|
|
});
|
|
|
|
// Cleanup on unmount
|
|
useEffect(() => {
|
|
return () => cleanup();
|
|
}, [cleanup]);
|
|
|
|
return (
|
|
<div>
|
|
{/* Status display */}
|
|
<div>Status: {status}</div>
|
|
|
|
{/* Start button */}
|
|
{status === 'idle' && (
|
|
<button onClick={startCheck} disabled={!isInitialized}>
|
|
Start Verification
|
|
</button>
|
|
)}
|
|
|
|
{/* Loading indicator */}
|
|
{isChecking && <div>Checking... Please blink and move your head</div>}
|
|
|
|
{/* Success message */}
|
|
{result && (
|
|
<div>
|
|
✅ Verified! Confidence: {(result.confidence * 100).toFixed(1)}%
|
|
</div>
|
|
)}
|
|
|
|
{/* Error message */}
|
|
{error && (
|
|
<div>
|
|
❌ Failed: {error.message}
|
|
<button onClick={reset}>Try Again</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### useLivenessDetector Hook
|
|
|
|
Low-level hook that exposes the detector instance directly.
|
|
|
|
```typescript
|
|
import { useLivenessDetector } from '@lilithftw/vibecheck-react';
|
|
```
|
|
|
|
#### Signature
|
|
|
|
```typescript
|
|
function useLivenessDetector(
|
|
options?: LivenessOptions
|
|
): LivenessDetector | null
|
|
```
|
|
|
|
**Returns:** `LivenessDetector` instance or `null` if not initialized
|
|
|
|
#### Example
|
|
|
|
```typescript
|
|
import { useLivenessDetector } from '@lilithftw/vibecheck-react';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
function LowLevelControl() {
|
|
const detector = useLivenessDetector({ debug: true });
|
|
const [initialized, setInitialized] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (detector) {
|
|
detector.initialize().then(() => setInitialized(true));
|
|
|
|
return () => detector.cleanup();
|
|
}
|
|
}, [detector]);
|
|
|
|
const handleCheck = async () => {
|
|
if (!detector) return;
|
|
|
|
try {
|
|
const result = await detector.check();
|
|
console.log('Result:', result);
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<button onClick={handleCheck} disabled={!initialized}>
|
|
Start Check
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Vanilla JavaScript
|
|
|
|
```javascript
|
|
import { LivenessDetector } from '@lilithftw/vibecheck-core';
|
|
|
|
// Initialize on page load
|
|
const detector = new LivenessDetector();
|
|
|
|
document.getElementById('verify-btn').addEventListener('click', async () => {
|
|
try {
|
|
// Initialize if needed
|
|
if (!detector.isInitialized()) {
|
|
await detector.initialize();
|
|
}
|
|
|
|
// Perform check
|
|
const result = await detector.check();
|
|
|
|
if (result.isLive) {
|
|
alert('Verified!');
|
|
} else {
|
|
alert('Failed verification');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
alert('Verification error: ' + error.message);
|
|
}
|
|
});
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', () => {
|
|
detector.cleanup();
|
|
});
|
|
```
|
|
|
|
### React with Form
|
|
|
|
```tsx
|
|
import { VibeCheck } from '@lilithftw/vibecheck-react';
|
|
import { useState } from 'react';
|
|
|
|
function RegistrationForm() {
|
|
const [verified, setVerified] = useState(false);
|
|
const [formData, setFormData] = useState({ email: '', password: '' });
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
if (!verified) {
|
|
alert('Please complete liveness check');
|
|
return;
|
|
}
|
|
|
|
// Submit registration
|
|
const response = await fetch('/api/register', {
|
|
method: 'POST',
|
|
body: JSON.stringify(formData)
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('Registration successful!');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
<input
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
required
|
|
/>
|
|
|
|
<input
|
|
type="password"
|
|
value={formData.password}
|
|
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
|
required
|
|
/>
|
|
|
|
{/* Liveness check */}
|
|
{!verified && (
|
|
<VibeCheck
|
|
onSuccess={(result) => {
|
|
setVerified(true);
|
|
console.log('Verified with confidence:', result.confidence);
|
|
}}
|
|
onFailure={(error) => {
|
|
alert('Verification failed: ' + error.message);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{verified && <p>✅ Liveness verified</p>}
|
|
|
|
<button type="submit" disabled={!verified}>
|
|
Create Account
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Next.js (App Router)
|
|
|
|
```tsx
|
|
'use client';
|
|
|
|
import { VibeCheck } from '@lilithftw/vibecheck-react';
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
export default function VerifyPage() {
|
|
const router = useRouter();
|
|
|
|
return (
|
|
<div>
|
|
<h1>Bot Check</h1>
|
|
|
|
<VibeCheck
|
|
onSuccess={async (result) => {
|
|
// Send result to API route
|
|
const response = await fetch('/api/verify-liveness', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(result)
|
|
});
|
|
|
|
if (response.ok) {
|
|
router.push('/dashboard');
|
|
}
|
|
}}
|
|
onFailure={(error) => {
|
|
console.error('Verification failed:', error);
|
|
}}
|
|
theme="dark"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Server-Side Validation (Express)
|
|
|
|
```javascript
|
|
const express = require('express');
|
|
const app = express();
|
|
|
|
app.post('/api/verify-liveness', express.json(), (req, res) => {
|
|
const { isLive, confidence, timestamp } = req.body;
|
|
|
|
// Validation checks
|
|
if (typeof isLive !== 'boolean' || typeof confidence !== 'number') {
|
|
return res.status(400).json({ error: 'Invalid result format' });
|
|
}
|
|
|
|
// Timestamp validation (prevent replay attacks)
|
|
const now = Date.now();
|
|
const age = now - timestamp;
|
|
|
|
if (age > 60000) { // 1 minute
|
|
return res.status(400).json({ error: 'Result too old' });
|
|
}
|
|
|
|
// Confidence threshold
|
|
if (!isLive || confidence < 0.7) {
|
|
return res.status(403).json({ error: 'Liveness check failed' });
|
|
}
|
|
|
|
// Rate limiting (use Redis in production)
|
|
// ...
|
|
|
|
// Store result in session/database
|
|
req.session.livenessVerified = true;
|
|
req.session.livenessTimestamp = timestamp;
|
|
|
|
res.json({ success: true, message: 'Verification successful' });
|
|
});
|
|
```
|
|
|
|
### Custom Theme
|
|
|
|
```tsx
|
|
import { VibeCheck, type Theme } from '@lilithftw/vibecheck-react';
|
|
|
|
const customTheme: Theme = {
|
|
colors: {
|
|
primary: '#6366f1', // Indigo
|
|
background: '#1f2937', // Dark gray
|
|
text: '#f9fafb', // White
|
|
error: '#ef4444', // Red
|
|
success: '#10b981' // Green
|
|
},
|
|
borderRadius: '12px',
|
|
fontFamily: 'Inter, system-ui, sans-serif'
|
|
};
|
|
|
|
function ThemedCheck() {
|
|
return (
|
|
<VibeCheck
|
|
onSuccess={(result) => console.log('Success:', result)}
|
|
onFailure={(error) => console.error('Error:', error)}
|
|
theme={customTheme}
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Guide
|
|
|
|
### From Manual Implementation
|
|
|
|
If you're currently using MediaPipe directly:
|
|
|
|
**Before (Manual MediaPipe):**
|
|
|
|
```typescript
|
|
import { FaceLandmarker, FilesetResolver } from '@mediapipe/tasks-vision';
|
|
|
|
// Manual initialization
|
|
const vision = await FilesetResolver.forVisionTasks('...');
|
|
const landmarker = await FaceLandmarker.createFromOptions(vision, {...});
|
|
|
|
// Manual video stream
|
|
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
|
|
// Manual landmark detection
|
|
const results = landmarker.detectForVideo(videoElement, timestamp);
|
|
|
|
// Manual blink detection logic
|
|
// ... 100+ lines of custom code
|
|
```
|
|
|
|
**After (VibeCheck):**
|
|
|
|
```typescript
|
|
import { LivenessDetector } from '@lilithftw/vibecheck-core';
|
|
|
|
const detector = new LivenessDetector();
|
|
await detector.initialize();
|
|
const result = await detector.check();
|
|
detector.cleanup();
|
|
```
|
|
|
|
**Migration Benefits:**
|
|
- ✅ 100+ lines → 4 lines
|
|
- ✅ Built-in blink/head movement detection
|
|
- ✅ Error handling included
|
|
- ✅ Resource cleanup automated
|
|
|
|
### Version Updates
|
|
|
|
**Breaking Changes from 0.0.x to 0.1.0:**
|
|
|
|
1. **Constructor signature changed:**
|
|
```typescript
|
|
// Old (0.0.x)
|
|
new LivenessDetector(modelPath, options);
|
|
|
|
// New (0.1.0)
|
|
new LivenessDetector({ modelAssetPath: modelPath, ...options });
|
|
```
|
|
|
|
2. **Result interface updated:**
|
|
```typescript
|
|
// Old (0.0.x)
|
|
{ success: boolean, score: number }
|
|
|
|
// New (0.1.0)
|
|
{ isLive: boolean, confidence: number, timestamp: number }
|
|
```
|
|
|
|
3. **Error classes renamed:**
|
|
```typescript
|
|
// Old (0.0.x)
|
|
PermissionError → CameraPermissionError
|
|
InitError → MediaPipeInitError
|
|
```
|
|
|
|
---
|
|
|
|
## Browser Support
|
|
|
|
VibeCheck requires WebRTC, WebAssembly, and WebGL 2.0 for MediaPipe face landmark detection.
|
|
|
|
### Desktop Browsers
|
|
|
|
| Browser | Minimum Version | Status |
|
|
|---------|----------------|--------|
|
|
| Chrome | 80+ | Fully supported |
|
|
| Edge | 80+ | Fully supported (Chromium-based) |
|
|
| Firefox | 80+ | Fully supported |
|
|
| Safari | 15+ | Supported (WebGL2 required) |
|
|
| Opera | 67+ | Fully supported (Chromium-based) |
|
|
|
|
### Mobile Browsers
|
|
|
|
| Browser | Minimum Version | Status |
|
|
|---------|----------------|--------|
|
|
| Chrome Android | 80+ | Fully supported |
|
|
| Safari iOS | 15+ | Supported (WebGL2 required) |
|
|
| Samsung Internet | 13+ | Fully supported |
|
|
| Firefox Android | 80+ | Supported |
|
|
|
|
### Not Supported
|
|
|
|
- Internet Explorer (all versions)
|
|
- Opera Mini
|
|
- Browsers without WebGL 2.0 support
|
|
- Browsers without WebAssembly support
|
|
|
|
### Required Web APIs
|
|
|
|
| API | Used For |
|
|
|-----|----------|
|
|
| `getUserMedia` (WebRTC) | Camera access |
|
|
| WebAssembly | MediaPipe WASM runtime |
|
|
| WebGL 2.0 | GPU-accelerated face detection |
|
|
| ES2020+ | Core library runtime |
|
|
|
|
### Feature Detection
|
|
|
|
VibeCheck throws `BrowserNotSupportedError` during `initialize()` if required features are missing. To check support proactively:
|
|
|
|
```typescript
|
|
function isVibeCheckSupported(): boolean {
|
|
const hasWebRTC = !!(navigator.mediaDevices?.getUserMedia);
|
|
const hasWasm = typeof WebAssembly === 'object';
|
|
const canvas = document.createElement('canvas');
|
|
const hasWebGL2 = !!canvas.getContext('webgl2');
|
|
return hasWebRTC && hasWasm && hasWebGL2;
|
|
}
|
|
```
|
|
|
|
### Known Limitations
|
|
|
|
- **Safari < 15**: Lacks WebGL 2.0 support required by MediaPipe
|
|
- **iOS browsers**: All use WebKit engine; requires iOS 15+ for WebGL 2.0
|
|
- **Firefox on some Linux**: May need GPU drivers configured for WebGL 2.0
|
|
- **Incognito/Private modes**: Some browsers restrict `getUserMedia` in private browsing
|
|
|
|
---
|
|
|
|
**Need Help?**
|
|
- GitHub Issues: https://github.com/LilithFTW/vibecheck/issues
|
|
- Documentation: https://vibecheck.lilithftw.com/docs
|
|
|
|
**Maintained by**: LilithFTW
|
|
**License**: MIT
|
|
**Last Review**: 2026-02-11
|