@rick427/react-native-liveness 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +20 -0
  2. package/LivenessCamera.podspec +26 -0
  3. package/README.md +167 -0
  4. package/android/build.gradle +80 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/livenesscamera/LivenessCameraPackage.kt +28 -0
  7. package/android/src/main/java/com/livenesscamera/LivenessCameraPlugin.kt +63 -0
  8. package/ios/LivenessCameraPlugin-Bridging-Header.h +3 -0
  9. package/ios/LivenessCameraPlugin.m +8 -0
  10. package/ios/LivenessCameraPlugin.swift +69 -0
  11. package/lib/module/LivenessCamera.js +283 -0
  12. package/lib/module/LivenessCamera.js.map +1 -0
  13. package/lib/module/LivenessDetector.js +23 -0
  14. package/lib/module/LivenessDetector.js.map +1 -0
  15. package/lib/module/index.js +5 -0
  16. package/lib/module/index.js.map +1 -0
  17. package/lib/module/livenessScoring.js +58 -0
  18. package/lib/module/livenessScoring.js.map +1 -0
  19. package/lib/module/package.json +1 -0
  20. package/lib/module/types.js +4 -0
  21. package/lib/module/types.js.map +1 -0
  22. package/lib/module/useLivenessCamera.js +167 -0
  23. package/lib/module/useLivenessCamera.js.map +1 -0
  24. package/lib/typescript/package.json +1 -0
  25. package/lib/typescript/src/LivenessCamera.d.ts +3 -0
  26. package/lib/typescript/src/LivenessCamera.d.ts.map +1 -0
  27. package/lib/typescript/src/LivenessDetector.d.ts +8 -0
  28. package/lib/typescript/src/LivenessDetector.d.ts.map +1 -0
  29. package/lib/typescript/src/index.d.ts +4 -0
  30. package/lib/typescript/src/index.d.ts.map +1 -0
  31. package/lib/typescript/src/livenessScoring.d.ts +11 -0
  32. package/lib/typescript/src/livenessScoring.d.ts.map +1 -0
  33. package/lib/typescript/src/types.d.ts +61 -0
  34. package/lib/typescript/src/types.d.ts.map +1 -0
  35. package/lib/typescript/src/useLivenessCamera.d.ts +21 -0
  36. package/lib/typescript/src/useLivenessCamera.d.ts.map +1 -0
  37. package/package.json +120 -0
  38. package/src/LivenessCamera.tsx +284 -0
  39. package/src/LivenessDetector.ts +34 -0
  40. package/src/index.ts +9 -0
  41. package/src/livenessScoring.ts +81 -0
  42. package/src/types.ts +88 -0
  43. package/src/useLivenessCamera.ts +206 -0
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { Animated, StyleSheet, Text, View } from 'react-native';
5
+ import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
6
+ import { Circle, Ellipse, Path, Svg } from 'react-native-svg';
7
+ import { useLivenessCamera } from "./useLivenessCamera.js";
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ const OVAL_H_RATIO = 0.55;
10
+ const OVAL_V_RATIO = 0.72;
11
+ const STROKE_WIDTH = 3;
12
+ // Cubic bezier approximation constant for a smooth ellipse
13
+ const K = 0.5523;
14
+ function getOvalColor(state) {
15
+ switch (state) {
16
+ case 'confirmed':
17
+ case 'countdown':
18
+ case 'capturing':
19
+ case 'done':
20
+ return '#4CAF50';
21
+ default:
22
+ return '#FFFFFF';
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Returns an SVG path string tracing an ellipse (cx, cy, rx, ry) using
28
+ * cubic bezier curves. Used inside a compound path with fillRule="evenodd"
29
+ * to punch a transparent hole through the dark scrim.
30
+ */
31
+ function ellipsePath(cx, cy, rx, ry) {
32
+ return [`M ${cx + rx} ${cy}`, `C ${cx + rx} ${cy - ry * K} ${cx + rx * K} ${cy - ry} ${cx} ${cy - ry}`, `C ${cx - rx * K} ${cy - ry} ${cx - rx} ${cy - ry * K} ${cx - rx} ${cy}`, `C ${cx - rx} ${cy + ry * K} ${cx - rx * K} ${cy + ry} ${cx} ${cy + ry}`, `C ${cx + rx * K} ${cy + ry} ${cx + rx} ${cy + ry * K} ${cx + rx} ${cy}`, 'Z'].join(' ');
33
+ }
34
+ function OvalOverlay({
35
+ width,
36
+ height,
37
+ state
38
+ }) {
39
+ if (width === 0 || height === 0) return null;
40
+ const cx = width / 2;
41
+ const cy = height / 2;
42
+ const rx = width * OVAL_H_RATIO / 2;
43
+ const ry = height * OVAL_V_RATIO / 2;
44
+ const color = getOvalColor(state);
45
+
46
+ // Compound path: outer rect + oval. evenodd fill rule makes the oval transparent.
47
+ const scrimD = `M0 0H${width}V${height}H0Z ${ellipsePath(cx, cy, rx, ry)}`;
48
+ const showDot = state === 'confirmed' || state === 'countdown' || state === 'capturing';
49
+ return /*#__PURE__*/_jsxs(Svg, {
50
+ style: StyleSheet.absoluteFill,
51
+ width: width,
52
+ height: height,
53
+ children: [/*#__PURE__*/_jsx(Path, {
54
+ d: scrimD,
55
+ fill: "rgba(0,0,0,0.55)",
56
+ fillRule: "evenodd"
57
+ }), /*#__PURE__*/_jsx(Ellipse, {
58
+ cx: cx,
59
+ cy: cy,
60
+ rx: rx,
61
+ ry: ry,
62
+ fill: "none",
63
+ stroke: color,
64
+ strokeWidth: STROKE_WIDTH
65
+ }), showDot && /*#__PURE__*/_jsx(Circle, {
66
+ cx: cx,
67
+ cy: cy - ry - 8,
68
+ r: 5,
69
+ fill: color
70
+ })]
71
+ });
72
+ }
73
+ function CountdownBubble({
74
+ value
75
+ }) {
76
+ // key={countdown} in the parent remounts this component on every tick,
77
+ // so [] deps are correct — each mount runs a fresh animation.
78
+ const scale = useRef(new Animated.Value(0)).current;
79
+ const opacity = useRef(new Animated.Value(0)).current;
80
+ useEffect(() => {
81
+ Animated.parallel([Animated.sequence([Animated.spring(scale, {
82
+ toValue: 1.2,
83
+ stiffness: 200,
84
+ damping: 6,
85
+ useNativeDriver: true
86
+ }), Animated.spring(scale, {
87
+ toValue: 1.0,
88
+ stiffness: 150,
89
+ damping: 8,
90
+ useNativeDriver: true
91
+ })]), Animated.timing(opacity, {
92
+ toValue: 1,
93
+ duration: 150,
94
+ useNativeDriver: true
95
+ })]).start();
96
+ return () => {
97
+ Animated.timing(opacity, {
98
+ toValue: 0,
99
+ duration: 200,
100
+ useNativeDriver: true
101
+ }).start();
102
+ };
103
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
104
+
105
+ return /*#__PURE__*/_jsx(Animated.View, {
106
+ style: [styles.countdownBubble, {
107
+ opacity,
108
+ transform: [{
109
+ scale
110
+ }]
111
+ }],
112
+ children: /*#__PURE__*/_jsx(Text, {
113
+ style: styles.countdownText,
114
+ children: value
115
+ })
116
+ });
117
+ }
118
+ export function LivenessCamera({
119
+ onCapture,
120
+ onLivenessConfirmed,
121
+ onError,
122
+ countdownFrom = 3,
123
+ livenessThreshold = 0.75,
124
+ confirmFrames = 15,
125
+ soundEnabled = true,
126
+ style
127
+ }) {
128
+ const {
129
+ hasPermission,
130
+ requestPermission
131
+ } = useCameraPermission();
132
+ const device = useCameraDevice('front');
133
+ const cameraRef = useRef(null);
134
+ const [containerSize, setContainerSize] = useState({
135
+ width: 0,
136
+ height: 0
137
+ });
138
+ const {
139
+ frameProcessor,
140
+ livenessState,
141
+ countdown,
142
+ feedback
143
+ } = useLivenessCamera({
144
+ livenessThreshold,
145
+ confirmFrames,
146
+ countdownFrom,
147
+ soundEnabled,
148
+ cameraRef,
149
+ onCapture,
150
+ onLivenessConfirmed,
151
+ onError
152
+ });
153
+ const handleLayout = useCallback(e => {
154
+ const {
155
+ width,
156
+ height
157
+ } = e.nativeEvent.layout;
158
+ setContainerSize({
159
+ width,
160
+ height
161
+ });
162
+ }, []);
163
+ useEffect(() => {
164
+ if (!hasPermission) {
165
+ requestPermission().catch(() => {
166
+ onError?.(new Error('Camera permission denied'));
167
+ });
168
+ }
169
+ }, [hasPermission, requestPermission, onError]);
170
+ if (!hasPermission) {
171
+ return /*#__PURE__*/_jsx(View, {
172
+ style: [styles.root, style, styles.centered],
173
+ children: /*#__PURE__*/_jsx(Text, {
174
+ style: styles.permissionText,
175
+ children: "Camera permission required"
176
+ })
177
+ });
178
+ }
179
+ if (!device) {
180
+ return /*#__PURE__*/_jsx(View, {
181
+ style: [styles.root, style, styles.centered],
182
+ children: /*#__PURE__*/_jsx(Text, {
183
+ style: styles.permissionText,
184
+ children: "No front camera found"
185
+ })
186
+ });
187
+ }
188
+ return /*#__PURE__*/_jsxs(View, {
189
+ style: [styles.root, style],
190
+ onLayout: handleLayout,
191
+ children: [/*#__PURE__*/_jsx(Camera, {
192
+ ref: cameraRef,
193
+ style: StyleSheet.absoluteFill,
194
+ device: device,
195
+ isActive: livenessState !== 'done' && livenessState !== 'error',
196
+ frameProcessor: frameProcessor,
197
+ photo: true,
198
+ pixelFormat: "yuv"
199
+ }), /*#__PURE__*/_jsx(OvalOverlay, {
200
+ width: containerSize.width,
201
+ height: containerSize.height,
202
+ state: livenessState
203
+ }), livenessState !== 'done' && /*#__PURE__*/_jsx(View, {
204
+ style: styles.feedbackContainer,
205
+ children: /*#__PURE__*/_jsx(Text, {
206
+ style: styles.feedbackText,
207
+ children: feedback
208
+ })
209
+ }), livenessState === 'countdown' && countdown !== null && /*#__PURE__*/_jsx(View, {
210
+ style: styles.countdownContainer,
211
+ children: /*#__PURE__*/_jsx(CountdownBubble, {
212
+ value: countdown
213
+ }, countdown)
214
+ }), livenessState === 'capturing' && /*#__PURE__*/_jsx(View, {
215
+ style: styles.captureFlash,
216
+ pointerEvents: "none"
217
+ })]
218
+ });
219
+ }
220
+ const styles = StyleSheet.create({
221
+ root: {
222
+ flex: 1,
223
+ backgroundColor: '#000',
224
+ overflow: 'hidden'
225
+ },
226
+ centered: {
227
+ justifyContent: 'center',
228
+ alignItems: 'center'
229
+ },
230
+ permissionText: {
231
+ color: '#fff',
232
+ fontSize: 16,
233
+ textAlign: 'center',
234
+ paddingHorizontal: 24
235
+ },
236
+ feedbackContainer: {
237
+ position: 'absolute',
238
+ bottom: '14%',
239
+ left: 0,
240
+ right: 0,
241
+ alignItems: 'center',
242
+ paddingHorizontal: 16
243
+ },
244
+ feedbackText: {
245
+ color: '#fff',
246
+ fontSize: 16,
247
+ fontWeight: '600',
248
+ textAlign: 'center',
249
+ textShadowColor: 'rgba(0,0,0,0.8)',
250
+ textShadowOffset: {
251
+ width: 0,
252
+ height: 1
253
+ },
254
+ textShadowRadius: 4
255
+ },
256
+ countdownContainer: {
257
+ ...StyleSheet.absoluteFillObject,
258
+ justifyContent: 'center',
259
+ alignItems: 'center'
260
+ },
261
+ countdownBubble: {
262
+ width: 96,
263
+ height: 96,
264
+ borderRadius: 48,
265
+ backgroundColor: 'rgba(255,255,255,0.15)',
266
+ borderWidth: 2,
267
+ borderColor: '#fff',
268
+ justifyContent: 'center',
269
+ alignItems: 'center'
270
+ },
271
+ countdownText: {
272
+ color: '#fff',
273
+ fontSize: 52,
274
+ fontWeight: '700',
275
+ lineHeight: 60
276
+ },
277
+ captureFlash: {
278
+ ...StyleSheet.absoluteFillObject,
279
+ backgroundColor: '#fff',
280
+ opacity: 0.4
281
+ }
282
+ });
283
+ //# sourceMappingURL=LivenessCamera.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useCallback","useEffect","useRef","useState","Animated","StyleSheet","Text","View","Camera","useCameraDevice","useCameraPermission","Circle","Ellipse","Path","Svg","useLivenessCamera","jsx","_jsx","jsxs","_jsxs","OVAL_H_RATIO","OVAL_V_RATIO","STROKE_WIDTH","K","getOvalColor","state","ellipsePath","cx","cy","rx","ry","join","OvalOverlay","width","height","color","scrimD","showDot","style","absoluteFill","children","d","fill","fillRule","stroke","strokeWidth","r","CountdownBubble","value","scale","Value","current","opacity","parallel","sequence","spring","toValue","stiffness","damping","useNativeDriver","timing","duration","start","styles","countdownBubble","transform","countdownText","LivenessCamera","onCapture","onLivenessConfirmed","onError","countdownFrom","livenessThreshold","confirmFrames","soundEnabled","hasPermission","requestPermission","device","cameraRef","containerSize","setContainerSize","frameProcessor","livenessState","countdown","feedback","handleLayout","e","nativeEvent","layout","catch","Error","root","centered","permissionText","onLayout","ref","isActive","photo","pixelFormat","feedbackContainer","feedbackText","countdownContainer","captureFlash","pointerEvents","create","flex","backgroundColor","overflow","justifyContent","alignItems","fontSize","textAlign","paddingHorizontal","position","bottom","left","right","fontWeight","textShadowColor","textShadowOffset","textShadowRadius","absoluteFillObject","borderRadius","borderWidth","borderColor","lineHeight"],"sourceRoot":"../../src","sources":["LivenessCamera.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ import { useMemo } from 'react';
4
+ import { VisionCameraProxy } from 'react-native-vision-camera';
5
+ function createPlugin() {
6
+ const plugin = VisionCameraProxy.initFrameProcessorPlugin('detectLiveness', {});
7
+ if (!plugin) {
8
+ throw new Error('[LivenessCamera] Frame Processor Plugin "detectLiveness" not found. ' + 'Make sure the native module is linked correctly.');
9
+ }
10
+ return {
11
+ detectLiveness: frame => {
12
+ 'worklet';
13
+
14
+ const result = plugin.call(frame);
15
+ // plugin.call returns a loosely-typed BasicParameterType — cast via unknown
16
+ return result;
17
+ }
18
+ };
19
+ }
20
+ export function useLivenessPlugin() {
21
+ return useMemo(() => createPlugin(), []);
22
+ }
23
+ //# sourceMappingURL=LivenessDetector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useMemo","VisionCameraProxy","createPlugin","plugin","initFrameProcessorPlugin","Error","detectLiveness","frame","result","call","useLivenessPlugin"],"sourceRoot":"../../src","sources":["LivenessDetector.ts"],"mappings":";;AAAA,SAASA,OAAO,QAAQ,OAAO;AAC/B,SAASC,iBAAiB,QAAoB,4BAA4B;AAO1E,SAASC,YAAYA,CAAA,EAAmB;EACtC,MAAMC,MAAM,GAAGF,iBAAiB,CAACG,wBAAwB,CACvD,gBAAgB,EAChB,CAAC,CACH,CAAC;EAED,IAAI,CAACD,MAAM,EAAE;IACX,MAAM,IAAIE,KAAK,CACb,sEAAsE,GACpE,kDACJ,CAAC;EACH;EAEA,OAAO;IACLC,cAAc,EAAGC,KAAY,IAAsB;MACjD,SAAS;;MACT,MAAMC,MAAM,GAAGL,MAAM,CAACM,IAAI,CAACF,KAAK,CAAC;MACjC;MACA,OAAOC,MAAM;IACf;EACF,CAAC;AACH;AAEA,OAAO,SAASE,iBAAiBA,CAAA,EAAmB;EAClD,OAAOV,OAAO,CAAC,MAAME,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC;AAC1C","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export { LivenessCamera } from "./LivenessCamera.js";
4
+ export { useLivenessCamera } from "./useLivenessCamera.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["LivenessCamera","useLivenessCamera"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,cAAc,QAAQ,qBAAkB;AACjD,SAASC,iBAAiB,QAAQ,wBAAqB","ignoreList":[]}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ const WEIGHTS = {
4
+ faceDetected: 0.2,
5
+ faceSize: 0.2,
6
+ headPose: 0.3,
7
+ eyesOpen: 0.3
8
+ };
9
+ const FACE_SIZE_MIN = 0.2;
10
+ const FACE_SIZE_MAX = 0.65;
11
+ const MAX_YAW_DEG = 20;
12
+ const MAX_PITCH_DEG = 20;
13
+ export function scoreFrame(face, frameWidth) {
14
+ if (!face.detected || frameWidth === 0) {
15
+ return {
16
+ total: 0,
17
+ faceSize: 0,
18
+ headPose: 0,
19
+ eyesOpen: 0
20
+ };
21
+ }
22
+ const faceWidthRatio = face.bounds.width / frameWidth;
23
+ const faceSize = faceWidthRatio >= FACE_SIZE_MIN && faceWidthRatio <= FACE_SIZE_MAX ? 1.0 : 0.0;
24
+ const yawOK = Math.abs(face.yawAngle) < MAX_YAW_DEG;
25
+ const pitchOK = Math.abs(face.pitchAngle) < MAX_PITCH_DEG;
26
+ const headPose = (yawOK ? 0.5 : 0) + (pitchOK ? 0.5 : 0);
27
+
28
+ // ML Kit returns -1 when classification is disabled or unavailable
29
+ const leftEye = face.leftEyeOpenProbability >= 0 ? face.leftEyeOpenProbability : 0.5;
30
+ const rightEye = face.rightEyeOpenProbability >= 0 ? face.rightEyeOpenProbability : 0.5;
31
+ const eyesOpen = (leftEye + rightEye) / 2;
32
+ const total = WEIGHTS.faceDetected * 1.0 + WEIGHTS.faceSize * faceSize + WEIGHTS.headPose * headPose + WEIGHTS.eyesOpen * eyesOpen;
33
+ return {
34
+ total,
35
+ faceSize,
36
+ headPose,
37
+ eyesOpen
38
+ };
39
+ }
40
+ export function rollingAverage(scores) {
41
+ if (scores.length === 0) return 0;
42
+ return scores.reduce((a, b) => a + b, 0) / scores.length;
43
+ }
44
+ export function getFeedback(face, frameWidth, livenessConfirmed) {
45
+ if (livenessConfirmed) return 'Liveness confirmed';
46
+ if (!face.detected) return 'Position your face in the oval';
47
+ const faceWidthRatio = face.bounds.width / frameWidth;
48
+ if (faceWidthRatio < FACE_SIZE_MIN) return 'Move closer';
49
+ if (faceWidthRatio > FACE_SIZE_MAX) return 'Move farther away';
50
+ const yawBad = Math.abs(face.yawAngle) >= MAX_YAW_DEG;
51
+ const pitchBad = Math.abs(face.pitchAngle) >= MAX_PITCH_DEG;
52
+ if (yawBad || pitchBad) return 'Look straight ahead';
53
+ const leftEye = face.leftEyeOpenProbability >= 0 ? face.leftEyeOpenProbability : 1;
54
+ const rightEye = face.rightEyeOpenProbability >= 0 ? face.rightEyeOpenProbability : 1;
55
+ if (leftEye < 0.4 || rightEye < 0.4) return 'Open your eyes';
56
+ return 'Hold still...';
57
+ }
58
+ //# sourceMappingURL=livenessScoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["WEIGHTS","faceDetected","faceSize","headPose","eyesOpen","FACE_SIZE_MIN","FACE_SIZE_MAX","MAX_YAW_DEG","MAX_PITCH_DEG","scoreFrame","face","frameWidth","detected","total","faceWidthRatio","bounds","width","yawOK","Math","abs","yawAngle","pitchOK","pitchAngle","leftEye","leftEyeOpenProbability","rightEye","rightEyeOpenProbability","rollingAverage","scores","length","reduce","a","b","getFeedback","livenessConfirmed","yawBad","pitchBad"],"sourceRoot":"../../src","sources":["livenessScoring.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["types.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { useFrameProcessor } from 'react-native-vision-camera';
5
+ import { Worklets } from 'react-native-worklets-core';
6
+ import { useLivenessPlugin } from "./LivenessDetector.js";
7
+ import { getFeedback, rollingAverage, scoreFrame } from "./livenessScoring.js";
8
+ const WINDOW_SIZE = 20;
9
+ export function useLivenessCamera(options) {
10
+ const {
11
+ livenessThreshold,
12
+ confirmFrames,
13
+ countdownFrom,
14
+ soundEnabled,
15
+ cameraRef,
16
+ onCapture,
17
+ onLivenessConfirmed,
18
+ onError
19
+ } = options;
20
+ const plugin = useLivenessPlugin();
21
+
22
+ // Mutable refs — updated every frame, never cause re-renders
23
+ const frameScores = useRef([]);
24
+ const consecutiveGood = useRef(0);
25
+ const frameWidth = useRef(0);
26
+ const stateRef = useRef('scanning');
27
+ const isCaptured = useRef(false);
28
+ const [state, setState] = useState({
29
+ livenessState: 'scanning',
30
+ livenessScore: 0,
31
+ countdown: null,
32
+ feedback: 'Position your face in the oval'
33
+ });
34
+ const setLivenessState = useCallback(next => {
35
+ stateRef.current = next;
36
+ setState(prev => ({
37
+ ...prev,
38
+ livenessState: next
39
+ }));
40
+ }, []);
41
+
42
+ // ─── Capture ──────────────────────────────────────────────────────────────
43
+
44
+ const capture = useCallback(async () => {
45
+ if (isCaptured.current || !cameraRef.current) return;
46
+ isCaptured.current = true;
47
+ setLivenessState('capturing');
48
+ try {
49
+ const photo = await cameraRef.current.takePhoto({
50
+ flash: 'off',
51
+ enableShutterSound: soundEnabled
52
+ });
53
+ const score = rollingAverage(frameScores.current);
54
+ setLivenessState('done');
55
+ onCapture({
56
+ photo,
57
+ livenessScore: score,
58
+ timestamp: Date.now()
59
+ });
60
+ } catch (err) {
61
+ setLivenessState('error');
62
+ onError?.(err instanceof Error ? err : new Error(String(err)));
63
+ }
64
+ }, [cameraRef, onCapture, onError, soundEnabled, setLivenessState]);
65
+
66
+ // ─── Countdown ────────────────────────────────────────────────────────────
67
+
68
+ const startCountdown = useCallback(() => {
69
+ setLivenessState('countdown');
70
+ let tick = countdownFrom;
71
+ setState(prev => ({
72
+ ...prev,
73
+ countdown: tick
74
+ }));
75
+ const interval = setInterval(() => {
76
+ tick -= 1;
77
+ if (tick <= 0) {
78
+ clearInterval(interval);
79
+ setState(prev => ({
80
+ ...prev,
81
+ countdown: null
82
+ }));
83
+ capture();
84
+ } else {
85
+ setState(prev => ({
86
+ ...prev,
87
+ countdown: tick
88
+ }));
89
+ }
90
+ }, 1000);
91
+ return () => clearInterval(interval);
92
+ }, [capture, countdownFrom, setLivenessState]);
93
+
94
+ // ─── Per-frame face handler (runs on JS thread, called from worklet) ──────
95
+
96
+ const handleFaceData = useCallback((face, width) => {
97
+ if (stateRef.current === 'capturing' || stateRef.current === 'done' || stateRef.current === 'error') {
98
+ return;
99
+ }
100
+ frameWidth.current = width;
101
+ const safeFace = face ?? {
102
+ detected: false,
103
+ bounds: {
104
+ x: 0,
105
+ y: 0,
106
+ width: 0,
107
+ height: 0
108
+ },
109
+ yawAngle: 0,
110
+ pitchAngle: 0,
111
+ rollAngle: 0,
112
+ leftEyeOpenProbability: -1,
113
+ rightEyeOpenProbability: -1,
114
+ smilingProbability: -1
115
+ };
116
+ const {
117
+ total
118
+ } = scoreFrame(safeFace, width);
119
+ frameScores.current.push(total);
120
+ if (frameScores.current.length > WINDOW_SIZE) {
121
+ frameScores.current.shift();
122
+ }
123
+ const avgScore = rollingAverage(frameScores.current);
124
+ if (total >= livenessThreshold) {
125
+ consecutiveGood.current += 1;
126
+ } else {
127
+ consecutiveGood.current = 0;
128
+ }
129
+ const isLive = consecutiveGood.current >= confirmFrames && avgScore >= livenessThreshold;
130
+ const feedback = getFeedback(safeFace, width, isLive);
131
+ setState(prev => ({
132
+ ...prev,
133
+ livenessScore: avgScore,
134
+ feedback
135
+ }));
136
+ if (isLive && stateRef.current === 'scanning') {
137
+ setLivenessState('confirmed');
138
+ onLivenessConfirmed?.();
139
+ startCountdown();
140
+ }
141
+ }, [confirmFrames, livenessThreshold, onLivenessConfirmed, setLivenessState, startCountdown]);
142
+
143
+ // ─── Frame processor ──────────────────────────────────────────────────────
144
+
145
+ const handleFaceDataJS = useMemo(() => Worklets.createRunOnJS(handleFaceData), [handleFaceData]);
146
+ const frameProcessor = useFrameProcessor(frame => {
147
+ 'worklet';
148
+
149
+ const face = plugin.detectLiveness(frame);
150
+ handleFaceDataJS(face, frame.width);
151
+ }, [plugin, handleFaceDataJS]);
152
+ useEffect(() => {
153
+ return () => {
154
+ frameScores.current = [];
155
+ consecutiveGood.current = 0;
156
+ isCaptured.current = false;
157
+ };
158
+ }, []);
159
+ return {
160
+ frameProcessor,
161
+ livenessState: state.livenessState,
162
+ livenessScore: state.livenessScore,
163
+ countdown: state.countdown,
164
+ feedback: state.feedback
165
+ };
166
+ }
167
+ //# sourceMappingURL=useLivenessCamera.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","useFrameProcessor","Worklets","useLivenessPlugin","getFeedback","rollingAverage","scoreFrame","WINDOW_SIZE","useLivenessCamera","options","livenessThreshold","confirmFrames","countdownFrom","soundEnabled","cameraRef","onCapture","onLivenessConfirmed","onError","plugin","frameScores","consecutiveGood","frameWidth","stateRef","isCaptured","state","setState","livenessState","livenessScore","countdown","feedback","setLivenessState","next","current","prev","capture","photo","takePhoto","flash","enableShutterSound","score","timestamp","Date","now","err","Error","String","startCountdown","tick","interval","setInterval","clearInterval","handleFaceData","face","width","safeFace","detected","bounds","x","y","height","yawAngle","pitchAngle","rollAngle","leftEyeOpenProbability","rightEyeOpenProbability","smilingProbability","total","push","length","shift","avgScore","isLive","handleFaceDataJS","createRunOnJS","frameProcessor","frame","detectLiveness"],"sourceRoot":"../../src","sources":["useLivenessCamera.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,3 @@
1
+ import type { LivenessCameraProps } from './types';
2
+ export declare function LivenessCamera({ onCapture, onLivenessConfirmed, onError, countdownFrom, livenessThreshold, confirmFrames, soundEnabled, style, }: LivenessCameraProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=LivenessCamera.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AA2HlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAiB,EACjB,iBAAwB,EACxB,aAAkB,EAClB,YAAmB,EACnB,KAAK,GACN,EAAE,mBAAmB,2CAiFrB"}
@@ -0,0 +1,8 @@
1
+ import { type Frame } from 'react-native-vision-camera';
2
+ import type { FaceData } from './types';
3
+ type LivenessPlugin = {
4
+ detectLiveness: (frame: Frame) => FaceData | null;
5
+ };
6
+ export declare function useLivenessPlugin(): LivenessPlugin;
7
+ export {};
8
+ //# sourceMappingURL=LivenessDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LivenessDetector.d.ts","sourceRoot":"","sources":["../../../src/LivenessDetector.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,KAAK,cAAc,GAAG;IACpB,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,QAAQ,GAAG,IAAI,CAAC;CACnD,CAAC;AAyBF,wBAAgB,iBAAiB,IAAI,cAAc,CAElD"}
@@ -0,0 +1,4 @@
1
+ export { LivenessCamera } from './LivenessCamera';
2
+ export { useLivenessCamera } from './useLivenessCamera';
3
+ export type { CaptureResult, FaceData, FeedbackMessage, LivenessCameraProps, LivenessState, } from './types';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { FaceData, FeedbackMessage } from './types';
2
+ export type FrameScore = {
3
+ total: number;
4
+ faceSize: number;
5
+ headPose: number;
6
+ eyesOpen: number;
7
+ };
8
+ export declare function scoreFrame(face: FaceData, frameWidth: number): FrameScore;
9
+ export declare function rollingAverage(scores: number[]): number;
10
+ export declare function getFeedback(face: FaceData, frameWidth: number, livenessConfirmed: boolean): FeedbackMessage;
11
+ //# sourceMappingURL=livenessScoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livenessScoring.d.ts","sourceRoot":"","sources":["../../../src/livenessScoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAczD,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CA6BzE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAGvD;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,OAAO,GACzB,eAAe,CAmBjB"}