react-native-biometric-verifier 0.0.6 → 0.0.8
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/EmployeeCard.js +4 -4
- package/src/index.js +157 -127
- package/src/utils/constants.js +0 -12
- package/src/utils/getLoaderGif.js +4 -4
- package/src/utils/Global.js +0 -6
package/package.json
CHANGED
|
@@ -2,10 +2,10 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import { View, Text, Image } from 'react-native';
|
|
3
3
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
|
-
import { COLORS
|
|
5
|
+
import { COLORS} from '../utils/constants';
|
|
6
6
|
import { styles } from './styles';
|
|
7
7
|
|
|
8
|
-
export const EmployeeCard = ({ employeeData }) => {
|
|
8
|
+
export const EmployeeCard = ({ employeeData,apiurl }) => {
|
|
9
9
|
const [imageError, setImageError] = useState(false);
|
|
10
10
|
|
|
11
11
|
if (!employeeData || typeof employeeData !== 'object') {
|
|
@@ -18,8 +18,8 @@ export const EmployeeCard = ({ employeeData }) => {
|
|
|
18
18
|
const employeeName = facename || 'Unknown Employee';
|
|
19
19
|
const employeeId = faceid || 'N/A';
|
|
20
20
|
const imageSource = !imageError && imageurl
|
|
21
|
-
? { uri: `${
|
|
22
|
-
: { uri: `${
|
|
21
|
+
? { uri: `${apiurl}file/filedownload/photo/${imageurl}` }
|
|
22
|
+
: { uri: `${apiurl}file/getCommonFile/image/camera.png` }; // Add a local fallback image in assets
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<View style={styles.employeeCard}>
|
package/src/index.js
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
COLORS,
|
|
17
17
|
COUNTDOWN_DURATION,
|
|
18
18
|
MAX_DISTANCE_METERS,
|
|
19
|
-
RECOGNIZE_URL,
|
|
20
19
|
} from './utils/constants';
|
|
21
20
|
import Loader from './components/Loader';
|
|
22
21
|
import { CountdownTimer } from './components/CountdownTimer';
|
|
@@ -28,9 +27,9 @@ import { useNavigation } from '@react-navigation/native';
|
|
|
28
27
|
import networkServiceCall from './utils/NetworkServiceCall';
|
|
29
28
|
import { getLoaderGif } from './utils/getLoaderGif';
|
|
30
29
|
|
|
31
|
-
const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback }) => {
|
|
30
|
+
const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback, apiurl }) => {
|
|
32
31
|
const navigation = useNavigation();
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
const { countdown, startCountdown, resetCountdown } = useCountdown();
|
|
35
34
|
const { requestLocationPermission, getCurrentLocation } = useGeolocation();
|
|
36
35
|
const { convertImageToBase64 } = useImageProcessing();
|
|
@@ -46,6 +45,23 @@ const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback
|
|
|
46
45
|
|
|
47
46
|
const dataRef = useRef(data);
|
|
48
47
|
const mountedRef = useRef(true);
|
|
48
|
+
const responseRef = useRef(null);
|
|
49
|
+
|
|
50
|
+
const safeCallback = useCallback(
|
|
51
|
+
(response) => {
|
|
52
|
+
if (typeof callback === 'function') {
|
|
53
|
+
try {
|
|
54
|
+
callback(response);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error('Callback execution failed:', err);
|
|
57
|
+
notifyMessage('Unexpected error while processing callback.', 'error');
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
console.log('Biometric Verification Response:', response);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[callback, notifyMessage]
|
|
64
|
+
);
|
|
49
65
|
|
|
50
66
|
useEffect(() => {
|
|
51
67
|
return () => {
|
|
@@ -59,7 +75,7 @@ const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback
|
|
|
59
75
|
|
|
60
76
|
const updateState = useCallback((newState) => {
|
|
61
77
|
if (mountedRef.current) {
|
|
62
|
-
setState(prev => ({ ...prev, ...newState }));
|
|
78
|
+
setState((prev) => ({ ...prev, ...newState }));
|
|
63
79
|
}
|
|
64
80
|
}, []);
|
|
65
81
|
|
|
@@ -73,68 +89,95 @@ const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback
|
|
|
73
89
|
resetCountdown();
|
|
74
90
|
}, [resetCountdown, updateState]);
|
|
75
91
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
animationState: ANIMATION_STATES.ERROR,
|
|
81
|
-
});
|
|
82
|
-
resetState();
|
|
83
|
-
navigation.goBack();
|
|
84
|
-
}, [navigation, notifyMessage, resetState, updateState]);
|
|
85
|
-
|
|
86
|
-
const uploadFaceScan = useCallback(async (selfie) => {
|
|
87
|
-
const currentData = dataRef.current;
|
|
88
|
-
const base64 = await convertImageToBase64(selfie.uri);
|
|
89
|
-
if (!currentData) {
|
|
90
|
-
notifyMessage('Employee data not found.', 'error');
|
|
91
|
-
updateState({ animationState: ANIMATION_STATES.ERROR });
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (!base64) {
|
|
95
|
-
notifyMessage('convert Image To Base64 Failed.', 'error');
|
|
92
|
+
const handleProcessError = useCallback(
|
|
93
|
+
(message, errorObj = null) => {
|
|
94
|
+
if (errorObj) console.error(message, errorObj);
|
|
95
|
+
notifyMessage(message, 'error');
|
|
96
96
|
updateState({ animationState: ANIMATION_STATES.ERROR });
|
|
97
|
-
|
|
97
|
+
setTimeout(resetState, 1200);
|
|
98
|
+
},
|
|
99
|
+
[notifyMessage, resetState, updateState]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const handleCountdownFinish = useCallback(() => {
|
|
103
|
+
handleProcessError('Time is up! Please try again.');
|
|
104
|
+
updateState({ modalVisible: false });
|
|
105
|
+
if (navigation.canGoBack()) navigation.goBack();
|
|
106
|
+
}, [handleProcessError, navigation, updateState]);
|
|
107
|
+
|
|
108
|
+
const validateApiUrl = useCallback(() => {
|
|
109
|
+
if (!apiurl || typeof apiurl !== 'string') {
|
|
110
|
+
handleProcessError('Invalid API URL configuration.');
|
|
111
|
+
return false;
|
|
98
112
|
}
|
|
113
|
+
return true;
|
|
114
|
+
}, [apiurl, handleProcessError]);
|
|
99
115
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
116
|
+
const uploadFaceScan = useCallback(
|
|
117
|
+
async (selfie) => {
|
|
118
|
+
if (!validateApiUrl()) return;
|
|
119
|
+
|
|
120
|
+
const currentData = dataRef.current;
|
|
121
|
+
if (!currentData) {
|
|
122
|
+
handleProcessError('Employee data not found.');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let base64;
|
|
127
|
+
try {
|
|
128
|
+
base64 = await convertImageToBase64(selfie?.uri);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
handleProcessError('Image conversion failed.', err);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!base64) {
|
|
135
|
+
handleProcessError('Failed to process image.');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
104
138
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
139
|
+
updateState({
|
|
140
|
+
isLoading: true,
|
|
141
|
+
animationState: ANIMATION_STATES.PROCESSING,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const body = { image: base64 };
|
|
146
|
+
const header = { faceid: currentData };
|
|
147
|
+
const buttonapi = `${apiurl}python/recognize`;
|
|
148
|
+
console.log('buttonapi', buttonapi);
|
|
149
|
+
const response = await networkServiceCall('POST', buttonapi, header, body);
|
|
150
|
+
|
|
151
|
+
if (response?.httpstatus === 200) {
|
|
152
|
+
responseRef.current = response;
|
|
153
|
+
updateState({
|
|
154
|
+
employeeData: response.data?.data || null,
|
|
155
|
+
animationState: ANIMATION_STATES.SUCCESS,
|
|
156
|
+
});
|
|
157
|
+
notifyMessage('Identity verified successfully!', 'success');
|
|
158
|
+
|
|
159
|
+
if (qrscan) {
|
|
160
|
+
setTimeout(() => startQRCodeScan(), 1500);
|
|
161
|
+
} else {
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
safeCallback(responseRef.current);
|
|
164
|
+
updateState({ modalVisible: false });
|
|
165
|
+
resetState();
|
|
166
|
+
}, 1500);
|
|
167
|
+
}
|
|
119
168
|
} else {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
updateState({ modalVisible: false });
|
|
124
|
-
resetState();
|
|
125
|
-
}, 1500);
|
|
169
|
+
handleProcessError(
|
|
170
|
+
response?.data?.error?.message || 'Face not recognized. Please try again.'
|
|
171
|
+
);
|
|
126
172
|
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
173
|
+
} catch (error) {
|
|
174
|
+
handleProcessError('Connection error. Please check your network.', error);
|
|
175
|
+
} finally {
|
|
176
|
+
updateState({ isLoading: false });
|
|
130
177
|
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} finally {
|
|
135
|
-
updateState({ isLoading: false });
|
|
136
|
-
}
|
|
137
|
-
}, [convertImageToBase64, notifyMessage, qrscan, resetState, updateState, callback]);
|
|
178
|
+
},
|
|
179
|
+
[convertImageToBase64, notifyMessage, qrscan, resetState, updateState, validateApiUrl, safeCallback]
|
|
180
|
+
);
|
|
138
181
|
|
|
139
182
|
const handleStartFaceScan = useCallback(() => {
|
|
140
183
|
updateState({
|
|
@@ -161,70 +204,62 @@ const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback
|
|
|
161
204
|
});
|
|
162
205
|
}, [navigation, updateState]);
|
|
163
206
|
|
|
164
|
-
const handleQRScanned = useCallback(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
isLoading: true
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
const hasPermission = await requestLocationPermission();
|
|
172
|
-
if (!hasPermission) {
|
|
173
|
-
notifyMessage('Location permission not granted.', 'error');
|
|
174
|
-
updateState({ animationState: ANIMATION_STATES.ERROR });
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const qrString = typeof qrCodeData === 'object' ? qrCodeData.data : qrCodeData;
|
|
179
|
-
if (!qrString || typeof qrString !== 'string') {
|
|
180
|
-
notifyMessage('Invalid QR code. Please try again.', 'error');
|
|
181
|
-
updateState({ animationState: ANIMATION_STATES.ERROR });
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const location = await getCurrentLocation();
|
|
186
|
-
const [latStr, lngStr] = qrString.split(',');
|
|
187
|
-
const lat = parseFloat(latStr);
|
|
188
|
-
const lng = parseFloat(lngStr);
|
|
207
|
+
const handleQRScanned = useCallback(
|
|
208
|
+
async (qrCodeData) => {
|
|
209
|
+
if (!validateApiUrl()) return;
|
|
189
210
|
|
|
190
|
-
|
|
191
|
-
|
|
211
|
+
updateState({
|
|
212
|
+
animationState: ANIMATION_STATES.PROCESSING,
|
|
213
|
+
isLoading: true,
|
|
214
|
+
});
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
216
|
+
try {
|
|
217
|
+
const hasPermission = await requestLocationPermission();
|
|
218
|
+
if (!hasPermission) {
|
|
219
|
+
handleProcessError('Location permission not granted.');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
200
222
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
223
|
+
const qrString = typeof qrCodeData === 'object' ? qrCodeData?.data : qrCodeData;
|
|
224
|
+
if (!qrString || typeof qrString !== 'string') {
|
|
225
|
+
handleProcessError('Invalid QR code. Please try again.');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
205
228
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
229
|
+
const location = await getCurrentLocation();
|
|
230
|
+
const [latStr, lngStr] = qrString.split(',');
|
|
231
|
+
const lat = parseFloat(latStr);
|
|
232
|
+
const lng = parseFloat(lngStr);
|
|
233
|
+
|
|
234
|
+
const validCoords = !isNaN(lat) && !isNaN(lng);
|
|
235
|
+
const validDev = !isNaN(location?.latitude) && !isNaN(location?.longitude);
|
|
236
|
+
|
|
237
|
+
if (validCoords && validDev) {
|
|
238
|
+
const distance = getDistanceInMeters(lat, lng, location.latitude, location.longitude);
|
|
239
|
+
|
|
240
|
+
if (distance <= MAX_DISTANCE_METERS) {
|
|
241
|
+
safeCallback(responseRef.current);
|
|
242
|
+
notifyMessage('Location verified successfully!', 'success');
|
|
243
|
+
updateState({ animationState: ANIMATION_STATES.SUCCESS });
|
|
244
|
+
|
|
245
|
+
setTimeout(() => {
|
|
246
|
+
updateState({ modalVisible: false });
|
|
247
|
+
resetState();
|
|
248
|
+
}, 1500);
|
|
249
|
+
} else {
|
|
250
|
+
handleProcessError(`Location mismatch (${distance.toFixed(0)}m away).`);
|
|
251
|
+
}
|
|
210
252
|
} else {
|
|
211
|
-
|
|
212
|
-
updateState({ animationState: ANIMATION_STATES.ERROR });
|
|
213
|
-
resetState();
|
|
253
|
+
handleProcessError('Invalid coordinates in QR code.');
|
|
214
254
|
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
255
|
+
} catch (error) {
|
|
256
|
+
handleProcessError('Unable to verify location. Please try again.', error);
|
|
257
|
+
} finally {
|
|
258
|
+
updateState({ isLoading: false });
|
|
219
259
|
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
resetState();
|
|
224
|
-
} finally {
|
|
225
|
-
updateState({ isLoading: false });
|
|
226
|
-
}
|
|
227
|
-
}, [callback, getCurrentLocation, notifyMessage, requestLocationPermission, resetState, updateState]);
|
|
260
|
+
},
|
|
261
|
+
[getCurrentLocation, notifyMessage, requestLocationPermission, resetState, updateState, validateApiUrl, safeCallback]
|
|
262
|
+
);
|
|
228
263
|
|
|
229
264
|
const startProcess = useCallback(() => {
|
|
230
265
|
startCountdown(COUNTDOWN_DURATION, handleCountdownFinish);
|
|
@@ -268,10 +303,7 @@ const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback
|
|
|
268
303
|
<StateIndicator state={state.animationState} size={120} />
|
|
269
304
|
|
|
270
305
|
{state.employeeData && (
|
|
271
|
-
<EmployeeCard
|
|
272
|
-
employeeData={state.employeeData}
|
|
273
|
-
hrenemp={dataRef.current?.data?.userdata?.hrenemp}
|
|
274
|
-
/>
|
|
306
|
+
<EmployeeCard employeeData={state.employeeData} apiurl={apiurl} />
|
|
275
307
|
)}
|
|
276
308
|
|
|
277
309
|
<Notification
|
|
@@ -280,14 +312,12 @@ const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback
|
|
|
280
312
|
slideAnim={slideAnim}
|
|
281
313
|
/>
|
|
282
314
|
|
|
283
|
-
<CountdownTimer
|
|
284
|
-
duration={COUNTDOWN_DURATION}
|
|
285
|
-
currentTime={countdown}
|
|
286
|
-
/>
|
|
315
|
+
<CountdownTimer duration={COUNTDOWN_DURATION} currentTime={countdown} />
|
|
287
316
|
|
|
288
|
-
{state.isLoading &&
|
|
289
|
-
|
|
290
|
-
|
|
317
|
+
{state.isLoading &&
|
|
318
|
+
getLoaderGif(state.animationState, state.currentStep, apiurl) && (
|
|
319
|
+
<Loader source={getLoaderGif(state.animationState, state.currentStep, apiurl)} />
|
|
320
|
+
)}
|
|
291
321
|
</View>
|
|
292
322
|
</Modal>
|
|
293
323
|
);
|
package/src/utils/constants.js
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
import { Global } from "./Global";
|
|
2
|
-
|
|
3
|
-
// ====== BASE CONFIG ======
|
|
4
|
-
export const BASE_URL = Global.ipAddress;
|
|
5
|
-
export const PORT = Global.port;
|
|
6
|
-
export const APIURL = `http://${BASE_URL}:${PORT}/`;
|
|
7
|
-
|
|
8
|
-
// ====== API ENDPOINTS ======
|
|
9
|
-
export const RECOGNIZE_URL = APIURL + "python/recognize";
|
|
10
|
-
export const IMAGE_URL = APIURL + "file/filedownload/photo/";
|
|
11
|
-
export const GIF_URL = APIURL + "file/getCommonFile/image/";
|
|
12
|
-
|
|
13
1
|
|
|
14
2
|
// ====== COLORS ======
|
|
15
3
|
export const COLORS = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ANIMATION_STATES
|
|
1
|
+
import { ANIMATION_STATES} from '../utils/constants';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Decides which GIF should be shown based on animationState or currentStep
|
|
@@ -6,11 +6,11 @@ import { ANIMATION_STATES, GIF_URL } from '../utils/constants';
|
|
|
6
6
|
* @param {string} currentStep - Current step of verification
|
|
7
7
|
* @returns {any} - Gif image source or null
|
|
8
8
|
*/
|
|
9
|
-
export const getLoaderGif = (animationState, currentStep) => {
|
|
9
|
+
export const getLoaderGif = (animationState, currentStep,APIURL) => {
|
|
10
10
|
const FaceGifUrl =
|
|
11
|
-
`${
|
|
11
|
+
`${APIURL}file/getCommonFile/image/Face.gif`;
|
|
12
12
|
const LocationGifUrl =
|
|
13
|
-
`${
|
|
13
|
+
`${APIURL}file/getCommonFile/image/Location.gif`;
|
|
14
14
|
|
|
15
15
|
if (
|
|
16
16
|
animationState === ANIMATION_STATES.FACE_SCAN ||
|