omnipay-reactnative-sdk 1.2.3-beta.9 → 1.2.4
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 +14 -64
- package/android/build.gradle +4 -15
- package/android/src/main/java/com/omniretail/omnipay/OmnipayActivityPackage.java +0 -5
- package/lib/commonjs/components/OmnipayProvider.js +98 -20
- package/lib/commonjs/components/OmnipayProvider.js.map +1 -1
- package/lib/commonjs/utils/buildUrlWithMetadata.js +14 -0
- package/lib/commonjs/utils/buildUrlWithMetadata.js.map +1 -0
- package/lib/module/components/OmnipayProvider.js +99 -21
- package/lib/module/components/OmnipayProvider.js.map +1 -1
- package/lib/module/components/OmnipayView.js.map +1 -1
- package/lib/module/components/views/BvnVerification.js.map +1 -1
- package/lib/module/components/views/PaylaterAgreement.js.map +1 -1
- package/lib/module/components/views/Registration.js.map +1 -1
- package/lib/module/utils/buildUrlWithMetadata.js +8 -0
- package/lib/module/utils/buildUrlWithMetadata.js.map +1 -0
- package/lib/typescript/components/OmnipayProvider.d.ts +5 -2
- package/lib/typescript/components/OmnipayProvider.d.ts.map +1 -1
- package/lib/typescript/hooks/useOmnipay.d.ts +4 -1
- package/lib/typescript/hooks/useOmnipay.d.ts.map +1 -1
- package/lib/typescript/utils/buildUrlWithMetadata.d.ts +2 -0
- package/lib/typescript/utils/buildUrlWithMetadata.d.ts.map +1 -0
- package/package.json +12 -29
- package/src/components/OmnipayProvider.tsx +154 -31
- package/src/components/OmnipayView.tsx +1 -1
- package/src/components/views/BvnVerification.tsx +1 -1
- package/src/components/views/PaylaterAgreement.tsx +1 -1
- package/src/components/views/Registration.tsx +1 -1
- package/src/hooks/useOmnipay.tsx +1 -1
- package/src/utils/buildUrlWithMetadata.ts +21 -0
- package/android/src/main/java/com/omniretail/omnipay/FaceVerificationFrameProcessor.kt +0 -111
- package/ios/FaceVerificationFrameProcessor.swift +0 -138
- package/ios/FaceVerificationFrameProcessorPlugin.m +0 -4
- package/ios/OmnipayReactnativeSdk.m +0 -5
- package/ios/OmnipayReactnativeSdk.swift +0 -10
- package/ios/omnipay-reactnative-sdk-Bridging-Header.h +0 -8
- package/ios/omnipay_reactnative_sdk.h +0 -6
- package/lib/commonjs/components/Button.js +0 -68
- package/lib/commonjs/components/Button.js.map +0 -1
- package/lib/commonjs/components/biometrics/FaceVerification.js +0 -380
- package/lib/commonjs/components/biometrics/FaceVerification.js.map +0 -1
- package/lib/commonjs/components/biometrics/useFaceVerification.js +0 -85
- package/lib/commonjs/components/biometrics/useFaceVerification.js.map +0 -1
- package/lib/commonjs/components/biometrics/useFaceVerificationFlow.js +0 -157
- package/lib/commonjs/components/biometrics/useFaceVerificationFlow.js.map +0 -1
- package/lib/module/components/Button.js +0 -61
- package/lib/module/components/Button.js.map +0 -1
- package/lib/module/components/biometrics/FaceVerification.js +0 -372
- package/lib/module/components/biometrics/FaceVerification.js.map +0 -1
- package/lib/module/components/biometrics/useFaceVerification.js +0 -78
- package/lib/module/components/biometrics/useFaceVerification.js.map +0 -1
- package/lib/module/components/biometrics/useFaceVerificationFlow.js +0 -150
- package/lib/module/components/biometrics/useFaceVerificationFlow.js.map +0 -1
- package/lib/typescript/components/Button.d.ts +0 -17
- package/lib/typescript/components/Button.d.ts.map +0 -1
- package/lib/typescript/components/biometrics/FaceVerification.d.ts +0 -9
- package/lib/typescript/components/biometrics/FaceVerification.d.ts.map +0 -1
- package/lib/typescript/components/biometrics/useFaceVerification.d.ts +0 -38
- package/lib/typescript/components/biometrics/useFaceVerification.d.ts.map +0 -1
- package/lib/typescript/components/biometrics/useFaceVerificationFlow.d.ts +0 -29
- package/lib/typescript/components/biometrics/useFaceVerificationFlow.d.ts.map +0 -1
- package/omnipay_reactnative_sdk.podspec +0 -52
- package/src/components/Button.tsx +0 -86
- package/src/components/biometrics/FaceVerification.tsx +0 -429
- package/src/components/biometrics/useFaceVerification.ts +0 -120
- package/src/components/biometrics/useFaceVerificationFlow.ts +0 -224
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
Modal,
|
|
5
|
-
Dimensions,
|
|
6
|
-
Platform,
|
|
7
|
-
StyleSheet,
|
|
8
|
-
TouchableOpacity,
|
|
9
|
-
Image,
|
|
10
|
-
Text,
|
|
11
|
-
} from 'react-native';
|
|
12
|
-
import {
|
|
13
|
-
Camera,
|
|
14
|
-
useCameraDevice,
|
|
15
|
-
useCameraPermission,
|
|
16
|
-
} from 'react-native-vision-camera';
|
|
17
|
-
import Button from '../Button';
|
|
18
|
-
import { useFaceVerification } from './useFaceVerification';
|
|
19
|
-
import { useFaceVerificationFlow } from './useFaceVerificationFlow';
|
|
20
|
-
|
|
21
|
-
type FaceVerificationProps = {
|
|
22
|
-
onClose: () => void;
|
|
23
|
-
onSuccess: () => void;
|
|
24
|
-
primaryColor: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const FaceVerification: React.FC<FaceVerificationProps> = ({
|
|
28
|
-
onClose,
|
|
29
|
-
onSuccess,
|
|
30
|
-
primaryColor,
|
|
31
|
-
}) => {
|
|
32
|
-
const device = useCameraDevice('front');
|
|
33
|
-
const { hasPermission, requestPermission } = useCameraPermission();
|
|
34
|
-
const [isRequestingPermission, setIsRequestingPermission] = useState(false);
|
|
35
|
-
|
|
36
|
-
// Face verification flow
|
|
37
|
-
const {
|
|
38
|
-
state: flowState,
|
|
39
|
-
resetFlow,
|
|
40
|
-
isCompleted,
|
|
41
|
-
isFailed,
|
|
42
|
-
} = useFaceVerificationFlow();
|
|
43
|
-
|
|
44
|
-
// Frame processor
|
|
45
|
-
const frameProcessor = useFaceVerification();
|
|
46
|
-
|
|
47
|
-
// Handle completion
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (isCompleted) {
|
|
50
|
-
setTimeout(() => {
|
|
51
|
-
onSuccess();
|
|
52
|
-
}, 1000); // Show success message briefly
|
|
53
|
-
}
|
|
54
|
-
}, [isCompleted, onSuccess]);
|
|
55
|
-
|
|
56
|
-
const handleRequestPermission = async () => {
|
|
57
|
-
setIsRequestingPermission(true);
|
|
58
|
-
try {
|
|
59
|
-
await requestPermission();
|
|
60
|
-
} catch (error) {
|
|
61
|
-
console.error('Error requesting camera permission:', error);
|
|
62
|
-
} finally {
|
|
63
|
-
setIsRequestingPermission(false);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const renderPermissionRequest = () => (
|
|
68
|
-
<View style={styles.permissionContainer}>
|
|
69
|
-
<View style={styles.permissionIconContainer}>
|
|
70
|
-
<Text style={styles.permissionIcon}>📷</Text>
|
|
71
|
-
</View>
|
|
72
|
-
<Text style={styles.permissionTitle}>Camera Permission Required</Text>
|
|
73
|
-
<Text style={styles.permissionDescription}>
|
|
74
|
-
To verify your identity, we need access to your camera. This allows us
|
|
75
|
-
to capture your face for secure verification.
|
|
76
|
-
</Text>
|
|
77
|
-
<Button
|
|
78
|
-
title="Allow Camera Access"
|
|
79
|
-
onPress={handleRequestPermission}
|
|
80
|
-
backgroundColor={primaryColor}
|
|
81
|
-
loading={isRequestingPermission}
|
|
82
|
-
/>
|
|
83
|
-
</View>
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const renderNoDevice = () => (
|
|
87
|
-
<View style={styles.errorContainer}>
|
|
88
|
-
<Text style={styles.errorTitle}>Camera Not Available</Text>
|
|
89
|
-
<Text style={styles.errorSubtitle}>
|
|
90
|
-
No front camera detected on this device. Face verification requires a
|
|
91
|
-
front-facing camera.
|
|
92
|
-
</Text>
|
|
93
|
-
</View>
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const renderCamera = () => {
|
|
97
|
-
if (!device) return null;
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<View style={styles.cameraContainer}>
|
|
101
|
-
<Camera
|
|
102
|
-
device={device}
|
|
103
|
-
style={styles.camera}
|
|
104
|
-
isActive={true}
|
|
105
|
-
frameProcessor={frameProcessor}
|
|
106
|
-
/>
|
|
107
|
-
<View style={styles.cameraOverlay}>
|
|
108
|
-
<View style={styles.faceFrame} />
|
|
109
|
-
|
|
110
|
-
{/* Progress indicator */}
|
|
111
|
-
<View style={styles.progressContainer}>
|
|
112
|
-
<View
|
|
113
|
-
style={[
|
|
114
|
-
styles.progressBar,
|
|
115
|
-
{
|
|
116
|
-
width: `${flowState.progress}%`,
|
|
117
|
-
backgroundColor: primaryColor,
|
|
118
|
-
},
|
|
119
|
-
]}
|
|
120
|
-
/>
|
|
121
|
-
</View>
|
|
122
|
-
|
|
123
|
-
{/* Current instruction */}
|
|
124
|
-
<Text style={styles.instructionText}>{flowState.instruction}</Text>
|
|
125
|
-
|
|
126
|
-
{/* Step indicator */}
|
|
127
|
-
<Text style={styles.stepIndicator}>
|
|
128
|
-
Step {flowState.completedSteps.length + 1} of 5
|
|
129
|
-
</Text>
|
|
130
|
-
|
|
131
|
-
{/* Success/Failure messages */}
|
|
132
|
-
{isCompleted && (
|
|
133
|
-
<View
|
|
134
|
-
style={[
|
|
135
|
-
styles.statusContainer,
|
|
136
|
-
{ backgroundColor: primaryColor },
|
|
137
|
-
]}
|
|
138
|
-
>
|
|
139
|
-
<Text style={styles.statusText}>✓ Verification Complete!</Text>
|
|
140
|
-
</View>
|
|
141
|
-
)}
|
|
142
|
-
|
|
143
|
-
{isFailed && (
|
|
144
|
-
<View style={[styles.statusContainer, styles.statusContainerError]}>
|
|
145
|
-
<Text style={styles.statusText}>✗ Verification Failed</Text>
|
|
146
|
-
<Button
|
|
147
|
-
title="Try Again"
|
|
148
|
-
onPress={resetFlow}
|
|
149
|
-
backgroundColor="white"
|
|
150
|
-
textColor="#ff4444"
|
|
151
|
-
style={styles.retryButton}
|
|
152
|
-
/>
|
|
153
|
-
</View>
|
|
154
|
-
)}
|
|
155
|
-
</View>
|
|
156
|
-
</View>
|
|
157
|
-
);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
console.log(onSuccess, primaryColor);
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<Modal
|
|
164
|
-
visible={true}
|
|
165
|
-
transparent={true}
|
|
166
|
-
onRequestClose={onClose}
|
|
167
|
-
style={styles.modal}
|
|
168
|
-
>
|
|
169
|
-
<TouchableOpacity
|
|
170
|
-
style={styles.backdrop}
|
|
171
|
-
activeOpacity={1}
|
|
172
|
-
onPress={onClose}
|
|
173
|
-
>
|
|
174
|
-
<View style={[styles.container]}>
|
|
175
|
-
<TouchableOpacity style={styles.close} onPress={onClose}>
|
|
176
|
-
<Image
|
|
177
|
-
source={require('../../assets/cancel.png')}
|
|
178
|
-
style={styles.closeIcon}
|
|
179
|
-
/>
|
|
180
|
-
</TouchableOpacity>
|
|
181
|
-
<View style={styles.contentContainer}>
|
|
182
|
-
{!hasPermission && renderPermissionRequest()}
|
|
183
|
-
{hasPermission && !device && renderNoDevice()}
|
|
184
|
-
{hasPermission && device && renderCamera()}
|
|
185
|
-
</View>
|
|
186
|
-
</View>
|
|
187
|
-
</TouchableOpacity>
|
|
188
|
-
</Modal>
|
|
189
|
-
);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
export default FaceVerification;
|
|
193
|
-
|
|
194
|
-
const styles = StyleSheet.create({
|
|
195
|
-
hide: {
|
|
196
|
-
display: 'none',
|
|
197
|
-
},
|
|
198
|
-
full: {
|
|
199
|
-
flex: 1,
|
|
200
|
-
width: '100%',
|
|
201
|
-
height: '100%',
|
|
202
|
-
},
|
|
203
|
-
webview: {
|
|
204
|
-
flex: 1,
|
|
205
|
-
width: '100%',
|
|
206
|
-
height: Dimensions.get('window').height - 40,
|
|
207
|
-
backgroundColor: 'white',
|
|
208
|
-
borderTopRightRadius: 20,
|
|
209
|
-
borderTopLeftRadius: 20,
|
|
210
|
-
paddingTop: 150,
|
|
211
|
-
},
|
|
212
|
-
webviewLoader: {
|
|
213
|
-
zIndex: 3,
|
|
214
|
-
backgroundColor: 'white',
|
|
215
|
-
alignItems: 'center',
|
|
216
|
-
justifyContent: 'center',
|
|
217
|
-
flex: 1,
|
|
218
|
-
width: '100%',
|
|
219
|
-
height: '100%',
|
|
220
|
-
position: 'absolute',
|
|
221
|
-
top: 0,
|
|
222
|
-
left: 0,
|
|
223
|
-
borderTopRightRadius: 20,
|
|
224
|
-
borderTopLeftRadius: 20,
|
|
225
|
-
},
|
|
226
|
-
backdrop: {
|
|
227
|
-
backgroundColor: 'rgba(0,0,0,0.3)',
|
|
228
|
-
flex: 1,
|
|
229
|
-
justifyContent: 'flex-end',
|
|
230
|
-
position: 'relative',
|
|
231
|
-
height: '100%',
|
|
232
|
-
},
|
|
233
|
-
container: {
|
|
234
|
-
backgroundColor: 'white',
|
|
235
|
-
borderTopRightRadius: 20,
|
|
236
|
-
borderTopLeftRadius: 20,
|
|
237
|
-
maxHeight: Dimensions.get('window').height - 120,
|
|
238
|
-
flex: 1,
|
|
239
|
-
position: 'relative',
|
|
240
|
-
...(Platform.OS === 'android' && { overflow: 'hidden' }),
|
|
241
|
-
},
|
|
242
|
-
modal: {
|
|
243
|
-
flex: 1,
|
|
244
|
-
backgroundColor: 'rgba(0,0,0,0.48)',
|
|
245
|
-
height: '100%',
|
|
246
|
-
width: '100%',
|
|
247
|
-
},
|
|
248
|
-
close: {
|
|
249
|
-
position: 'absolute',
|
|
250
|
-
top: 10,
|
|
251
|
-
right: 10,
|
|
252
|
-
backgroundColor: 'white',
|
|
253
|
-
height: 24,
|
|
254
|
-
width: 24,
|
|
255
|
-
borderRadius: 1000,
|
|
256
|
-
alignItems: 'center',
|
|
257
|
-
justifyContent: 'center',
|
|
258
|
-
zIndex: 2,
|
|
259
|
-
},
|
|
260
|
-
closeIcon: {
|
|
261
|
-
height: 12,
|
|
262
|
-
width: 12,
|
|
263
|
-
},
|
|
264
|
-
contentContainer: {
|
|
265
|
-
height: '100%',
|
|
266
|
-
position: 'relative',
|
|
267
|
-
borderTopRightRadius: 20,
|
|
268
|
-
borderTopLeftRadius: 20,
|
|
269
|
-
padding: 16,
|
|
270
|
-
},
|
|
271
|
-
testContent: {
|
|
272
|
-
paddingTop: 30,
|
|
273
|
-
paddingLeft: 16,
|
|
274
|
-
},
|
|
275
|
-
testTwoContent: {
|
|
276
|
-
paddingTop: 10,
|
|
277
|
-
paddingLeft: 16,
|
|
278
|
-
},
|
|
279
|
-
errorSubtitle: {
|
|
280
|
-
textAlign: 'center',
|
|
281
|
-
fontSize: 14,
|
|
282
|
-
color: '#5e7079',
|
|
283
|
-
marginBottom: 20,
|
|
284
|
-
paddingHorizontal: 8,
|
|
285
|
-
marginTop: 14,
|
|
286
|
-
},
|
|
287
|
-
errorContainer: {
|
|
288
|
-
flex: 1,
|
|
289
|
-
justifyContent: 'center',
|
|
290
|
-
alignItems: 'center',
|
|
291
|
-
width: Dimensions.get('window').width,
|
|
292
|
-
height: Dimensions.get('window').height,
|
|
293
|
-
zIndex: 2,
|
|
294
|
-
backgroundColor: 'white',
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
camera: {
|
|
298
|
-
width: '100%',
|
|
299
|
-
height: 400,
|
|
300
|
-
},
|
|
301
|
-
// Permission request styles
|
|
302
|
-
permissionContainer: {
|
|
303
|
-
flex: 1,
|
|
304
|
-
justifyContent: 'center',
|
|
305
|
-
alignItems: 'center',
|
|
306
|
-
paddingHorizontal: 24,
|
|
307
|
-
paddingVertical: 40,
|
|
308
|
-
},
|
|
309
|
-
permissionIconContainer: {
|
|
310
|
-
width: 80,
|
|
311
|
-
height: 80,
|
|
312
|
-
borderRadius: 40,
|
|
313
|
-
backgroundColor: '#f0f0f0',
|
|
314
|
-
justifyContent: 'center',
|
|
315
|
-
alignItems: 'center',
|
|
316
|
-
marginBottom: 24,
|
|
317
|
-
},
|
|
318
|
-
permissionIcon: {
|
|
319
|
-
fontSize: 40,
|
|
320
|
-
},
|
|
321
|
-
permissionTitle: {
|
|
322
|
-
fontSize: 20,
|
|
323
|
-
fontWeight: '600',
|
|
324
|
-
color: '#1a1a1a',
|
|
325
|
-
textAlign: 'center',
|
|
326
|
-
marginBottom: 12,
|
|
327
|
-
},
|
|
328
|
-
permissionDescription: {
|
|
329
|
-
fontSize: 16,
|
|
330
|
-
color: '#666',
|
|
331
|
-
textAlign: 'center',
|
|
332
|
-
lineHeight: 22,
|
|
333
|
-
marginBottom: 32,
|
|
334
|
-
},
|
|
335
|
-
|
|
336
|
-
// Error styles
|
|
337
|
-
errorTitle: {
|
|
338
|
-
fontSize: 18,
|
|
339
|
-
fontWeight: '600',
|
|
340
|
-
color: '#1a1a1a',
|
|
341
|
-
textAlign: 'center',
|
|
342
|
-
marginBottom: 8,
|
|
343
|
-
},
|
|
344
|
-
// Camera styles
|
|
345
|
-
cameraContainer: {
|
|
346
|
-
flex: 1,
|
|
347
|
-
position: 'relative',
|
|
348
|
-
marginTop: 60,
|
|
349
|
-
},
|
|
350
|
-
cameraOverlay: {
|
|
351
|
-
position: 'absolute',
|
|
352
|
-
top: 0,
|
|
353
|
-
left: 0,
|
|
354
|
-
right: 0,
|
|
355
|
-
bottom: 0,
|
|
356
|
-
justifyContent: 'center',
|
|
357
|
-
alignItems: 'center',
|
|
358
|
-
},
|
|
359
|
-
faceFrame: {
|
|
360
|
-
width: 200,
|
|
361
|
-
height: 250,
|
|
362
|
-
borderWidth: 3,
|
|
363
|
-
borderColor: 'white',
|
|
364
|
-
borderRadius: 100,
|
|
365
|
-
borderStyle: 'dashed',
|
|
366
|
-
backgroundColor: 'transparent',
|
|
367
|
-
},
|
|
368
|
-
instructionText: {
|
|
369
|
-
color: 'white',
|
|
370
|
-
fontSize: 16,
|
|
371
|
-
fontWeight: '500',
|
|
372
|
-
textAlign: 'center',
|
|
373
|
-
marginTop: 20,
|
|
374
|
-
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
375
|
-
paddingHorizontal: 16,
|
|
376
|
-
paddingVertical: 8,
|
|
377
|
-
borderRadius: 20,
|
|
378
|
-
},
|
|
379
|
-
// Verification flow styles
|
|
380
|
-
progressContainer: {
|
|
381
|
-
position: 'absolute',
|
|
382
|
-
top: 20,
|
|
383
|
-
left: 20,
|
|
384
|
-
right: 20,
|
|
385
|
-
height: 4,
|
|
386
|
-
backgroundColor: 'rgba(255,255,255,0.3)',
|
|
387
|
-
borderRadius: 2,
|
|
388
|
-
overflow: 'hidden',
|
|
389
|
-
},
|
|
390
|
-
progressBar: {
|
|
391
|
-
height: '100%',
|
|
392
|
-
borderRadius: 2,
|
|
393
|
-
},
|
|
394
|
-
stepIndicator: {
|
|
395
|
-
color: 'white',
|
|
396
|
-
fontSize: 14,
|
|
397
|
-
fontWeight: '400',
|
|
398
|
-
textAlign: 'center',
|
|
399
|
-
marginTop: 8,
|
|
400
|
-
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
401
|
-
paddingHorizontal: 12,
|
|
402
|
-
paddingVertical: 4,
|
|
403
|
-
borderRadius: 12,
|
|
404
|
-
alignSelf: 'center',
|
|
405
|
-
},
|
|
406
|
-
statusContainer: {
|
|
407
|
-
position: 'absolute',
|
|
408
|
-
bottom: 40,
|
|
409
|
-
left: 20,
|
|
410
|
-
right: 20,
|
|
411
|
-
padding: 20,
|
|
412
|
-
borderRadius: 12,
|
|
413
|
-
alignItems: 'center',
|
|
414
|
-
},
|
|
415
|
-
statusContainerError: {
|
|
416
|
-
backgroundColor: '#ff4444',
|
|
417
|
-
},
|
|
418
|
-
statusText: {
|
|
419
|
-
color: 'white',
|
|
420
|
-
fontSize: 18,
|
|
421
|
-
fontWeight: '600',
|
|
422
|
-
textAlign: 'center',
|
|
423
|
-
marginBottom: 8,
|
|
424
|
-
},
|
|
425
|
-
retryButton: {
|
|
426
|
-
marginTop: 12,
|
|
427
|
-
minWidth: 120,
|
|
428
|
-
},
|
|
429
|
-
});
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useFrameProcessor,
|
|
3
|
-
VisionCameraProxy,
|
|
4
|
-
} from 'react-native-vision-camera';
|
|
5
|
-
|
|
6
|
-
export interface FaceVerificationResult {
|
|
7
|
-
faceDetected: boolean;
|
|
8
|
-
isSmiling?: boolean;
|
|
9
|
-
isBlinking?: boolean;
|
|
10
|
-
leftEyeClosed?: boolean;
|
|
11
|
-
rightEyeClosed?: boolean;
|
|
12
|
-
headPose?: {
|
|
13
|
-
yaw: number; // Left-right movement (-90 to 90)
|
|
14
|
-
pitch: number; // Up-down movement (-90 to 90)
|
|
15
|
-
roll: number; // Tilt movement (-180 to 180)
|
|
16
|
-
};
|
|
17
|
-
boundingBox?: {
|
|
18
|
-
x?: number;
|
|
19
|
-
y?: number;
|
|
20
|
-
width?: number;
|
|
21
|
-
height?: number;
|
|
22
|
-
left?: number;
|
|
23
|
-
top?: number;
|
|
24
|
-
right?: number;
|
|
25
|
-
bottom?: number;
|
|
26
|
-
};
|
|
27
|
-
smileProbability?: number;
|
|
28
|
-
leftEyeOpenProbability?: number;
|
|
29
|
-
rightEyeOpenProbability?: number;
|
|
30
|
-
trackingId?: number;
|
|
31
|
-
error?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Note: For production use, you would typically use shared values or state management
|
|
35
|
-
// to communicate results from the worklet back to the React component.
|
|
36
|
-
// This simplified version just logs results for debugging.
|
|
37
|
-
|
|
38
|
-
// Initialize the plugin
|
|
39
|
-
const plugin = VisionCameraProxy.initFrameProcessorPlugin('detectFaces', {});
|
|
40
|
-
|
|
41
|
-
export const useFaceVerification = () => {
|
|
42
|
-
const frameProcessor = useFrameProcessor((frame) => {
|
|
43
|
-
'worklet';
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
if (plugin == null) {
|
|
47
|
-
console.error('Failed to load Frame Processor Plugin "detectFaces"!');
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const result = plugin.call(frame, {}) as any;
|
|
52
|
-
|
|
53
|
-
// For debugging - this works in worklets
|
|
54
|
-
if (result && result.faceDetected) {
|
|
55
|
-
console.log('Face detected:', result);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return result;
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error('Face verification error:', error);
|
|
61
|
-
return {
|
|
62
|
-
faceDetected: false,
|
|
63
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
}, []);
|
|
67
|
-
|
|
68
|
-
return frameProcessor;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Utility functions for interpreting results
|
|
72
|
-
export const FaceVerificationUtils = {
|
|
73
|
-
// Check if head is turned left (yaw > 20 degrees)
|
|
74
|
-
isHeadTurnedLeft: (result: FaceVerificationResult): boolean => {
|
|
75
|
-
return result.headPose?.yaw ? result.headPose.yaw > 20 : false;
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
// Check if head is turned right (yaw < -20 degrees)
|
|
79
|
-
isHeadTurnedRight: (result: FaceVerificationResult): boolean => {
|
|
80
|
-
return result.headPose?.yaw ? result.headPose.yaw < -20 : false;
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
// Check if head is tilted up (pitch > 10 degrees)
|
|
84
|
-
isHeadTiltedUp: (result: FaceVerificationResult): boolean => {
|
|
85
|
-
return result.headPose?.pitch ? result.headPose.pitch > 10 : false;
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
// Check if head is tilted down (pitch < -10 degrees)
|
|
89
|
-
isHeadTiltedDown: (result: FaceVerificationResult): boolean => {
|
|
90
|
-
return result.headPose?.pitch ? result.headPose.pitch < -10 : false;
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
// Check if face is centered (within ±10 degrees)
|
|
94
|
-
isFaceCentered: (result: FaceVerificationResult): boolean => {
|
|
95
|
-
if (!result.headPose) return false;
|
|
96
|
-
const { yaw, pitch } = result.headPose;
|
|
97
|
-
return Math.abs(yaw) <= 10 && Math.abs(pitch) <= 10;
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
// Check if smile is confident (high probability)
|
|
101
|
-
isConfidentSmile: (result: FaceVerificationResult): boolean => {
|
|
102
|
-
return result.smileProbability
|
|
103
|
-
? result.smileProbability > 0.8
|
|
104
|
-
: result.isSmiling || false;
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
// Check if both eyes are confidently closed
|
|
108
|
-
isConfidentBlink: (result: FaceVerificationResult): boolean => {
|
|
109
|
-
if (
|
|
110
|
-
result.leftEyeOpenProbability !== undefined &&
|
|
111
|
-
result.rightEyeOpenProbability !== undefined
|
|
112
|
-
) {
|
|
113
|
-
return (
|
|
114
|
-
result.leftEyeOpenProbability < 0.2 &&
|
|
115
|
-
result.rightEyeOpenProbability < 0.2
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
return result.isBlinking || false;
|
|
119
|
-
},
|
|
120
|
-
};
|