Skip to content

dragonalter/liveness

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@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/liveness

Requires a secure context. getUserMedia only works over HTTPS (or localhost). On first use the browser downloads the MediaPipe face model (~13 MB) and caches it. Feature‑detect with isLivenessSupported().


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 fill
  • onComplete(frames: string[]) — JPEG data URLs from centre/left/right angles once the check passes
  • onError(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 typecheck

Publishing (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

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors