react-native-biometric-verifier 0.0.55 → 0.0.57
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/package.json +4 -16
- package/src/components/CaptureImageWithoutEdit.js +3 -4
- package/src/components/Loader.js +84 -96
- package/src/components/Notification.js +38 -14
- package/src/hooks/useFaceDetectionFrameProcessor.js +26 -26
- package/src/hooks/useGeolocation.js +46 -224
- package/src/index.js +430 -560
- package/src/utils/Global.js +0 -1
- package/src/hooks/useBluetoothService.js +0 -195
- package/src/hooks/useWifiService.js +0 -175
package/package.json
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-biometric-verifier",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.57",
|
|
4
4
|
"description": "A React Native module for biometric verification with face recognition and QR code scanning",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
-
"private": false,
|
|
7
|
-
"license": "JESCON TECHNOLOGIES PVT LMT ,THRISSUR,KERALA",
|
|
8
|
-
"author": "PRAFUL DAS M M",
|
|
9
6
|
"scripts": {
|
|
10
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
8
|
},
|
|
@@ -16,24 +13,15 @@
|
|
|
16
13
|
"face-recognition",
|
|
17
14
|
"qr-code"
|
|
18
15
|
],
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"url": "https://github.com/jescon-technologies/react-native-biometric-verifier"
|
|
22
|
-
},
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/jescon-technologies/react-native-biometric-verifier/issues"
|
|
25
|
-
},
|
|
26
|
-
"homepage": "https://github.com/jescon-technologies/react-native-biometric-verifier#readme",
|
|
27
|
-
"files": [
|
|
28
|
-
"src",
|
|
29
|
-
"README.md"
|
|
30
|
-
],
|
|
16
|
+
"author": "PRAFULDAS M M",
|
|
17
|
+
"license": "JESCON TECHNOLOGIES PVT LTD",
|
|
31
18
|
"peerDependencies": {
|
|
32
19
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
33
20
|
"react-native": ">=0.60.0",
|
|
34
21
|
"react-native-vector-icons": "^9.0.0",
|
|
35
22
|
"react-native-geolocation-service": "^5.0.0",
|
|
36
23
|
"react-native-image-resizer": "^1.0.0",
|
|
24
|
+
"@react-navigation/native": "^6.0.0",
|
|
37
25
|
"prop-types": "^15.8.0",
|
|
38
26
|
"react-native-fs": "^2.20.0"
|
|
39
27
|
},
|
|
@@ -24,7 +24,7 @@ const CaptureImageWithoutEdit = React.memo(
|
|
|
24
24
|
showCodeScanner = false,
|
|
25
25
|
isLoading = false,
|
|
26
26
|
frameProcessorFps = 1,
|
|
27
|
-
livenessLevel = 0,
|
|
27
|
+
livenessLevel = 0,
|
|
28
28
|
antispooflevel,
|
|
29
29
|
}) => {
|
|
30
30
|
const cameraRef = useRef(null);
|
|
@@ -119,7 +119,6 @@ const CaptureImageWithoutEdit = React.memo(
|
|
|
119
119
|
);
|
|
120
120
|
|
|
121
121
|
const onFacesUpdate = useCallback((payload) => {
|
|
122
|
-
console.log('qqqqqq',JSON.stringify(payload))
|
|
123
122
|
if (!isMounted.current) return;
|
|
124
123
|
try {
|
|
125
124
|
const { count, progress, antiSpoofState } = payload;
|
|
@@ -204,7 +203,7 @@ const CaptureImageWithoutEdit = React.memo(
|
|
|
204
203
|
isLoading,
|
|
205
204
|
isActive: showCamera && cameraInitialized,
|
|
206
205
|
livenessLevel: livenessLevel,
|
|
207
|
-
antispooflevel
|
|
206
|
+
antispooflevel,
|
|
208
207
|
});
|
|
209
208
|
|
|
210
209
|
useEffect(() => {
|
|
@@ -436,7 +435,7 @@ const CaptureImageWithoutEdit = React.memo(
|
|
|
436
435
|
isActive={showCamera && !isLoading}
|
|
437
436
|
photo={true}
|
|
438
437
|
format={format}
|
|
439
|
-
codeScanner={showCodeScanner ? codeScanner : undefined}
|
|
438
|
+
codeScanner={showCodeScanner && cameraInitialized ? codeScanner : undefined}
|
|
440
439
|
enableZoomGesture={false}
|
|
441
440
|
lowLightBoost={cameraDevice.supportsLowLightBoost}
|
|
442
441
|
frameProcessor={
|
package/src/components/Loader.js
CHANGED
|
@@ -5,30 +5,23 @@ import {
|
|
|
5
5
|
Modal,
|
|
6
6
|
Animated,
|
|
7
7
|
Easing,
|
|
8
|
-
Text
|
|
9
|
-
Image,
|
|
10
|
-
Dimensions,
|
|
8
|
+
Text
|
|
11
9
|
} from "react-native";
|
|
10
|
+
import FastImage from 'react-native-fast-image';
|
|
11
|
+
import { normalize } from "react-native-elements";
|
|
12
12
|
import { getLoaderGif } from "../utils/getLoaderGif";
|
|
13
13
|
|
|
14
|
-
const { width, height } = Dimensions.get("window");
|
|
15
|
-
|
|
16
|
-
// Helper: convert percentage of screen width to px
|
|
17
|
-
const wp = (percent) => (width * percent) / 100;
|
|
18
|
-
|
|
19
14
|
export default function Loader({
|
|
20
15
|
state,
|
|
21
|
-
overlayColor =
|
|
22
|
-
loaderColor =
|
|
23
|
-
size =
|
|
24
|
-
gifSource = {
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
message = "",
|
|
16
|
+
overlayColor = 'rgba(0,0,0,0.4)',
|
|
17
|
+
loaderColor = 'lightblue',
|
|
18
|
+
size = 50,
|
|
19
|
+
gifSource = { uri: "http://emr.amalaims.org:9393/file/getCommonFile/image/Face.gif" },
|
|
20
|
+
message = '',
|
|
28
21
|
messageStyle = {},
|
|
29
|
-
animationType =
|
|
22
|
+
animationType = 'fade',
|
|
30
23
|
hasBackground = true,
|
|
31
|
-
borderRadius =
|
|
24
|
+
borderRadius = 20,
|
|
32
25
|
shadow = true,
|
|
33
26
|
imageurl,
|
|
34
27
|
}) {
|
|
@@ -37,29 +30,30 @@ export default function Loader({
|
|
|
37
30
|
const [fade] = useState(new Animated.Value(0));
|
|
38
31
|
const [imageSource, setImageSource] = useState(gifSource);
|
|
39
32
|
|
|
40
|
-
const error = getLoaderGif(
|
|
41
|
-
state.animationState,
|
|
42
|
-
state.currentStep,
|
|
43
|
-
"http://emr.amalaims.org:9393/",
|
|
44
|
-
imageurl
|
|
45
|
-
);
|
|
33
|
+
const error = getLoaderGif(state.animationState, state.currentStep, "http://emr.amalaims.org:9393/", imageurl);
|
|
46
34
|
|
|
35
|
+
// Reset imageSource whenever gifSource prop changes
|
|
47
36
|
useEffect(() => {
|
|
48
37
|
setImageSource(gifSource);
|
|
49
38
|
}, [gifSource]);
|
|
50
39
|
|
|
51
40
|
const handleImageError = () => {
|
|
52
|
-
|
|
41
|
+
try {
|
|
42
|
+
setImageSource(error);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error("Loader image error:", err);
|
|
45
|
+
}
|
|
53
46
|
};
|
|
54
47
|
|
|
48
|
+
// Rotation, pulse, and fade-in animations
|
|
55
49
|
useEffect(() => {
|
|
56
|
-
if (!gifSource) {
|
|
50
|
+
if (!gifSource) { // Only animate if not using a GIF
|
|
57
51
|
Animated.loop(
|
|
58
52
|
Animated.timing(rotation, {
|
|
59
53
|
toValue: 1,
|
|
60
54
|
duration: 1500,
|
|
61
55
|
easing: Easing.linear,
|
|
62
|
-
useNativeDriver: true
|
|
56
|
+
useNativeDriver: true
|
|
63
57
|
})
|
|
64
58
|
).start();
|
|
65
59
|
}
|
|
@@ -69,90 +63,80 @@ export default function Loader({
|
|
|
69
63
|
Animated.timing(pulse, {
|
|
70
64
|
toValue: 1.1,
|
|
71
65
|
duration: 800,
|
|
72
|
-
useNativeDriver: true
|
|
66
|
+
useNativeDriver: true
|
|
73
67
|
}),
|
|
74
68
|
Animated.timing(pulse, {
|
|
75
69
|
toValue: 1,
|
|
76
70
|
duration: 800,
|
|
77
|
-
useNativeDriver: true
|
|
78
|
-
})
|
|
71
|
+
useNativeDriver: true
|
|
72
|
+
})
|
|
79
73
|
])
|
|
80
74
|
).start();
|
|
81
75
|
|
|
82
76
|
Animated.timing(fade, {
|
|
83
77
|
toValue: 1,
|
|
84
78
|
duration: 300,
|
|
85
|
-
useNativeDriver: true
|
|
79
|
+
useNativeDriver: true
|
|
86
80
|
}).start();
|
|
87
81
|
}, []);
|
|
88
82
|
|
|
89
83
|
const spin = rotation.interpolate({
|
|
90
84
|
inputRange: [0, 1],
|
|
91
|
-
outputRange: [
|
|
85
|
+
outputRange: ['0deg', '360deg']
|
|
92
86
|
});
|
|
93
87
|
|
|
94
|
-
const loaderSize = wp(size);
|
|
95
|
-
const borderSize = loaderSize * 0.12;
|
|
96
|
-
|
|
97
88
|
const loaderContent = gifSource ? (
|
|
98
|
-
<
|
|
99
|
-
style={[
|
|
100
|
-
styles.icon,
|
|
101
|
-
{ width: loaderSize, height: loaderSize },
|
|
102
|
-
]}
|
|
89
|
+
<FastImage
|
|
90
|
+
style={[styles.icon_style, { width: normalize(size), height: normalize(size) }]}
|
|
103
91
|
source={imageSource}
|
|
92
|
+
resizeMode={FastImage.resizeMode.contain}
|
|
104
93
|
onError={handleImageError}
|
|
105
94
|
/>
|
|
106
95
|
) : (
|
|
107
|
-
<Animated.View
|
|
108
|
-
|
|
109
|
-
|
|
96
|
+
<Animated.View style={[
|
|
97
|
+
styles.defaultLoader,
|
|
98
|
+
{
|
|
99
|
+
borderColor: loaderColor,
|
|
100
|
+
transform: [{ rotate: spin }, { scale: pulse }],
|
|
101
|
+
width: normalize(size),
|
|
102
|
+
height: normalize(size),
|
|
103
|
+
borderWidth: normalize(size / 10)
|
|
104
|
+
}
|
|
105
|
+
]}>
|
|
106
|
+
<View style={[
|
|
107
|
+
styles.innerCircle,
|
|
110
108
|
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
},
|
|
117
|
-
]}
|
|
118
|
-
>
|
|
119
|
-
<View
|
|
120
|
-
style={[
|
|
121
|
-
styles.innerCircle,
|
|
122
|
-
{
|
|
123
|
-
width: loaderSize / 2,
|
|
124
|
-
height: loaderSize / 2,
|
|
125
|
-
backgroundColor: loaderColor,
|
|
126
|
-
},
|
|
127
|
-
]}
|
|
128
|
-
/>
|
|
109
|
+
backgroundColor: loaderColor,
|
|
110
|
+
width: normalize(size / 2),
|
|
111
|
+
height: normalize(size / 2)
|
|
112
|
+
}
|
|
113
|
+
]} />
|
|
129
114
|
</Animated.View>
|
|
130
115
|
);
|
|
131
116
|
|
|
132
117
|
return (
|
|
133
118
|
<Modal
|
|
134
119
|
animationType={animationType}
|
|
135
|
-
transparent
|
|
120
|
+
transparent={true}
|
|
136
121
|
visible={state.isLoading}
|
|
137
|
-
onRequestClose={() => {}}
|
|
122
|
+
onRequestClose={() => { }}
|
|
138
123
|
>
|
|
139
|
-
<Animated.View
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
>
|
|
124
|
+
<Animated.View style={[
|
|
125
|
+
styles.modalContainer,
|
|
126
|
+
{
|
|
127
|
+
backgroundColor: overlayColor,
|
|
128
|
+
opacity: fade
|
|
129
|
+
}
|
|
130
|
+
]}>
|
|
131
|
+
<Animated.View style={[
|
|
132
|
+
styles.loaderContainer,
|
|
133
|
+
{
|
|
134
|
+
backgroundColor: hasBackground ? 'white' : 'transparent',
|
|
135
|
+
borderRadius: normalize(borderRadius),
|
|
136
|
+
transform: [{ scale: pulse }],
|
|
137
|
+
...(shadow && styles.shadowStyle)
|
|
138
|
+
}
|
|
139
|
+
]}>
|
|
156
140
|
{loaderContent}
|
|
157
141
|
{message ? (
|
|
158
142
|
<Text style={[styles.messageText, messageStyle]}>
|
|
@@ -168,36 +152,40 @@ export default function Loader({
|
|
|
168
152
|
const styles = StyleSheet.create({
|
|
169
153
|
modalContainer: {
|
|
170
154
|
flex: 1,
|
|
171
|
-
justifyContent:
|
|
172
|
-
alignItems:
|
|
155
|
+
justifyContent: 'center',
|
|
156
|
+
alignItems: 'center',
|
|
173
157
|
},
|
|
174
158
|
loaderContainer: {
|
|
175
|
-
padding:
|
|
176
|
-
justifyContent:
|
|
177
|
-
alignItems:
|
|
159
|
+
padding: normalize(20),
|
|
160
|
+
justifyContent: 'center',
|
|
161
|
+
alignItems: 'center',
|
|
178
162
|
},
|
|
179
|
-
|
|
180
|
-
|
|
163
|
+
icon_style: {
|
|
164
|
+
justifyContent: "center",
|
|
165
|
+
alignItems: "center"
|
|
181
166
|
},
|
|
182
167
|
defaultLoader: {
|
|
183
|
-
borderRadius:
|
|
184
|
-
justifyContent:
|
|
185
|
-
alignItems:
|
|
168
|
+
borderRadius: normalize(100),
|
|
169
|
+
justifyContent: 'center',
|
|
170
|
+
alignItems: 'center',
|
|
186
171
|
},
|
|
187
172
|
innerCircle: {
|
|
188
|
-
borderRadius:
|
|
173
|
+
borderRadius: normalize(100),
|
|
189
174
|
},
|
|
190
175
|
messageText: {
|
|
191
|
-
marginTop:
|
|
192
|
-
fontSize:
|
|
193
|
-
color:
|
|
194
|
-
textAlign:
|
|
176
|
+
marginTop: normalize(15),
|
|
177
|
+
fontSize: normalize(14),
|
|
178
|
+
color: '#555',
|
|
179
|
+
textAlign: 'center'
|
|
195
180
|
},
|
|
196
181
|
shadowStyle: {
|
|
197
182
|
shadowColor: "#000",
|
|
198
|
-
shadowOffset: {
|
|
183
|
+
shadowOffset: {
|
|
184
|
+
width: 0,
|
|
185
|
+
height: 2,
|
|
186
|
+
},
|
|
199
187
|
shadowOpacity: 0.25,
|
|
200
188
|
shadowRadius: 3.84,
|
|
201
189
|
elevation: 5,
|
|
202
|
-
}
|
|
190
|
+
}
|
|
203
191
|
});
|
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { Animated, Text, Platform, StyleSheet } from 'react-native';
|
|
3
3
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
4
5
|
import { Global } from '../utils/Global';
|
|
5
6
|
|
|
6
|
-
export const Notification = ({ notification
|
|
7
|
-
|
|
7
|
+
export const Notification = ({ notification, fadeAnim, slideAnim }) => {
|
|
8
|
+
if (!notification || typeof notification !== 'object') {
|
|
9
|
+
console.warn('Notification: Invalid or missing notification object');
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const shakeAnim = useRef(new Animated.Value(0)).current;
|
|
13
|
+
const { visible, type = 'info', message = '' } = notification;
|
|
14
|
+
if (!visible) return null;
|
|
12
15
|
|
|
16
|
+
// Icon and color mapping
|
|
13
17
|
const iconMap = {
|
|
14
18
|
success: { name: 'check-circle', color: Global.AppTheme.success },
|
|
15
19
|
error: { name: 'error', color: Global.AppTheme.error },
|
|
16
20
|
info: { name: 'info', color: Global.AppTheme.info },
|
|
17
21
|
};
|
|
18
22
|
|
|
19
|
-
const { name: iconName, color: iconColor } =
|
|
20
|
-
|
|
23
|
+
const { name: iconName, color: iconColor } = iconMap[type] || iconMap.info;
|
|
24
|
+
|
|
25
|
+
// Heartbeat animation (scale in/out)
|
|
26
|
+
const scaleAnim = useRef(new Animated.Value(1)).current;
|
|
27
|
+
|
|
28
|
+
// Shake animation (rotation wiggle)
|
|
29
|
+
const shakeAnim = useRef(new Animated.Value(0)).current;
|
|
21
30
|
|
|
22
31
|
useEffect(() => {
|
|
23
32
|
const heartbeat = Animated.loop(
|
|
@@ -69,20 +78,20 @@ export const Notification = ({ notification = {}, fadeAnim, slideAnim }) => {
|
|
|
69
78
|
heartbeat.stop();
|
|
70
79
|
shake.stop();
|
|
71
80
|
};
|
|
72
|
-
}, [visible, type]);
|
|
81
|
+
}, [visible, type, scaleAnim, shakeAnim]);
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
// Shake rotation mapping (small wiggle)
|
|
84
|
+
const shakeInterpolate = shakeAnim.interpolate({
|
|
75
85
|
inputRange: [-1, 1],
|
|
76
86
|
outputRange: ['-10deg', '10deg'],
|
|
77
87
|
});
|
|
78
88
|
|
|
79
89
|
return (
|
|
80
90
|
<Animated.View
|
|
81
|
-
pointerEvents={visible ? 'auto' : 'none'}
|
|
82
91
|
style={[
|
|
83
92
|
styles.container,
|
|
84
93
|
{
|
|
85
|
-
opacity:
|
|
94
|
+
opacity: fadeAnim instanceof Animated.Value ? fadeAnim : 1,
|
|
86
95
|
transform: [
|
|
87
96
|
{ translateY: slideAnim instanceof Animated.Value ? slideAnim : 0 },
|
|
88
97
|
],
|
|
@@ -93,13 +102,12 @@ export const Notification = ({ notification = {}, fadeAnim, slideAnim }) => {
|
|
|
93
102
|
style={{
|
|
94
103
|
transform: [
|
|
95
104
|
{ scale: scaleAnim },
|
|
96
|
-
...(type === 'error' ? [{ rotate:
|
|
105
|
+
...(type === 'error' ? [{ rotate: shakeInterpolate }] : []),
|
|
97
106
|
],
|
|
98
107
|
}}
|
|
99
108
|
>
|
|
100
|
-
<Icon name={iconName} size={50} color={iconColor} />
|
|
109
|
+
<Icon name={iconName} size={50} color={iconColor} style={styles.icon} />
|
|
101
110
|
</Animated.View>
|
|
102
|
-
|
|
103
111
|
<Text style={styles.message}>
|
|
104
112
|
{message || 'No message provided'}
|
|
105
113
|
</Text>
|
|
@@ -134,4 +142,20 @@ const styles = StyleSheet.create({
|
|
|
134
142
|
},
|
|
135
143
|
});
|
|
136
144
|
|
|
145
|
+
Notification.propTypes = {
|
|
146
|
+
notification: PropTypes.shape({
|
|
147
|
+
visible: PropTypes.bool.isRequired,
|
|
148
|
+
type: PropTypes.oneOf(['success', 'error', 'info']),
|
|
149
|
+
message: PropTypes.string,
|
|
150
|
+
}),
|
|
151
|
+
fadeAnim: PropTypes.instanceOf(Animated.Value),
|
|
152
|
+
slideAnim: PropTypes.instanceOf(Animated.Value),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
Notification.defaultProps = {
|
|
156
|
+
notification: { visible: false, type: 'info', message: '' },
|
|
157
|
+
fadeAnim: new Animated.Value(1),
|
|
158
|
+
slideAnim: new Animated.Value(0),
|
|
159
|
+
};
|
|
160
|
+
|
|
137
161
|
export default Notification;
|
|
@@ -38,8 +38,8 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
38
38
|
showCodeScanner = false,
|
|
39
39
|
isLoading = false,
|
|
40
40
|
isActive = true,
|
|
41
|
-
livenessLevel
|
|
42
|
-
antispooflevel = 7,
|
|
41
|
+
livenessLevel,
|
|
42
|
+
antispooflevel = 0.7,
|
|
43
43
|
}) => {
|
|
44
44
|
const { detectFaces } = useFaceDetector({
|
|
45
45
|
performanceMode: 'fast',
|
|
@@ -56,7 +56,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
56
56
|
// Initialize anti-spoofing with memoization
|
|
57
57
|
const initializeAntiSpoof = useCallback(async () => {
|
|
58
58
|
if (antiSpoofInitialized.current) return true;
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
try {
|
|
61
61
|
const available = isFaceAntiSpoofAvailable?.();
|
|
62
62
|
if (!available) return false;
|
|
@@ -82,10 +82,10 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
82
82
|
Worklets.createSharedValue({
|
|
83
83
|
// Core timing
|
|
84
84
|
lastProcessedTime: 0,
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
// Face tracking - packed for memory efficiency
|
|
87
87
|
faceTracking: { lastX: 0, lastY: 0, lastW: 0, lastH: 0, stableCount: 0 },
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
// State flags - packed together
|
|
90
90
|
flags: {
|
|
91
91
|
captured: false,
|
|
@@ -95,14 +95,14 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
95
95
|
isFaceCentered: false,
|
|
96
96
|
eyeClosed: false,
|
|
97
97
|
},
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
// Liveness state
|
|
100
100
|
liveness: {
|
|
101
101
|
level: livenessLevel,
|
|
102
102
|
step: 0,
|
|
103
103
|
blinkCount: 0,
|
|
104
104
|
},
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
// Anti-spoof state
|
|
107
107
|
antiSpoof: {
|
|
108
108
|
consecutiveLiveFrames: 0,
|
|
@@ -110,14 +110,14 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
110
110
|
isLive: false,
|
|
111
111
|
confidence: 0,
|
|
112
112
|
},
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
// Face centering
|
|
115
115
|
centering: {
|
|
116
116
|
centeredFrames: 0,
|
|
117
117
|
frameWidth: 0,
|
|
118
118
|
frameHeight: 0,
|
|
119
119
|
},
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
// Performance tracking
|
|
122
122
|
performance: {
|
|
123
123
|
batchCounter: 0,
|
|
@@ -181,7 +181,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
181
181
|
Worklets.createRunOnJS((count, progress, step, isCentered, antiSpoofState) => {
|
|
182
182
|
const now = Date.now();
|
|
183
183
|
const callbacks = callbacksRef.current;
|
|
184
|
-
|
|
184
|
+
|
|
185
185
|
if (now - callbacks.lastFacesEventTime > FACES_EVENT_INTERVAL_MS) {
|
|
186
186
|
callbacks.lastFacesEventTime = now;
|
|
187
187
|
onFacesUpdate?.({ count, progress, step, isCentered, antiSpoofState });
|
|
@@ -195,7 +195,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
195
195
|
Worklets.createRunOnJS((step, extra) => {
|
|
196
196
|
const now = Date.now();
|
|
197
197
|
const callbacks = callbacksRef.current;
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
if (now - callbacks.lastLivenessEventTime > LIVENESS_EVENT_INTERVAL_MS) {
|
|
200
200
|
callbacks.lastLivenessEventTime = now;
|
|
201
201
|
onLivenessUpdate?.(step, extra);
|
|
@@ -209,7 +209,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
209
209
|
Worklets.createRunOnJS((result) => {
|
|
210
210
|
const now = Date.now();
|
|
211
211
|
const callbacks = callbacksRef.current;
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
if (now - callbacks.lastAntiSpoofEventTime > ANTI_SPOOF_EVENT_INTERVAL_MS) {
|
|
214
214
|
callbacks.lastAntiSpoofEventTime = now;
|
|
215
215
|
onAntiSpoofUpdate?.(result);
|
|
@@ -221,7 +221,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
221
221
|
// Optimized face centering check - inlined for performance
|
|
222
222
|
const isFaceCenteredInFrame = Worklets.createRunOnJS((faceBounds, frameWidth, frameHeight) => {
|
|
223
223
|
'worklet';
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
if (!faceBounds || frameWidth === 0 || frameHeight === 0) return false;
|
|
226
226
|
|
|
227
227
|
const faceCenterX = faceBounds.x + faceBounds.width / 2;
|
|
@@ -251,10 +251,10 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
251
251
|
const frameProcessor = useFrameProcessor(
|
|
252
252
|
(frame) => {
|
|
253
253
|
'worklet';
|
|
254
|
-
|
|
254
|
+
|
|
255
255
|
// Performance monitoring
|
|
256
256
|
const processingStart = Date.now();
|
|
257
|
-
|
|
257
|
+
|
|
258
258
|
const state = sharedState.value;
|
|
259
259
|
const now = frame?.timestamp ? frame.timestamp / 1e6 : Date.now();
|
|
260
260
|
|
|
@@ -269,7 +269,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
269
269
|
frame.release?.();
|
|
270
270
|
return;
|
|
271
271
|
}
|
|
272
|
-
|
|
272
|
+
|
|
273
273
|
frameProcessingStartTime.current = processingStart;
|
|
274
274
|
|
|
275
275
|
let detected = null;
|
|
@@ -293,7 +293,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
293
293
|
state.centering.centeredFrames = 0;
|
|
294
294
|
state.flags.isFaceCentered = false;
|
|
295
295
|
state.lastProcessedTime = now;
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
runOnFaces(0, 0, state.liveness.step, false, {
|
|
298
298
|
isLive: false,
|
|
299
299
|
confidence: 0,
|
|
@@ -341,8 +341,8 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
341
341
|
|
|
342
342
|
// Face centering check
|
|
343
343
|
const centered = isFaceCenteredInFrame(
|
|
344
|
-
bounds,
|
|
345
|
-
state.centering.frameWidth,
|
|
344
|
+
bounds,
|
|
345
|
+
state.centering.frameWidth,
|
|
346
346
|
state.centering.frameHeight
|
|
347
347
|
);
|
|
348
348
|
|
|
@@ -434,8 +434,8 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
434
434
|
} else {
|
|
435
435
|
const dx = Math.abs(x - state.faceTracking.lastX);
|
|
436
436
|
const dy = Math.abs(y - state.faceTracking.lastY);
|
|
437
|
-
newStableCount = (dx < FACE_MOVEMENT_THRESHOLD && dy < FACE_MOVEMENT_THRESHOLD)
|
|
438
|
-
? state.faceTracking.stableCount + 1
|
|
437
|
+
newStableCount = (dx < FACE_MOVEMENT_THRESHOLD && dy < FACE_MOVEMENT_THRESHOLD)
|
|
438
|
+
? state.faceTracking.stableCount + 1
|
|
439
439
|
: 1;
|
|
440
440
|
}
|
|
441
441
|
|
|
@@ -452,7 +452,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
452
452
|
state.performance.batchCounter++;
|
|
453
453
|
|
|
454
454
|
const progress = Math.min(100, (newStableCount / FACE_STABILITY_THRESHOLD) * 100);
|
|
455
|
-
|
|
455
|
+
|
|
456
456
|
// Batch face updates
|
|
457
457
|
if (state.performance.batchCounter % BATCH_UPDATE_THRESHOLD === 0) {
|
|
458
458
|
runOnFaces(1, progress, newLivenessStep, state.flags.isFaceCentered, {
|
|
@@ -471,8 +471,8 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
471
471
|
state.antiSpoof.consecutiveLiveFrames >= REQUIRED_CONSECUTIVE_LIVE_FRAMES &&
|
|
472
472
|
state.flags.isFaceCentered &&
|
|
473
473
|
(localState.livenessLevel === 0 || (
|
|
474
|
-
localState.livenessLevel === 1 &&
|
|
475
|
-
newLivenessStep === 2 &&
|
|
474
|
+
localState.livenessLevel === 1 &&
|
|
475
|
+
newLivenessStep === 2 &&
|
|
476
476
|
newBlinkCount >= REQUIRED_BLINKS
|
|
477
477
|
))
|
|
478
478
|
);
|
|
@@ -492,7 +492,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
492
492
|
state.flags.hasSingleFace = false;
|
|
493
493
|
state.centering.centeredFrames = 0;
|
|
494
494
|
state.flags.isFaceCentered = false;
|
|
495
|
-
|
|
495
|
+
|
|
496
496
|
runOnFaces(detected.length, 0, state.liveness.step, false, {
|
|
497
497
|
isLive: false,
|
|
498
498
|
confidence: 0,
|
|
@@ -537,7 +537,7 @@ export const useFaceDetectionFrameProcessor = ({
|
|
|
537
537
|
|
|
538
538
|
const forceResetCaptureState = useCallback(() => {
|
|
539
539
|
const current = sharedState.value;
|
|
540
|
-
|
|
540
|
+
|
|
541
541
|
sharedState.value = {
|
|
542
542
|
lastProcessedTime: 0,
|
|
543
543
|
faceTracking: {
|