react-native-biometric-verifier 0.0.43 → 0.0.44
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 +1 -1
- package/src/components/Notification.js +14 -38
- package/src/index.js +46 -13
package/package.json
CHANGED
|
@@ -1,32 +1,23 @@
|
|
|
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';
|
|
5
4
|
import { Global } from '../utils/Global';
|
|
6
5
|
|
|
7
|
-
export const Notification = ({ notification, fadeAnim, slideAnim }) => {
|
|
8
|
-
|
|
9
|
-
console.warn('Notification: Invalid or missing notification object');
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
6
|
+
export const Notification = ({ notification = {}, fadeAnim, slideAnim }) => {
|
|
7
|
+
const { visible = false, type = 'info', message = '' } = notification;
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
// Animations (must ALWAYS exist)
|
|
10
|
+
const scaleAnim = useRef(new Animated.Value(1)).current;
|
|
11
|
+
const shakeAnim = useRef(new Animated.Value(0)).current;
|
|
15
12
|
|
|
16
|
-
// Icon and color mapping
|
|
17
13
|
const iconMap = {
|
|
18
14
|
success: { name: 'check-circle', color: Global.AppTheme.success },
|
|
19
15
|
error: { name: 'error', color: Global.AppTheme.error },
|
|
20
16
|
info: { name: 'info', color: Global.AppTheme.info },
|
|
21
17
|
};
|
|
22
18
|
|
|
23
|
-
const { name: iconName, color: iconColor } =
|
|
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;
|
|
19
|
+
const { name: iconName, color: iconColor } =
|
|
20
|
+
iconMap[type] || iconMap.info;
|
|
30
21
|
|
|
31
22
|
useEffect(() => {
|
|
32
23
|
const heartbeat = Animated.loop(
|
|
@@ -78,20 +69,20 @@ export const Notification = ({ notification, fadeAnim, slideAnim }) => {
|
|
|
78
69
|
heartbeat.stop();
|
|
79
70
|
shake.stop();
|
|
80
71
|
};
|
|
81
|
-
}, [visible, type
|
|
72
|
+
}, [visible, type]);
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
const shakeInterpolate = shakeAnim.interpolate({
|
|
74
|
+
const shakeRotate = shakeAnim.interpolate({
|
|
85
75
|
inputRange: [-1, 1],
|
|
86
76
|
outputRange: ['-10deg', '10deg'],
|
|
87
77
|
});
|
|
88
78
|
|
|
89
79
|
return (
|
|
90
80
|
<Animated.View
|
|
81
|
+
pointerEvents={visible ? 'auto' : 'none'}
|
|
91
82
|
style={[
|
|
92
83
|
styles.container,
|
|
93
84
|
{
|
|
94
|
-
opacity:
|
|
85
|
+
opacity: visible ? 1 : 0,
|
|
95
86
|
transform: [
|
|
96
87
|
{ translateY: slideAnim instanceof Animated.Value ? slideAnim : 0 },
|
|
97
88
|
],
|
|
@@ -102,12 +93,13 @@ export const Notification = ({ notification, fadeAnim, slideAnim }) => {
|
|
|
102
93
|
style={{
|
|
103
94
|
transform: [
|
|
104
95
|
{ scale: scaleAnim },
|
|
105
|
-
...(type === 'error' ? [{ rotate:
|
|
96
|
+
...(type === 'error' ? [{ rotate: shakeRotate }] : []),
|
|
106
97
|
],
|
|
107
98
|
}}
|
|
108
99
|
>
|
|
109
|
-
<Icon name={iconName} size={50} color={iconColor}
|
|
100
|
+
<Icon name={iconName} size={50} color={iconColor} />
|
|
110
101
|
</Animated.View>
|
|
102
|
+
|
|
111
103
|
<Text style={styles.message}>
|
|
112
104
|
{message || 'No message provided'}
|
|
113
105
|
</Text>
|
|
@@ -142,20 +134,4 @@ const styles = StyleSheet.create({
|
|
|
142
134
|
},
|
|
143
135
|
});
|
|
144
136
|
|
|
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
|
-
|
|
161
137
|
export default Notification;
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,8 @@ import React, {
|
|
|
3
3
|
useEffect,
|
|
4
4
|
useRef,
|
|
5
5
|
useCallback,
|
|
6
|
-
|
|
6
|
+
useImperativeHandle,
|
|
7
|
+
forwardRef,
|
|
7
8
|
} from "react";
|
|
8
9
|
import {
|
|
9
10
|
View,
|
|
@@ -37,8 +38,21 @@ import { Notification } from "./components/Notification";
|
|
|
37
38
|
import CaptureImageWithoutEdit from "./components/CaptureImageWithoutEdit";
|
|
38
39
|
import StepIndicator from "./components/StepIndicator";
|
|
39
40
|
|
|
40
|
-
const BiometricModal =
|
|
41
|
-
|
|
41
|
+
const BiometricModal = forwardRef(({
|
|
42
|
+
data,
|
|
43
|
+
depkey,
|
|
44
|
+
qrscan = false,
|
|
45
|
+
callback,
|
|
46
|
+
apiurl,
|
|
47
|
+
onclose,
|
|
48
|
+
frameProcessorFps,
|
|
49
|
+
livenessLevel,
|
|
50
|
+
fileurl,
|
|
51
|
+
imageurl,
|
|
52
|
+
navigation,
|
|
53
|
+
MaxDistanceMeters = 50,
|
|
54
|
+
duration = 100
|
|
55
|
+
}, ref) => {
|
|
42
56
|
// Custom hooks
|
|
43
57
|
const { countdown, startCountdown, resetCountdown, pauseCountdown, resumeCountdown } = useCountdown(duration);
|
|
44
58
|
const { requestLocationPermission, getCurrentLocation } = useGeolocation();
|
|
@@ -68,6 +82,14 @@ const BiometricModal = React.memo(
|
|
|
68
82
|
const iconScaleAnim = useRef(new Animated.Value(1)).current;
|
|
69
83
|
const iconOpacityAnim = useRef(new Animated.Value(0)).current;
|
|
70
84
|
|
|
85
|
+
// Expose methods to parent via ref
|
|
86
|
+
useImperativeHandle(ref, () => ({
|
|
87
|
+
reset: resetState,
|
|
88
|
+
start: startProcess,
|
|
89
|
+
close: resetState,
|
|
90
|
+
getStatus: () => state,
|
|
91
|
+
}));
|
|
92
|
+
|
|
71
93
|
// Cleanup on unmount
|
|
72
94
|
useEffect(() => {
|
|
73
95
|
return () => {
|
|
@@ -145,7 +167,9 @@ const BiometricModal = React.memo(
|
|
|
145
167
|
|
|
146
168
|
// Reset state helper
|
|
147
169
|
const resetState = useCallback(() => {
|
|
148
|
-
onclose
|
|
170
|
+
if (onclose) {
|
|
171
|
+
onclose(false);
|
|
172
|
+
}
|
|
149
173
|
|
|
150
174
|
setState({
|
|
151
175
|
isLoading: false,
|
|
@@ -165,7 +189,7 @@ const BiometricModal = React.memo(
|
|
|
165
189
|
clearTimeout(resetTimeoutRef.current);
|
|
166
190
|
resetTimeoutRef.current = null;
|
|
167
191
|
}
|
|
168
|
-
}, [resetCountdown, clearNotification]);
|
|
192
|
+
}, [resetCountdown, clearNotification, onclose]);
|
|
169
193
|
|
|
170
194
|
// Error handler
|
|
171
195
|
const handleProcessError = useCallback(
|
|
@@ -196,7 +220,7 @@ const BiometricModal = React.memo(
|
|
|
196
220
|
const handleCountdownFinish = useCallback(() => {
|
|
197
221
|
handleProcessError("Time is up! Please try again.");
|
|
198
222
|
|
|
199
|
-
if (navigation
|
|
223
|
+
if (navigation?.canGoBack?.()) {
|
|
200
224
|
navigation.goBack();
|
|
201
225
|
}
|
|
202
226
|
}, [handleProcessError, navigation]);
|
|
@@ -322,7 +346,9 @@ const BiometricModal = React.memo(
|
|
|
322
346
|
requestLocationPermission,
|
|
323
347
|
updateState,
|
|
324
348
|
validateApiUrl,
|
|
325
|
-
handleProcessError
|
|
349
|
+
handleProcessError,
|
|
350
|
+
depkey,
|
|
351
|
+
MaxDistanceMeters
|
|
326
352
|
]
|
|
327
353
|
);
|
|
328
354
|
|
|
@@ -436,7 +462,8 @@ const BiometricModal = React.memo(
|
|
|
436
462
|
validateApiUrl,
|
|
437
463
|
safeCallback,
|
|
438
464
|
handleProcessError,
|
|
439
|
-
state.qrData
|
|
465
|
+
state.qrData,
|
|
466
|
+
apiurl
|
|
440
467
|
]
|
|
441
468
|
);
|
|
442
469
|
|
|
@@ -444,9 +471,9 @@ const BiometricModal = React.memo(
|
|
|
444
471
|
const handleImageCapture = useCallback(
|
|
445
472
|
async (capturedData) => {
|
|
446
473
|
if (state.currentStep === "Location Verification") {
|
|
447
|
-
handleQRScanned(capturedData);
|
|
474
|
+
await handleQRScanned(capturedData);
|
|
448
475
|
} else if (state.currentStep === "Identity Verification") {
|
|
449
|
-
uploadFaceScan(capturedData);
|
|
476
|
+
await uploadFaceScan(capturedData);
|
|
450
477
|
}
|
|
451
478
|
},
|
|
452
479
|
[state.currentStep, uploadFaceScan, handleQRScanned]
|
|
@@ -478,7 +505,7 @@ const BiometricModal = React.memo(
|
|
|
478
505
|
} else {
|
|
479
506
|
startFaceRecognition();
|
|
480
507
|
}
|
|
481
|
-
}, [handleCountdownFinish, handleStartQRScan, startCountdown, startFaceRecognition]);
|
|
508
|
+
}, [handleCountdownFinish, handleStartQRScan, startCountdown, startFaceRecognition, qrscan]);
|
|
482
509
|
|
|
483
510
|
// Open modal when data is received
|
|
484
511
|
useEffect(() => {
|
|
@@ -487,7 +514,7 @@ const BiometricModal = React.memo(
|
|
|
487
514
|
setModalVisible(true);
|
|
488
515
|
startProcess();
|
|
489
516
|
}
|
|
490
|
-
}, [data, modalVisible, startProcess
|
|
517
|
+
}, [data, modalVisible, startProcess]);
|
|
491
518
|
|
|
492
519
|
// Determine if camera should be shown
|
|
493
520
|
const shouldShowCamera =
|
|
@@ -577,6 +604,12 @@ const BiometricModal = React.memo(
|
|
|
577
604
|
}
|
|
578
605
|
);
|
|
579
606
|
|
|
607
|
+
// Wrap with memo after forwardRef
|
|
608
|
+
const MemoizedBiometricModal = React.memo(BiometricModal);
|
|
609
|
+
|
|
610
|
+
// Add display name for debugging
|
|
611
|
+
MemoizedBiometricModal.displayName = 'BiometricModal';
|
|
612
|
+
|
|
580
613
|
const styles = StyleSheet.create({
|
|
581
614
|
modalContainer: {
|
|
582
615
|
flex: 1,
|
|
@@ -671,4 +704,4 @@ const styles = StyleSheet.create({
|
|
|
671
704
|
},
|
|
672
705
|
});
|
|
673
706
|
|
|
674
|
-
export default
|
|
707
|
+
export default MemoizedBiometricModal;
|