/* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; import { useState, useRef, useEffect } from "react"; import Webcam from "react-webcam"; import { Card, CardContent } from "@/components/ui/card"; import { Loader2 } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import * as faceapi from "face-api.js"; export default function FaceLiveness() { const webcamRef = useRef(null); const [isModelLoading, setIsModelLoading] = useState(true); const [isProcessing, setIsProcessing] = useState(false); const [previousExpressions, setPreviousExpressions] = useState(null); const processingTimeoutRef = useRef(null); const { toast } = useToast(); useEffect(() => { const loadModels = async () => { try { const MODEL_URL = "https://justadudewhohacks.github.io/face-api.js/models"; await Promise.all([ faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL), faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL), faceapi.nets.faceExpressionNet.loadFromUri(MODEL_URL), ]); setIsModelLoading(false); } catch (error) { console.error("Error loading models:", error); toast({ title: "Error", description: "Failed to load face detection models. Please refresh the page.", variant: "destructive", }); } }; loadModels(); }, [toast]); const checkLiveness = (expressions: any, landmarks: any) => { if (!previousExpressions) { setPreviousExpressions(expressions); return false; } // Check for expression changes const expressionThreshold = 0.1; let hasExpressionChange = false; for (const expression in expressions) { const diff = Math.abs( expressions[expression] - previousExpressions[expression] ); if (diff > expressionThreshold) { hasExpressionChange = true; break; } } // Check for natural facial movement using landmarks const eyeBlinkDetected = detectEyeBlink(landmarks); setPreviousExpressions(expressions); return hasExpressionChange || eyeBlinkDetected; }; const detectEyeBlink = (landmarks: any) => { const leftEye = landmarks.getLeftEye(); const rightEye = landmarks.getRightEye(); // Calculate eye aspect ratio const leftEAR = getEyeAspectRatio(leftEye); const rightEAR = getEyeAspectRatio(rightEye); // If either eye is closed (low aspect ratio), consider it a blink const blinkThreshold = 0.2; return leftEAR < blinkThreshold || rightEAR < blinkThreshold; }; const getEyeAspectRatio = (eye: any) => { // Calculate the eye aspect ratio using the landmark points const height1 = distance(eye[1], eye[5]); const height2 = distance(eye[2], eye[4]); const width = distance(eye[0], eye[3]); return (height1 + height2) / (2.0 * width); }; const distance = (point1: any, point2: any) => { return Math.sqrt( Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2) ); }; useEffect(() => { const processFrame = async () => { if (!webcamRef.current || isProcessing || isModelLoading) return; setIsProcessing(true); try { const imageSrc = webcamRef.current.getScreenshot(); if (!imageSrc) return; const img = new Image(); img.src = imageSrc; await new Promise((resolve) => (img.onload = resolve)); const detections = await faceapi .detectAllFaces(img, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks() .withFaceExpressions(); if (detections.length > 0) { // Process each detected face with high confidence detections .filter((detection) => detection.detection.score > 0.7) .forEach((detection) => { const isLive = checkLiveness( detection.expressions, detection.landmarks ); if (isLive) { toast({ title: "Liveness Detected", description: "Real face detected with natural movements", }); } else { toast({ title: "Liveness Check", description: "Please move or blink naturally", variant: "destructive", }); } }); } } catch (error) { console.error("Processing error:", error); } finally { setIsProcessing(false); // Schedule next frame processing processingTimeoutRef.current = setTimeout(processFrame, 1000); // Process every second } }; if (!isModelLoading) { processFrame(); } return () => { if (processingTimeoutRef.current) { clearTimeout(processingTimeoutRef.current); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [isModelLoading, isProcessing, toast]); if (isModelLoading) { return (

Loading face detection models...

); } return (

Move your face naturally or blink to verify liveness

); }