@alternitis-corp/liveness
v0.6.1
Published
Embedded, browser-based liveness check — Face-ID-style guided head-turn via MediaPipe FaceLandmarker. Framework-agnostic core + React & Vue wrappers + a script-tag global build.
Maintainers
Readme
@alternitis-corp/liveness
Embedded, browser-based ACTIVE liveness check with a Face‑ID‑style scan ring. It guides the user through an ordered, one‑at‑a‑time sequence — turn left → turn right → look up → look down → blink → open mouth — where each head step lights only its own arc of the ring (left arc turning left, top arc looking up, …), captures the face from multiple angles, and hands you the frames.
Yaw (left/right) and pitch (up/down) are measured from translation‑invariant face landmarks (via Google
MediaPipe FaceLandmarker), so panning or shaking the device does not advance it — only a real
head turn does. Blink and mouth‑open come from MediaPipe face blendshapes (eyeBlinkLeft/Right,
jawOpen), with a landmark EAR/MAR fallback. A photo, a static dummy head, or a naïve replay can’t
satisfy the active challenges. Deliberate, smooth, and yields a detailed multi‑angle set you still
verify server‑side.
- 🛡️ Active liveness — ordered turn / look‑up‑down / blink / mouth challenges, each lighting its own ring arc, defeat photos & replays
- 🎯 Framework‑agnostic core (plain DOM) — works in React, Vue, Svelte, Angular, vanilla, Expo Web…
- ⚛️ First‑class wrappers for React and Vue, plus a
<script>global build (no bundler needed) - 📦 Zero runtime dependencies — the MediaPipe model + WASM load lazily from a CDN (overridable)
- 🔒 Runs entirely client‑side; you decide what to do with the captured frames
- 🪶 Renders its own live video + animated ring; you supply the surrounding UI and instruction text
Install
npm install @alternitis-corp/livenessRequires a secure context.
getUserMediaonly works over HTTPS (orlocalhost). On first use the browser downloads the MediaPipe face model (~13 MB) and caches it. Feature‑detect withisLivenessSupported().
Usage
React
import { LivenessCamera } from '@alternitis-corp/liveness/react';
export function Step() {
return (
<LivenessCamera
size={260}
turnThreshold={0.16} // higher = the user must turn further
onPrompt={(p) => setHint(promptText[p])} // 'center' | 'turn-left' | 'turn-right' | 'hold' | 'complete'
onProgress={(p) => {}} // 0..1 ring fill
onComplete={(frames) => upload(frames)} // JPEG data URLs across the head angles
onError={(e) => alert(e.message)}
/>
);
}Vue 3
<script setup lang="ts">
import { LivenessCamera } from '@alternitis-corp/liveness/vue';
function onFrames(frames: string[]) { upload(frames); }
</script>
<template>
<LivenessCamera :turn-threshold="0.16" @prompt="setHint" @complete="onFrames" @error="onError" />
</template>Vanilla / any framework (the core)
Use the core class directly on any square element — this is how you integrate with Svelte, Angular, Solid, Expo Web, or no framework at all:
import { LivenessCheck, isLivenessSupported } from '@alternitis-corp/liveness';
if (!isLivenessSupported()) {
// show an "open over HTTPS / allow camera" message and bail
}
const check = new LivenessCheck(document.getElementById('camera')!, {
turnThreshold: 0.16,
onPrompt: (p) => (hint.textContent = p),
onProgress: (p) => (bar.style.width = `${p * 100}%`),
onComplete: (frames) => {
fetch('/api/liveness', { method: 'POST', body: JSON.stringify({ frames }) });
},
onError: (e) => console.error(e),
});
check.start();
// later / on unmount:
check.stop();Plain <script> tag (global build, no bundler)
<div id="camera" style="width:260px;height:260px"></div>
<script src="https://unpkg.com/@alternitis-corp/liveness/dist/liveness.global.js"></script>
<script>
const check = new Liveness.LivenessCheck(document.getElementById('camera'), {
onComplete: (frames) => console.log('captured', frames.length),
});
check.start();
</script>React Native
- Expo Web / React‑Native‑Web: supported — render a host
View, grab its DOM node, and use the core (new LivenessCheck(node, …)). (That’s exactly how the Trufy app uses it.) - Native iOS/Android React Native: not supported — there’s no DOM or
getUserMedia. Native apps need a native camera + on‑device ML (e.g.react-native-vision-camera+ a face model). This package targets the web.
The guided flow
The check walks the user through the ordered sequence and reports each step via onPrompt:
| Prompt | Show your own copy |
| --- | --- |
| center | "Center your face in the circle" |
| turn-left | "Slowly turn your head to the left" |
| turn-right | "Now slowly turn to the right" |
| look-up | "Now tilt your head up" |
| look-down | "And tilt your head down" |
| blink | "Blink your eyes" |
| open-mouth | "Open your mouth" |
| hold | "Hold still…" (finishing up) |
| complete | done — onComplete(frames) fires |
The steps run in order, one at a time (set randomizeChallenges to shuffle them for anti‑replay),
and each head step fills only its own ring arc. It completes once every step is cleared, with frames
captured across the angles and a minimum duration elapsed — so it can’t be rushed.
Options
| Option | Default | Description |
| --- | --- | --- |
| size | 260 | Diameter of the camera circle, px |
| turnThreshold | 0.16 | Yaw magnitude per side (~0–0.5) required for a full turn — higher = turn further |
| pitchThreshold | 0.04 | Pitch deviation from neutral required for a clear look up / down |
| invertPitch | false | Flip up/down if a camera reports pitch inverted |
| challenges | ['turn-left','turn-right','look-up','look-down','blink','open-mouth'] | The ordered challenges, one at a time |
| randomizeChallenges | false | Shuffle the challenge order each run (anti-replay) |
| targetFrames | 9 | Frames captured across the flow (multi‑angle set) |
| minDurationMs | 4000 | Minimum run time before completing — stops it finishing too fast |
| ringColor | #5b4dff | Lit‑tick colour |
| ringTrackColor | rgba(20,20,40,0.10) | Unlit‑tick colour |
| ringWidth | 5 | Tick stroke width, px |
| mirror | true | Mirror the video (selfie view) |
| attribution | Powered by Alternitis Corp | Caption shown at the bottom of the camera; set "" to hide |
| jpegQuality | 0.9 | Captured‑frame JPEG quality |
| model | CDN | { esm, wasm, model } URL overrides (e.g. to self‑host the MediaPipe assets) |
Callbacks
onPhase(phase)—'starting' → 'loading' → 'searching' → 'live' → 'complete'(or'error')onPrompt(prompt)— step guidance (see table above)onProgress(0..1)— ring fillonComplete(frames: string[])— JPEG data URLs from centre/left/right angles once the check passesonError(error)— no secure context, camera blocked, or model failed to load
What you get & verifying it
onComplete gives you a multi‑angle set of full‑resolution JPEG frames (data URLs). The active
challenges (head sweep + randomised blink / mouth / up / down) make a photo, a static dummy head, or a
naïve replay fail — but client‑side liveness still runs on an untrusted device and is not a
substitute for server‑side verification or a dedicated PAD provider against a determined attacker.
Pair the frames with a server‑side check — a vision model and/or a human reviewer (e.g. compare the
straight‑on frame to a government‑ID photo), or a purpose‑built liveness vendor — for the actual decision.
Self‑hosting the model
To avoid the CDN at runtime, host the MediaPipe assets yourself and pass model:
new LivenessCheck(el, {
model: {
esm: '/vendor/tasks-vision/vision_bundle.mjs',
wasm: '/vendor/tasks-vision/wasm',
model: '/vendor/face_landmarker.task',
},
});Browser support
Any modern browser with getUserMedia over HTTPS and WebAssembly: Chrome/Edge, Safari (incl. iOS 16+),
Firefox. MediaPipe runs via WASM (+ WebGL where available).
Develop / build
npm install
npm run build # tsc → dist (ESM + .d.ts) plus the IIFE global (dist/liveness.global.js)
npm run typecheckPublishing (maintainers)
Scoped public package. Own the alternitis-corp npm org, then from this repo:
npm version patch | minor | major
npm publish --access public # or push a v* tag if NPM_TOKEN is set for the CI workflow