ugx-face-liveness-react
v3.0.0
Published
React face-liveness component: on-device MediaPipe FaceLandmarker with a randomized head-turn challenge. Framework-agnostic detection core shared with ugx-face-liveness (Angular).
Maintainers
Readme
ugx-face-liveness-react
React face-liveness widget with an on-device MediaPipe FaceLandmarker and a
randomized head-turn challenge. No server, no cloud, no cost — all
detection runs in a Web Worker in the browser. It is the React counterpart to
the Angular ugx-face-liveness library and shares
the same detection core (liveness-challenge.ts + the MediaPipe worker), though
the two are tuned independently — see the changelog for differences.
- 🎯 Randomized anti-spoofing challenge (turn left / turn right) — a static photo can't pass it.
- 🧠 Runs fully client-side (WASM + Web Worker). Nothing leaves the device.
- 📦 Ships a
<FaceLiveness>component and a headlessuseFaceLiveness()hook for custom UIs. - 🖼️ Returns a JPEG snapshot + an optional WebM recording of the challenge.
Installation
npm install ugx-face-liveness-reactreact and react-dom (17–19) are peer dependencies.
Serve the static assets (required)
The model, WASM runtime and detection worker are shipped as static files,
not bundled — they must be served by your app and reachable at a base URL
(default /assets/face-liveness).
Copy the package's assets/ folder into your public/static directory:
# Vite / CRA (public/) — run once, or add to a "postinstall"/build step
cp -R node_modules/ugx-face-liveness-react/assets public/assets/face-livenessThat yields:
public/assets/face-liveness/
worker/face-landmarker.worker.js
mediapipe-wasm/… (~21 MB, SIMD + no-SIMD)
models-v2/face_landmarker.task (~3.6 MB)
models-v2/efficientdet_lite0.tflite (~4.6 MB, phone/screen detection)If you serve them somewhere else, pass options={{ assetBaseUrl: '/your/path' }}.
Upgrading: re-run the asset copy after bumping the package — a new version may add files (e.g. the phone-detection model above). If that model is missing, device detection is silently skipped; core liveness keeps working.
HTTPS + permissions: camera access requires a secure context (
https://orlocalhost).
Usage — the ready-made component
import { FaceLiveness, type LivenessResult } from 'ugx-face-liveness-react';
export function Verify() {
const handleCompleted = (result: LivenessResult) => {
// result.snapshot -> Blob (image/jpeg)
// result.video -> Blob (video/webm) | null
upload(result.snapshot, result.video);
};
return (
<FaceLiveness
onCompleted={handleCompleted}
onError={(message) => console.warn(message)}
/>
);
}Styles are injected automatically — no CSS import needed.
Props
| Prop | Type | Description |
| --- | --- | --- |
| options | FaceLivenessOptions | Tunables (see below). |
| onCompleted | (r: LivenessResult) => void | Fired once the challenge is passed. |
| onError | (message: string) => void | Camera denied, timeout, model error, … |
| onFaceDetectionStatusChange | (isValid: boolean) => void | Alignment status flips. |
| showDebug | boolean | Show the on-screen stage/yaw/inference readout. |
| className | string | Extra class on the root element. |
Usage — headless hook (custom UI)
Want your own markup? Drive everything from the hook and render whatever you like:
import { useFaceLiveness } from 'ugx-face-liveness-react';
export function CustomVerify() {
const { state, videoRef, canvasRef, start, stop } = useFaceLiveness({
onCompleted: (r) => upload(r.snapshot, r.video),
onError: (m) => alert(m),
});
return (
<div>
<video ref={videoRef} autoPlay muted playsInline style={{ transform: 'scaleX(-1)' }} />
<canvas ref={canvasRef} hidden />
<p>{state.instruction}</p>
<progress value={state.progress} max={100} />
{state.stage === 'idle'
? <button onClick={start}>Start</button>
: <button onClick={stop}>Cancel</button>}
</div>
);
}state (FaceLivenessState) exposes stage, instruction, status,
progress, currentAction, isValidFaceDetected, lastDetectionResult, and
more — enough to render any UI.
For non-React or fully manual control, the same logic is available as the
framework-agnostic FaceLivenessEngine class (attach(), start(), stop(),
subscribe()).
Options
| Option | Default | Description |
| --- | --- | --- |
| assetBaseUrl | /assets/face-liveness | Where the worker/wasm/model are served. |
| detectionIntervalMs | 140 | Min ms between inferences. |
| maxDetectionIntervalMs | 280 | Cap when the device is slow (auto-throttles). |
| minDetectionConfidence | 0.55 | MediaPipe detection/presence threshold. |
| timeoutMs | 25000 | Abort the flow if not completed in time. |
| captureWidth / captureHeight | 640 / 480 | Requested camera resolution. |
| videoBitsPerSecond | 600000 | Challenge recording bitrate. |
| thresholds | {} | Partial<ChallengeThresholds> overriding the challenge tuning (e.g. { minFaceHeight: 0.4 }). Merged over the defaults; also drives the size of the on-screen alignment oval. |
| deviceDetection | { enabled: true, scoreThreshold: 0.4, minFramesToFlag: 2, minFramesToClear: 2 } | Phone/screen presentation-attack layer (see below). Set enabled: false to skip loading the extra model. |
Phone / screen detection
When deviceDetection.enabled is on (the default), the worker also runs a small
object detector (assets/models-v2/efficientdet_lite0.tflite). If a phone,
tablet or monitor is held up in front of the camera — overlapping the face —
the flow pauses and shows "Remove any phone or screen from view.", then
auto-resumes once the device is gone. A phone lying in the background does not
trip it (the device must overlap the face or dominate the frame).
Limitations — this raises the bar, it is not KYC-grade on its own:
- It catches a device only when its body/bezel is visible. A screen cropped to fill the entire frame (no edges) looks like a bare face and can slip through.
- Detection is client-side, so a determined attacker can still bypass it by injecting a fake video stream (a virtual camera). For high-stakes identity checks, verify liveness server-side as well.
Local development
npm install
npm run build # builds the worker (esbuild) + the library (tsup)
cd example # a Vite React demo that consumes the built library
npm install
npm run dev # http://localhost:5173How it relates to the Angular library
src/liveness-challenge.ts and src/face-landmarker.worker.ts are copied
verbatim from ugx-face-liveness. The camera / worker / recorder / snapshot
logic lives in a framework-agnostic FaceLivenessEngine; the React hook and
component are thin shells over it, mirroring the Angular component's outputs
(livenessCompleted, errorOccurred, faceDetectionStatusChange).
