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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-biometric-verifier",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "A React Native module for biometric verification with face recognition and QR code scanning",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -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 ,GIF_URL,IMAGE_URL} from '../utils/constants';
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: `${IMAGE_URL}${imageurl}` }
22
- : { uri: `${GIF_URL}camera.png` }; // Add a local fallback image in assets
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 handleCountdownFinish = useCallback(() => {
77
- notifyMessage('Time is up! Please try again.', 'error');
78
- updateState({
79
- modalVisible: false,
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
- return;
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
- updateState({
101
- isLoading: true,
102
- animationState: ANIMATION_STATES.PROCESSING,
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
- try {
106
- const body = { image: base64 }
107
- const header = { faceid: currentData }
108
- const response = await networkServiceCall("POST",RECOGNIZE_URL,header,body);
109
- if (response.httpstatus === 200) {
110
- notifyMessage('Identity verified successfully!', 'success');
111
- updateState({
112
- employeeData: response.data?.data,
113
- animationState: ANIMATION_STATES.SUCCESS,
114
- });
115
-
116
- if (qrscan) {
117
- // If QR scanning is required, continue with QR Scan
118
- setTimeout(() => startQRCodeScan(), 1500);
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
- // If only Face recognition → finish process
121
- setTimeout(() => {
122
- callback?.(dataRef.current);
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
- } else {
128
- updateState({ animationState: ANIMATION_STATES.ERROR });
129
- notifyMessage(response.data?.error?.message || 'Face not recognized. Please try again.', 'error');
173
+ } catch (error) {
174
+ handleProcessError('Connection error. Please check your network.', error);
175
+ } finally {
176
+ updateState({ isLoading: false });
130
177
  }
131
- } catch (error) {
132
- updateState({ animationState: ANIMATION_STATES.ERROR });
133
- notifyMessage('Connection error. Please check your network.', 'error');
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(async (qrCodeData) => {
165
- updateState({
166
- animationState: ANIMATION_STATES.PROCESSING,
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
- const validCoords = !isNaN(lat) && !isNaN(lng);
191
- const validDev = !isNaN(location.latitude) && !isNaN(location.longitude);
211
+ updateState({
212
+ animationState: ANIMATION_STATES.PROCESSING,
213
+ isLoading: true,
214
+ });
192
215
 
193
- if (validCoords && validDev) {
194
- const distance = getDistanceInMeters(
195
- lat,
196
- lng,
197
- location.latitude,
198
- location.longitude
199
- );
216
+ try {
217
+ const hasPermission = await requestLocationPermission();
218
+ if (!hasPermission) {
219
+ handleProcessError('Location permission not granted.');
220
+ return;
221
+ }
200
222
 
201
- if (distance <= MAX_DISTANCE_METERS) {
202
- callback?.(dataRef.current);
203
- notifyMessage('Location verified successfully!', 'success');
204
- updateState({ animationState: ANIMATION_STATES.SUCCESS });
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
- setTimeout(() => {
207
- updateState({ modalVisible: false });
208
- resetState();
209
- }, 1500);
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
- notifyMessage(`Location mismatch (${distance.toFixed(0)}m away).`, 'error');
212
- updateState({ animationState: ANIMATION_STATES.ERROR });
213
- resetState();
253
+ handleProcessError('Invalid coordinates in QR code.');
214
254
  }
215
- } else {
216
- notifyMessage('Invalid coordinates in QR code.', 'error');
217
- updateState({ animationState: ANIMATION_STATES.ERROR });
218
- resetState();
255
+ } catch (error) {
256
+ handleProcessError('Unable to verify location. Please try again.', error);
257
+ } finally {
258
+ updateState({ isLoading: false });
219
259
  }
220
- } catch (error) {
221
- notifyMessage('Unable to verify location. Please try again.', 'error');
222
- updateState({ animationState: ANIMATION_STATES.ERROR });
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 && getLoaderGif(state.animationState, state.currentStep) && (
289
- <Loader source={getLoaderGif(state.animationState, state.currentStep)} />
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
  );
@@ -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, GIF_URL } from '../utils/constants';
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
- `${GIF_URL}Face.gif`;
11
+ `${APIURL}file/getCommonFile/image/Face.gif`;
12
12
  const LocationGifUrl =
13
- `${GIF_URL}Location.gif`;
13
+ `${APIURL}file/getCommonFile/image/Location.gif`;
14
14
 
15
15
  if (
16
16
  animationState === ANIMATION_STATES.FACE_SCAN ||
@@ -1,6 +0,0 @@
1
- export class Global {
2
- static ipAddress = "api.amalaplus.org";
3
- static port = "9090";
4
-
5
- }
6
-