@rick427/react-native-liveness 0.2.3 → 0.3.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.
- package/README.md +7 -13
- package/lib/module/LivenessCamera.js +27 -34
- package/lib/module/LivenessCamera.js.map +1 -1
- package/lib/module/LivenessCameraModal.js +0 -4
- package/lib/module/LivenessCameraModal.js.map +1 -1
- package/lib/module/livenessScoring.js +33 -67
- package/lib/module/livenessScoring.js.map +1 -1
- package/lib/module/useLivenessCamera.js +49 -75
- package/lib/module/useLivenessCamera.js.map +1 -1
- package/lib/typescript/src/LivenessCamera.d.ts +1 -1
- package/lib/typescript/src/LivenessCamera.d.ts.map +1 -1
- package/lib/typescript/src/LivenessCameraModal.d.ts +1 -1
- package/lib/typescript/src/LivenessCameraModal.d.ts.map +1 -1
- package/lib/typescript/src/livenessScoring.d.ts +13 -8
- package/lib/typescript/src/livenessScoring.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +2 -10
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useLivenessCamera.d.ts +0 -2
- package/lib/typescript/src/useLivenessCamera.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/LivenessCamera.tsx +10 -20
- package/src/LivenessCameraModal.tsx +0 -4
- package/src/livenessScoring.ts +54 -103
- package/src/types.ts +4 -17
- package/src/useLivenessCamera.ts +56 -96
|
@@ -4,25 +4,20 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
4
4
|
import { runAtTargetFps, useFrameProcessor } from 'react-native-vision-camera';
|
|
5
5
|
import { Worklets } from 'react-native-worklets-core';
|
|
6
6
|
import { useLivenessPlugin } from "./LivenessDetector.js";
|
|
7
|
-
import {
|
|
8
|
-
const
|
|
9
|
-
// How many frames to decay consecutiveGood by on a bad frame.
|
|
10
|
-
// 1 means a single noisy/blink frame only undoes one good frame.
|
|
11
|
-
const CONSECUTIVE_DECAY = 1;
|
|
7
|
+
import { CHALLENGE_STEPS } from "./livenessScoring.js";
|
|
8
|
+
const TOTAL_STEPS = CHALLENGE_STEPS.length;
|
|
12
9
|
const _worklet_11184642299290_init_data = {
|
|
13
10
|
code: "function useLivenessCameraTs1(){const{plugin,frame,handleFaceDataJS}=this.__closure;const face=plugin.detectLiveness(frame);handleFaceDataJS(face,frame.width);}",
|
|
14
11
|
location: "/home/runner/work/react-native-liveness/react-native-liveness/src/useLivenessCamera.ts",
|
|
15
|
-
sourceMap: "{\"version\":3,\"names\":[\"useLivenessCameraTs1\",\"plugin\",\"frame\",\"handleFaceDataJS\",\"__closure\",\"face\",\"detectLiveness\",\"width\"],\"sources\":[\"/home/runner/work/react-native-liveness/react-native-liveness/src/useLivenessCamera.ts\"],\"mappings\":\"
|
|
12
|
+
sourceMap: "{\"version\":3,\"names\":[\"useLivenessCameraTs1\",\"plugin\",\"frame\",\"handleFaceDataJS\",\"__closure\",\"face\",\"detectLiveness\",\"width\"],\"sources\":[\"/home/runner/work/react-native-liveness/react-native-liveness/src/useLivenessCamera.ts\"],\"mappings\":\"AAkLyB,SAAAA,oBAAMA,CAAA,QAAAC,MAAA,CAAAC,KAAA,CAAAC,gBAAA,OAAAC,SAAA,CAEvB,KAAM,CAAAC,IAAI,CAAGJ,MAAM,CAACK,cAAc,CAACJ,KAAK,CAAC,CACzCC,gBAAgB,CAACE,IAAI,CAAEH,KAAK,CAACK,KAAK,CAAC,CACrC\",\"ignoreList\":[]}"
|
|
16
13
|
};
|
|
17
14
|
const _worklet_14471027507603_init_data = {
|
|
18
15
|
code: "function useLivenessCameraTs2(frame){const{runAtTargetFps,_worklet_11184642299290_init_data,plugin,handleFaceDataJS}=this.__closure;runAtTargetFps(20,function useLivenessCameraTs1Factory({_worklet_11184642299290_init_data:_worklet_11184642299290_init_data,plugin:plugin,frame:frame,handleFaceDataJS:handleFaceDataJS}){const _e=[new global.Error(),-4,-27];const useLivenessCameraTs1=function(){const face=plugin.detectLiveness(frame);handleFaceDataJS(face,frame.width);};useLivenessCameraTs1.__closure={plugin:plugin,frame:frame,handleFaceDataJS:handleFaceDataJS};useLivenessCameraTs1.__workletHash=11184642299290;useLivenessCameraTs1.__pluginVersion=\"0.8.3\";useLivenessCameraTs1.__initData=_worklet_11184642299290_init_data;useLivenessCameraTs1.__stackDetails=_e;return useLivenessCameraTs1;}({_worklet_11184642299290_init_data:_worklet_11184642299290_init_data,plugin:plugin,frame:frame,handleFaceDataJS:handleFaceDataJS}));}",
|
|
19
16
|
location: "/home/runner/work/react-native-liveness/react-native-liveness/src/useLivenessCamera.ts",
|
|
20
|
-
sourceMap: "{\"version\":3,\"names\":[\"useLivenessCameraTs2\",\"frame\",\"runAtTargetFps\",\"_worklet_11184642299290_init_data\",\"plugin\",\"handleFaceDataJS\",\"__closure\",\"useLivenessCameraTs1Factory\",\"_e\",\"global\",\"Error\",\"useLivenessCameraTs1\",\"face\",\"detectLiveness\",\"width\",\"__workletHash\",\"__pluginVersion\",\"__initData\",\"__stackDetails\"],\"sources\":[\"/home/runner/work/react-native-liveness/react-native-liveness/src/useLivenessCamera.ts\"],\"mappings\":\"
|
|
17
|
+
sourceMap: "{\"version\":3,\"names\":[\"useLivenessCameraTs2\",\"frame\",\"runAtTargetFps\",\"_worklet_11184642299290_init_data\",\"plugin\",\"handleFaceDataJS\",\"__closure\",\"useLivenessCameraTs1Factory\",\"_e\",\"global\",\"Error\",\"useLivenessCameraTs1\",\"face\",\"detectLiveness\",\"width\",\"__workletHash\",\"__pluginVersion\",\"__initData\",\"__stackDetails\"],\"sources\":[\"/home/runner/work/react-native-liveness/react-native-liveness/src/useLivenessCamera.ts\"],\"mappings\":\"AAgLI,QAAC,CAAAA,oBAAiBA,CAAAC,KAAA,QAAAC,cAAA,CAAAC,iCAAA,CAAAC,MAAA,CAAAC,gBAAA,OAAAC,SAAA,CAEhBJ,cAAc,CAAC,EAAE,CAAE,SAAAK,4BAAA,CAAAJ,iCAAA,CAAAA,iCAAA,CAEJC,MAAM,CAANA,MAAM,CAAgBH,KAAK,CAALA,KAAK,CACxCI,gBAAA,CAAAA,gBAAgB,SAAAG,EAAA,MAAAC,MAAA,CAAAC,KAAA,iBAAAC,oBAAA,SAAAA,CAAA,CAHO,CAEvB,KAAM,CAAAC,IAAI,CAAGR,MAAM,CAACS,cAAc,CAACZ,KAAK,CAAC,CACzCI,gBAAgB,CAACO,IAAI,CAAEX,KAAK,CAACa,KAAK,CAAC,CACrC,CAAC,CAAAH,oBAAA,CAAAL,SAAA,EAFcF,MAAM,CAANA,MAAM,CAAgBH,KAAK,CAALA,KAAK,CACxCI,gBAAA,CAAAA,gBAAgB,EAAAM,oBAAA,CAAAI,aAAA,gBAAAJ,oBAAA,CAAAK,eAAA,SAAAL,oBAAA,CAAAM,UAAA,CAAAd,iCAAA,CAAAQ,oBAAA,CAAAO,cAAA,CAAAV,EAAA,QAAAG,oBAAA,EAHC,EAAAR,iCAAA,CAAAA,iCAAA,CAEJC,MAAM,CAANA,MAAM,CAAgBH,KAAK,CAALA,KAAK,CACxCI,gBAAA,CAAAA,gBAAgB,EACjB,CAAC,CACJ\",\"ignoreList\":[]}"
|
|
21
18
|
};
|
|
22
19
|
export function useLivenessCamera(options) {
|
|
23
20
|
const {
|
|
24
|
-
livenessThreshold,
|
|
25
|
-
confirmFrames,
|
|
26
21
|
countdownFrom,
|
|
27
22
|
soundEnabled,
|
|
28
23
|
cameraRef,
|
|
@@ -32,24 +27,16 @@ export function useLivenessCamera(options) {
|
|
|
32
27
|
} = options;
|
|
33
28
|
const plugin = useLivenessPlugin();
|
|
34
29
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const frameWidth = useRef(0);
|
|
30
|
+
// ── Step machine refs — mutated every frame, never cause re-renders ────────
|
|
31
|
+
const currentStepIdx = useRef(0);
|
|
32
|
+
const stepFrameCount = useRef(0);
|
|
39
33
|
const stateRef = useRef('scanning');
|
|
40
34
|
const isCaptured = useRef(false);
|
|
41
|
-
|
|
42
|
-
// Feedback debouncing: only update the displayed text after the same message
|
|
43
|
-
// has been stable for FEEDBACK_DEBOUNCE_MS. Prevents rapid flickering when
|
|
44
|
-
// ML Kit oscillates between two states on consecutive frames.
|
|
45
|
-
const FEEDBACK_DEBOUNCE_MS = 400;
|
|
46
|
-
const feedbackTimer = useRef(null);
|
|
47
|
-
const pendingFeedback = useRef('Position your face in the circle');
|
|
48
35
|
const [state, setState] = useState({
|
|
49
36
|
livenessState: 'scanning',
|
|
50
37
|
livenessScore: 0,
|
|
51
38
|
countdown: null,
|
|
52
|
-
feedback:
|
|
39
|
+
feedback: CHALLENGE_STEPS[0].instruction
|
|
53
40
|
});
|
|
54
41
|
const setLivenessState = useCallback(next => {
|
|
55
42
|
stateRef.current = next;
|
|
@@ -59,8 +46,7 @@ export function useLivenessCamera(options) {
|
|
|
59
46
|
}));
|
|
60
47
|
}, []);
|
|
61
48
|
|
|
62
|
-
//
|
|
63
|
-
|
|
49
|
+
// ── Capture ────────────────────────────────────────────────────────────────
|
|
64
50
|
const capture = useCallback(async () => {
|
|
65
51
|
if (isCaptured.current || !cameraRef.current) return;
|
|
66
52
|
isCaptured.current = true;
|
|
@@ -70,11 +56,10 @@ export function useLivenessCamera(options) {
|
|
|
70
56
|
flash: 'off',
|
|
71
57
|
enableShutterSound: soundEnabled
|
|
72
58
|
});
|
|
73
|
-
const score = rollingAverage(frameScores.current);
|
|
74
59
|
setLivenessState('done');
|
|
75
60
|
onCapture({
|
|
76
61
|
photo,
|
|
77
|
-
livenessScore:
|
|
62
|
+
livenessScore: 1,
|
|
78
63
|
timestamp: Date.now()
|
|
79
64
|
});
|
|
80
65
|
} catch (err) {
|
|
@@ -83,8 +68,7 @@ export function useLivenessCamera(options) {
|
|
|
83
68
|
}
|
|
84
69
|
}, [cameraRef, onCapture, onError, soundEnabled, setLivenessState]);
|
|
85
70
|
|
|
86
|
-
//
|
|
87
|
-
|
|
71
|
+
// ── Countdown ──────────────────────────────────────────────────────────────
|
|
88
72
|
const startCountdown = useCallback(() => {
|
|
89
73
|
setLivenessState('countdown');
|
|
90
74
|
let tick = countdownFrom;
|
|
@@ -111,13 +95,10 @@ export function useLivenessCamera(options) {
|
|
|
111
95
|
return () => clearInterval(interval);
|
|
112
96
|
}, [capture, countdownFrom, setLivenessState]);
|
|
113
97
|
|
|
114
|
-
//
|
|
115
|
-
|
|
98
|
+
// ── Per-frame face handler (runs on JS thread, called from worklet) ────────
|
|
116
99
|
const handleFaceData = useCallback((face, width) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
frameWidth.current = width;
|
|
100
|
+
const s = stateRef.current;
|
|
101
|
+
if (s === 'confirmed' || s === 'countdown' || s === 'capturing' || s === 'done' || s === 'error') return;
|
|
121
102
|
const safeFace = face ?? {
|
|
122
103
|
detected: false,
|
|
123
104
|
bounds: {
|
|
@@ -133,55 +114,51 @@ export function useLivenessCamera(options) {
|
|
|
133
114
|
rightEyeOpenProbability: -1,
|
|
134
115
|
smilingProbability: -1
|
|
135
116
|
};
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
} = scoreFrame(safeFace, width);
|
|
139
|
-
frameScores.current.push(total);
|
|
140
|
-
if (frameScores.current.length > WINDOW_SIZE) {
|
|
141
|
-
frameScores.current.shift();
|
|
142
|
-
}
|
|
143
|
-
const avgScore = rollingAverage(frameScores.current);
|
|
117
|
+
const step = CHALLENGE_STEPS[currentStepIdx.current];
|
|
118
|
+
const stepMet = step.check(safeFace, width);
|
|
144
119
|
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
consecutiveGood.current += 1;
|
|
120
|
+
// Advance or gently decay the frame counter
|
|
121
|
+
if (stepMet) {
|
|
122
|
+
stepFrameCount.current += 1;
|
|
149
123
|
} else {
|
|
150
|
-
|
|
124
|
+
stepFrameCount.current = Math.max(0, stepFrameCount.current - 1);
|
|
151
125
|
}
|
|
152
126
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Debounce the feedback text: only apply a new message after it has been
|
|
160
|
-
// stable for FEEDBACK_DEBOUNCE_MS, preventing rapid label flickering.
|
|
161
|
-
const newFeedback = getFeedback(safeFace, width, isLive);
|
|
162
|
-
if (newFeedback !== pendingFeedback.current) {
|
|
163
|
-
pendingFeedback.current = newFeedback;
|
|
164
|
-
if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
|
|
165
|
-
feedbackTimer.current = setTimeout(() => {
|
|
127
|
+
// Step complete?
|
|
128
|
+
if (stepFrameCount.current >= step.framesRequired) {
|
|
129
|
+
stepFrameCount.current = 0;
|
|
130
|
+
const nextIdx = currentStepIdx.current + 1;
|
|
131
|
+
if (nextIdx >= TOTAL_STEPS) {
|
|
132
|
+
// All challenges done — confirm liveness
|
|
166
133
|
setState(prev => ({
|
|
167
134
|
...prev,
|
|
168
|
-
|
|
135
|
+
livenessScore: 1,
|
|
136
|
+
feedback: 'Liveness confirmed'
|
|
169
137
|
}));
|
|
170
|
-
|
|
138
|
+
setLivenessState('confirmed');
|
|
139
|
+
onLivenessConfirmed?.();
|
|
140
|
+
startCountdown();
|
|
141
|
+
} else {
|
|
142
|
+
// Advance to next challenge
|
|
143
|
+
currentStepIdx.current = nextIdx;
|
|
144
|
+
setState(prev => ({
|
|
145
|
+
...prev,
|
|
146
|
+
livenessScore: nextIdx / TOTAL_STEPS,
|
|
147
|
+
feedback: CHALLENGE_STEPS[nextIdx].instruction
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
171
151
|
}
|
|
152
|
+
|
|
153
|
+
// Smooth arc progress within the current step
|
|
154
|
+
const progress = (currentStepIdx.current + stepFrameCount.current / step.framesRequired) / TOTAL_STEPS;
|
|
172
155
|
setState(prev => ({
|
|
173
156
|
...prev,
|
|
174
|
-
livenessScore:
|
|
157
|
+
livenessScore: progress
|
|
175
158
|
}));
|
|
176
|
-
|
|
177
|
-
setLivenessState('confirmed');
|
|
178
|
-
onLivenessConfirmed?.();
|
|
179
|
-
startCountdown();
|
|
180
|
-
}
|
|
181
|
-
}, [confirmFrames, livenessThreshold, onLivenessConfirmed, setLivenessState, startCountdown]);
|
|
182
|
-
|
|
183
|
-
// ─── Frame processor ──────────────────────────────────────────────────────
|
|
159
|
+
}, [onLivenessConfirmed, setLivenessState, startCountdown]);
|
|
184
160
|
|
|
161
|
+
// ── Frame processor ────────────────────────────────────────────────────────
|
|
185
162
|
const handleFaceDataJS = useMemo(() => Worklets.createRunOnJS(handleFaceData), [handleFaceData]);
|
|
186
163
|
const frameProcessor = useFrameProcessor(function useLivenessCameraTs2Factory({
|
|
187
164
|
_worklet_14471027507603_init_data,
|
|
@@ -192,8 +169,6 @@ export function useLivenessCamera(options) {
|
|
|
192
169
|
}) {
|
|
193
170
|
const _e = [new global.Error(), -5, -27];
|
|
194
171
|
const useLivenessCameraTs2 = function (frame) {
|
|
195
|
-
// Camera preview renders at full fps (60). ML Kit only needs ~20fps —
|
|
196
|
-
// running it on every frame would block the render thread unnecessarily.
|
|
197
172
|
runAtTargetFps(20, function useLivenessCameraTs1Factory({
|
|
198
173
|
_worklet_11184642299290_init_data,
|
|
199
174
|
plugin,
|
|
@@ -242,10 +217,9 @@ export function useLivenessCamera(options) {
|
|
|
242
217
|
}), [plugin, handleFaceDataJS]);
|
|
243
218
|
useEffect(() => {
|
|
244
219
|
return () => {
|
|
245
|
-
|
|
246
|
-
|
|
220
|
+
currentStepIdx.current = 0;
|
|
221
|
+
stepFrameCount.current = 0;
|
|
247
222
|
isCaptured.current = false;
|
|
248
|
-
if (feedbackTimer.current) clearTimeout(feedbackTimer.current);
|
|
249
223
|
};
|
|
250
224
|
}, []);
|
|
251
225
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","runAtTargetFps","useFrameProcessor","Worklets","useLivenessPlugin","
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","runAtTargetFps","useFrameProcessor","Worklets","useLivenessPlugin","CHALLENGE_STEPS","TOTAL_STEPS","length","_worklet_11184642299290_init_data","code","location","sourceMap","_worklet_14471027507603_init_data","useLivenessCamera","options","countdownFrom","soundEnabled","cameraRef","onCapture","onLivenessConfirmed","onError","plugin","currentStepIdx","stepFrameCount","stateRef","isCaptured","state","setState","livenessState","livenessScore","countdown","feedback","instruction","setLivenessState","next","current","prev","capture","photo","takePhoto","flash","enableShutterSound","timestamp","Date","now","err","Error","String","startCountdown","tick","interval","setInterval","clearInterval","handleFaceData","face","width","s","safeFace","detected","bounds","x","y","height","yawAngle","pitchAngle","rollAngle","leftEyeOpenProbability","rightEyeOpenProbability","smilingProbability","step","stepMet","check","Math","max","framesRequired","nextIdx","progress","handleFaceDataJS","createRunOnJS","frameProcessor","useLivenessCameraTs2Factory","_e","global","useLivenessCameraTs2","frame","useLivenessCameraTs1Factory","useLivenessCameraTs1","detectLiveness","__closure","__workletHash","__pluginVersion","__initData","__stackDetails"],"sourceRoot":"../../src","sources":["useLivenessCamera.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { LivenessCameraProps } from './types';
|
|
2
|
-
export declare function LivenessCamera({ onCapture, onLivenessConfirmed, onError, countdownFrom,
|
|
2
|
+
export declare function LivenessCamera({ onCapture, onLivenessConfirmed, onError, countdownFrom, soundEnabled, fontFamily, style, }: LivenessCameraProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=LivenessCamera.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAySlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAiB,EACjB,YAAmB,EACnB,UAAyB,EACzB,KAAK,GACN,EAAE,mBAAmB,2CA4FrB"}
|
|
@@ -11,5 +11,5 @@ import type { LivenessCameraModalProps } from './types';
|
|
|
11
11
|
* />
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
|
-
export declare function LivenessCameraModal({ visible, onClose, animationType, closeButtonStyle, closeButtonIconColor, closeButtonIconSize, onCapture, onLivenessConfirmed, onError, countdownFrom,
|
|
14
|
+
export declare function LivenessCameraModal({ visible, onClose, animationType, closeButtonStyle, closeButtonIconColor, closeButtonIconSize, onCapture, onLivenessConfirmed, onError, countdownFrom, soundEnabled, fontFamily, }: LivenessCameraModalProps): import("react/jsx-runtime").JSX.Element;
|
|
15
15
|
//# sourceMappingURL=LivenessCameraModal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LivenessCameraModal.d.ts","sourceRoot":"","sources":["../../../src/LivenessCameraModal.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAwBxD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,aAAuB,EACvB,gBAAgB,EAChB,oBAA6B,EAC7B,mBAAwB,EAExB,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAa,EACb,
|
|
1
|
+
{"version":3,"file":"LivenessCameraModal.d.ts","sourceRoot":"","sources":["../../../src/LivenessCameraModal.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAwBxD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,aAAuB,EACvB,gBAAgB,EAChB,oBAA6B,EAC7B,mBAAwB,EAExB,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAa,EACb,YAAY,EACZ,UAAU,GACX,EAAE,wBAAwB,2CAmC1B"}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { FaceData, FeedbackMessage } from './types';
|
|
2
|
-
export type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
export type ChallengeStep = {
|
|
3
|
+
/** Text shown to the user while this step is active. */
|
|
4
|
+
instruction: FeedbackMessage;
|
|
5
|
+
/** How many consecutive ML Kit frames must pass the check to complete the step. */
|
|
6
|
+
framesRequired: number;
|
|
7
|
+
/** Returns true when the face satisfies this step's condition. */
|
|
8
|
+
check: (face: FaceData, frameWidth: number) => boolean;
|
|
7
9
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Sequential liveness challenges.
|
|
12
|
+
* Each step completes when `framesRequired` consecutive frames pass `check`.
|
|
13
|
+
* The progress arc fills 1/N per step, then turns green at the end.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CHALLENGE_STEPS: readonly ChallengeStep[];
|
|
11
16
|
//# sourceMappingURL=livenessScoring.d.ts.map
|
|
@@ -1 +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;
|
|
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;AAOzD,MAAM,MAAM,aAAa,GAAG;IAC1B,wDAAwD;IACxD,WAAW,EAAE,eAAe,CAAC;IAC7B,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;CACxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAuC1C,CAAC"}
|
|
@@ -16,9 +16,10 @@ export type FaceData = {
|
|
|
16
16
|
smilingProbability: number;
|
|
17
17
|
};
|
|
18
18
|
export type LivenessState = 'idle' | 'scanning' | 'confirmed' | 'countdown' | 'capturing' | 'done' | 'error';
|
|
19
|
-
export type FeedbackMessage = 'Position your face in the circle' | '
|
|
19
|
+
export type FeedbackMessage = 'Position your face in the circle' | 'Turn your head slightly' | 'Now look straight ahead' | 'Now blink' | 'Liveness confirmed' | '';
|
|
20
20
|
export type CaptureResult = {
|
|
21
21
|
photo: PhotoFile;
|
|
22
|
+
/** Always 1.0 — all challenges were passed before capture. */
|
|
22
23
|
livenessScore: number;
|
|
23
24
|
timestamp: number;
|
|
24
25
|
};
|
|
@@ -39,15 +40,6 @@ export type LivenessCameraProps = {
|
|
|
39
40
|
* Countdown start value. Defaults to 3.
|
|
40
41
|
*/
|
|
41
42
|
countdownFrom?: number;
|
|
42
|
-
/**
|
|
43
|
-
* Score (0–1) required to confirm liveness. Defaults to 0.75.
|
|
44
|
-
*/
|
|
45
|
-
livenessThreshold?: number;
|
|
46
|
-
/**
|
|
47
|
-
* Number of consecutive high-score frames required before liveness is
|
|
48
|
-
* confirmed. Defaults to 10 (~500ms at 20fps).
|
|
49
|
-
*/
|
|
50
|
-
confirmFrames?: number;
|
|
51
43
|
/**
|
|
52
44
|
* Style applied to the root container.
|
|
53
45
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;IAChC,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,eAAe,GACvB,kCAAkC,GAClC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;IAChC,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,eAAe,GACvB,kCAAkC,GAClC,yBAAyB,GACzB,yBAAyB,GACzB,WAAW,GACX,oBAAoB,GACpB,EAAE,CAAC;AAEP,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC;;OAEG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAE3C;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC,GAAG;IAC1E;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IAE5C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAE7B;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC"}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { Camera } from 'react-native-vision-camera';
|
|
2
2
|
import type { CaptureResult, FeedbackMessage, LivenessState } from './types';
|
|
3
3
|
type Options = {
|
|
4
|
-
livenessThreshold: number;
|
|
5
|
-
confirmFrames: number;
|
|
6
4
|
countdownFrom: number;
|
|
7
5
|
soundEnabled: boolean;
|
|
8
6
|
cameraRef: React.RefObject<Camera | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/useLivenessCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAS,MAAM,4BAA4B,CAAC;AAKhE,OAAO,KAAK,EACV,aAAa,EAEb,eAAe,EACf,aAAa,EACd,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"useLivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/useLivenessCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAS,MAAM,4BAA4B,CAAC;AAKhE,OAAO,KAAK,EACV,aAAa,EAEb,eAAe,EACf,aAAa,EACd,MAAM,SAAS,CAAC;AAIjB,KAAK,OAAO,GAAG;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAUF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO;;;;;;EA0KjD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rick427/react-native-liveness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Liveness detection library for React Native using Vision Camera v4 and ML Kit",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/LivenessCamera.tsx
CHANGED
|
@@ -47,9 +47,9 @@ const BRACKET_STROKE = STROKE_WIDTH + 1;
|
|
|
47
47
|
|
|
48
48
|
// ─── Colour helper ────────────────────────────────────────────────────────────
|
|
49
49
|
/**
|
|
50
|
-
* ● White – no face
|
|
51
|
-
* ● Yellow –
|
|
52
|
-
* ● Green –
|
|
50
|
+
* ● White – no face yet (arc at 0)
|
|
51
|
+
* ● Yellow – challenge in progress (arc filling)
|
|
52
|
+
* ● Green – all challenges passed (confirmed / countdown / capture)
|
|
53
53
|
* ● Red – error
|
|
54
54
|
*/
|
|
55
55
|
function getCircleColor(state: LivenessState, score: number): string {
|
|
@@ -62,7 +62,7 @@ function getCircleColor(state: LivenessState, score: number): string {
|
|
|
62
62
|
case 'done':
|
|
63
63
|
return '#4CAF50';
|
|
64
64
|
default:
|
|
65
|
-
return score
|
|
65
|
+
return score > 0.02 ? '#FFD60A' : '#FFFFFF';
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -103,13 +103,11 @@ function CircleOverlay({
|
|
|
103
103
|
height,
|
|
104
104
|
state,
|
|
105
105
|
score,
|
|
106
|
-
livenessThreshold,
|
|
107
106
|
}: {
|
|
108
107
|
width: number;
|
|
109
108
|
height: number;
|
|
110
109
|
state: LivenessState;
|
|
111
110
|
score: number;
|
|
112
|
-
livenessThreshold: number;
|
|
113
111
|
}) {
|
|
114
112
|
// ── Shared values — all hooks before any early return ─────────────────────
|
|
115
113
|
// scanProgress 0=top 1=bottom, ping-pongs continuously
|
|
@@ -147,13 +145,12 @@ function CircleOverlay({
|
|
|
147
145
|
[cx, cy]
|
|
148
146
|
);
|
|
149
147
|
|
|
148
|
+
// score is already 0–1 challenge progress — arc fills directly
|
|
150
149
|
const progressAnimProps = useAnimatedProps(
|
|
151
150
|
() => ({
|
|
152
|
-
strokeDashoffset:
|
|
153
|
-
circumference -
|
|
154
|
-
(livenessProgress.value / livenessThreshold) * circumference,
|
|
151
|
+
strokeDashoffset: circumference * (1 - livenessProgress.value),
|
|
155
152
|
}),
|
|
156
|
-
[circumference
|
|
153
|
+
[circumference]
|
|
157
154
|
);
|
|
158
155
|
|
|
159
156
|
// ── Start scan line + bracket rotation on mount ───────────────────────────
|
|
@@ -186,12 +183,10 @@ function CircleOverlay({
|
|
|
186
183
|
}
|
|
187
184
|
}, [state]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
188
185
|
|
|
189
|
-
// ── Drive progress arc from
|
|
186
|
+
// ── Drive progress arc from challenge progress (0–1) ─────────────────────
|
|
190
187
|
useEffect(() => {
|
|
191
|
-
livenessProgress.value = withTiming(
|
|
192
|
-
|
|
193
|
-
});
|
|
194
|
-
}, [score, livenessThreshold]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
188
|
+
livenessProgress.value = withTiming(score, { duration: 220 });
|
|
189
|
+
}, [score]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
195
190
|
|
|
196
191
|
// ── Guard — render nothing until dimensions are known ─────────────────────
|
|
197
192
|
if (width === 0 || height === 0) return null;
|
|
@@ -334,8 +329,6 @@ export function LivenessCamera({
|
|
|
334
329
|
onLivenessConfirmed,
|
|
335
330
|
onError,
|
|
336
331
|
countdownFrom = 3,
|
|
337
|
-
livenessThreshold = 0.65,
|
|
338
|
-
confirmFrames = 7,
|
|
339
332
|
soundEnabled = true,
|
|
340
333
|
fontFamily = DEFAULT_FONT,
|
|
341
334
|
style,
|
|
@@ -349,8 +342,6 @@ export function LivenessCamera({
|
|
|
349
342
|
|
|
350
343
|
const { frameProcessor, livenessState, livenessScore, countdown, feedback } =
|
|
351
344
|
useLivenessCamera({
|
|
352
|
-
livenessThreshold,
|
|
353
|
-
confirmFrames,
|
|
354
345
|
countdownFrom,
|
|
355
346
|
soundEnabled,
|
|
356
347
|
cameraRef,
|
|
@@ -413,7 +404,6 @@ export function LivenessCamera({
|
|
|
413
404
|
height={containerSize.height}
|
|
414
405
|
state={livenessState}
|
|
415
406
|
score={livenessScore}
|
|
416
|
-
livenessThreshold={livenessThreshold}
|
|
417
407
|
/>
|
|
418
408
|
{livenessState !== 'done' && (
|
|
419
409
|
<View style={styles.feedbackContainer}>
|
|
@@ -49,8 +49,6 @@ export function LivenessCameraModal({
|
|
|
49
49
|
onLivenessConfirmed,
|
|
50
50
|
onError,
|
|
51
51
|
countdownFrom,
|
|
52
|
-
livenessThreshold,
|
|
53
|
-
confirmFrames,
|
|
54
52
|
soundEnabled,
|
|
55
53
|
fontFamily,
|
|
56
54
|
}: LivenessCameraModalProps) {
|
|
@@ -73,8 +71,6 @@ export function LivenessCameraModal({
|
|
|
73
71
|
onLivenessConfirmed={onLivenessConfirmed}
|
|
74
72
|
onError={onError}
|
|
75
73
|
countdownFrom={countdownFrom}
|
|
76
|
-
livenessThreshold={livenessThreshold}
|
|
77
|
-
confirmFrames={confirmFrames}
|
|
78
74
|
soundEnabled={soundEnabled}
|
|
79
75
|
fontFamily={fontFamily}
|
|
80
76
|
/>
|