react-native-biometric-verifier 0.0.25 → 0.0.26

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.25",
3
+ "version": "0.0.26",
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": {
@@ -24,11 +24,10 @@ const CaptureImageWithoutEdit = React.memo(
24
24
  showCodeScanner = false,
25
25
  isLoading = false,
26
26
  frameProcessorFps = 1,
27
- livenessLevel = 1,
27
+ livenessLevel,
28
28
  }) => {
29
29
  const cameraRef = useRef(null);
30
30
  const [cameraDevice, setCameraDevice] = useState(null);
31
- const [cameraPermission, setCameraPermission] = useState('not-determined');
32
31
  const [showCamera, setShowCamera] = useState(false);
33
32
  const [cameraInitialized, setCameraInitialized] = useState(false);
34
33
  const [currentCameraType, setCurrentCameraType] = useState(cameraType);
@@ -161,7 +160,7 @@ const CaptureImageWithoutEdit = React.memo(
161
160
  showCodeScanner,
162
161
  isLoading,
163
162
  isActive: showCamera && cameraInitialized,
164
- livenessLevel,
163
+ livenessLevel: livenessLevel || 0, // Ensure livenessLevel is never null/undefined
165
164
  });
166
165
 
167
166
  useEffect(() => {
@@ -181,20 +180,22 @@ const CaptureImageWithoutEdit = React.memo(
181
180
  setShowCamera(false);
182
181
 
183
182
  const newCameraPermission = await Camera.requestCameraPermission();
184
- setCameraPermission(newCameraPermission);
185
-
186
183
  if (newCameraPermission === 'granted') {
187
- const devices = await Camera.getAvailableCameraDevices();
184
+ let devices = await Camera.getAvailableCameraDevices();
185
+
186
+ // 🔁 Retry once after short delay if no devices found
187
+ if (!devices || devices.length === 0) {
188
+ console.warn('No camera devices yet, retrying in 300ms...');
189
+ await new Promise((resolve) => setTimeout(resolve, 300));
190
+ devices = await Camera.getAvailableCameraDevices();
191
+ }
188
192
 
189
193
  if (!devices || devices.length === 0) {
190
194
  throw new Error('No camera devices available');
191
195
  }
192
196
 
193
197
  const device = getCameraDevice(devices, currentCameraType);
194
-
195
- if (!device) {
196
- throw new Error(`No ${currentCameraType} camera available`);
197
- }
198
+ if (!device) throw new Error(`No ${currentCameraType} camera available`);
198
199
 
199
200
  setCameraDevice(device);
200
201
  setShowCamera(true);
@@ -213,6 +214,7 @@ const CaptureImageWithoutEdit = React.memo(
213
214
  }
214
215
  }, [currentCameraType]);
215
216
 
217
+
216
218
  const initializeCamera = useCallback(async () => {
217
219
  await getPermission();
218
220
  }, [getPermission]);
@@ -289,13 +291,15 @@ const CaptureImageWithoutEdit = React.memo(
289
291
  return 'Multiple faces detected';
290
292
  }
291
293
 
292
- if (livenessLevel === 0) {
294
+ const currentLevel = livenessLevel || 0;
295
+
296
+ if (currentLevel === 0) {
293
297
  if (faces.length === 0) return 'Position your face in the frame';
294
298
  if (progress < 100) return 'Hold still...';
295
299
  return 'Perfect! Capturing...';
296
300
  }
297
301
 
298
- if (livenessLevel === 1) {
302
+ if (currentLevel === 1) {
299
303
  switch (livenessStep) {
300
304
  case 0:
301
305
  return 'Face the camera straight';
@@ -308,7 +312,7 @@ const CaptureImageWithoutEdit = React.memo(
308
312
  }
309
313
  }
310
314
 
311
- if (livenessLevel === 2) {
315
+ if (currentLevel === 2) {
312
316
  switch (livenessStep) {
313
317
  case 0:
314
318
  return 'Face the camera straight';
@@ -323,7 +327,7 @@ const CaptureImageWithoutEdit = React.memo(
323
327
  }
324
328
  }
325
329
 
326
- if (livenessLevel === 3) {
330
+ if (currentLevel === 3) {
327
331
  switch (livenessStep) {
328
332
  case 0:
329
333
  return 'Face the camera straight';
@@ -351,7 +355,9 @@ const CaptureImageWithoutEdit = React.memo(
351
355
  ]);
352
356
 
353
357
  const getStepConfig = useCallback(() => {
354
- switch (livenessLevel) {
358
+ const currentLevel = livenessLevel || 0;
359
+
360
+ switch (currentLevel) {
355
361
  case 0:
356
362
  return { totalSteps: 0, showSteps: false };
357
363
  case 1:
@@ -377,13 +383,6 @@ const CaptureImageWithoutEdit = React.memo(
377
383
  return (
378
384
  <View style={styles.container}>
379
385
  <View style={styles.cameraContainer}>
380
- {isInitializing && (
381
- <View style={styles.loadingContainer}>
382
- <ActivityIndicator size="large" color={Global.AppTheme.primary} />
383
- <Text style={styles.placeholderText}>Initializing camera...</Text>
384
- </View>
385
- )}
386
-
387
386
  {!isInitializing && showCamera && cameraDevice ? (
388
387
  <Camera
389
388
  ref={cameraRef}
@@ -403,21 +402,16 @@ const CaptureImageWithoutEdit = React.memo(
403
402
  }}
404
403
  onError={(error) => {
405
404
  console.log('Camera error:', error);
406
- setCameraError('Camera error occurred');
407
- setCameraInitialized(false);
408
405
  }}
409
- enableZoomGesture={false}
410
- exposure={0}
411
- pixelFormat="yuv"
412
- preset="medium"
413
- orientation="portrait"
414
406
  />
415
407
  ) : (
416
408
  <View style={styles.placeholderContainer}>
417
- <Icon name="camera-alt" size={40} color={Global.AppTheme.light} />
418
- <Text style={styles.placeholderText}>
419
- {cameraError || 'Camera not available'}
420
- </Text>
409
+ {isInitializing && (
410
+ <View style={styles.loadingContainer}>
411
+ <ActivityIndicator size="large" color={Global.AppTheme.primary} />
412
+ <Text style={styles.placeholderText}>Initializing camera...</Text>
413
+ </View>
414
+ )}
421
415
  <TouchableOpacity
422
416
  style={styles.retryButton}
423
417
  onPress={handleRetry}
@@ -428,7 +422,7 @@ const CaptureImageWithoutEdit = React.memo(
428
422
  </View>
429
423
  )}
430
424
 
431
- {!showCodeScanner && showCamera && cameraDevice && livenessLevel > 0 && (
425
+ {!showCodeScanner && showCamera && cameraDevice && (livenessLevel || 0) > 0 && (
432
426
  <View style={styles.livenessContainer}>
433
427
  <Animated.View
434
428
  style={[
@@ -476,8 +470,8 @@ const CaptureImageWithoutEdit = React.memo(
476
470
  livenessStep > step
477
471
  ? styles.stepCompleted
478
472
  : livenessStep === step
479
- ? styles.stepCurrent
480
- : styles.stepPending,
473
+ ? styles.stepCurrent
474
+ : styles.stepPending,
481
475
  ]}
482
476
  >
483
477
  <Text style={styles.stepText}>{step + 1}</Text>
@@ -525,7 +519,7 @@ const CaptureImageWithoutEdit = React.memo(
525
519
  </View>
526
520
  )}
527
521
 
528
- {!showCodeScanner && showCamera && cameraDevice && livenessLevel === 0 && (
522
+ {!showCodeScanner && showCamera && cameraDevice && (livenessLevel || 0) === 0 && (
529
523
  <View style={styles.livenessContainer}>
530
524
  <Animated.View
531
525
  style={[
@@ -10,35 +10,35 @@ import {
10
10
  } from "react-native";
11
11
  import FastImage from 'react-native-fast-image';
12
12
  import { normalize } from "react-native-elements";
13
+ import { getLoaderGif } from "../utils/getLoaderGif";
13
14
 
14
15
  export default function Loader({
15
- visible = false,
16
+ state,
16
17
  overlayColor = 'rgba(0,0,0,0.4)',
17
18
  loaderColor = 'lightblue',
18
19
  size = 50,
19
- gifSource,
20
+ gifSource = { uri: "http://emr.amalaims.org:9393/file/getCommonFile/image/Face.gif" },
20
21
  message = '',
21
22
  messageStyle = {},
22
23
  animationType = 'fade',
23
24
  hasBackground = true,
24
25
  borderRadius = 20,
25
- shadow = true
26
+ shadow = true,
27
+ imageurl,
26
28
  }) {
27
29
  const [rotation] = useState(new Animated.Value(0));
28
30
  const [pulse] = useState(new Animated.Value(1));
29
31
  const [fade] = useState(new Animated.Value(0));
30
32
  const [imageSource, setImageSource] = useState(gifSource);
31
-
33
+ const error = getLoaderGif(state.animationState, state.currentStep, "http://emr.amalaims.org:9393/", imageurl)
32
34
  // Reset imageSource whenever gifSource prop changes
33
35
  useEffect(() => {
34
36
  setImageSource(gifSource);
35
37
  }, [gifSource]);
36
38
 
37
39
  const handleImageError = () => {
38
- // Fallback to default heartpulse GIF when image fails to load
39
- setImageSource({
40
- uri: "http://emr.amalaims.org:9393/file/getCommonFile/image/heartpulse.gif"
41
- });
40
+ console.log('LoaderError', JSON.stringify(error))
41
+ setImageSource(error);
42
42
  };
43
43
 
44
44
  // Rotation animation
@@ -92,8 +92,8 @@ export default function Loader({
92
92
  />
93
93
  ) : (
94
94
  <Animated.View style={[
95
- styles.defaultLoader,
96
- {
95
+ styles.defaultLoader,
96
+ {
97
97
  borderColor: loaderColor,
98
98
  transform: [{ rotate: spin }, { scale: pulse }],
99
99
  width: normalize(size),
@@ -102,8 +102,8 @@ export default function Loader({
102
102
  }
103
103
  ]}>
104
104
  <View style={[
105
- styles.innerCircle,
106
- {
105
+ styles.innerCircle,
106
+ {
107
107
  backgroundColor: loaderColor,
108
108
  width: normalize(size / 2),
109
109
  height: normalize(size / 2)
@@ -116,19 +116,19 @@ export default function Loader({
116
116
  <Modal
117
117
  animationType={animationType}
118
118
  transparent={true}
119
- visible={visible}
120
- onRequestClose={() => {}}
119
+ visible={state.isLoading}
120
+ onRequestClose={() => { }}
121
121
  >
122
122
  <Animated.View style={[
123
123
  styles.modalContainer,
124
- {
124
+ {
125
125
  backgroundColor: overlayColor,
126
126
  opacity: fade
127
127
  }
128
128
  ]}>
129
129
  <Animated.View style={[
130
130
  styles.loaderContainer,
131
- {
131
+ {
132
132
  backgroundColor: hasBackground ? 'white' : 'transparent',
133
133
  borderRadius: normalize(borderRadius),
134
134
  transform: [{ scale: pulse }],
@@ -6,7 +6,7 @@ import { useFaceDetector } from 'react-native-vision-camera-face-detector';
6
6
  // Tuned constants for liveness detection / stability
7
7
  const FACE_STABILITY_THRESHOLD = 3;
8
8
  const FACE_MOVEMENT_THRESHOLD = 15;
9
- const FRAME_PROCESSOR_MIN_INTERVAL_MS = 800; // increased to reduce load
9
+ const FRAME_PROCESSOR_MIN_INTERVAL_MS = 500;
10
10
  const MIN_FACE_SIZE = 0.2;
11
11
 
12
12
  // Liveness detection constants
@@ -19,19 +19,19 @@ const BLINK_THRESHOLD = 0.3; // Eye closed if below this
19
19
  const REQUIRED_BLINKS = 3;
20
20
 
21
21
  export const useFaceDetectionFrameProcessor = ({
22
- onStableFaceDetected = () => {},
23
- onFacesUpdate = () => {},
24
- onLivenessUpdate = () => {},
22
+ onStableFaceDetected = () => { },
23
+ onFacesUpdate = () => { },
24
+ onLivenessUpdate = () => { },
25
25
  showCodeScanner = false,
26
26
  isLoading = false,
27
27
  isActive = true,
28
- livenessLevel = 3, // 0=no liveness, 1=blink only, 2=face turn only, 3=both
28
+ livenessLevel,
29
29
  }) => {
30
30
  const { detectFaces } = useFaceDetector({
31
31
  performanceMode: 'fast',
32
32
  landmarkMode: 'none',
33
33
  contourMode: 'none',
34
- classificationMode: livenessLevel === 1 || livenessLevel === 3 ? 'all' : 'none', // Only enable classification for blink detection
34
+ classificationMode: (livenessLevel === 1 || livenessLevel === 3) ? 'all' : 'none', // Only enable classification for blink detection
35
35
  minFaceSize: MIN_FACE_SIZE,
36
36
  });
37
37
 
@@ -49,7 +49,7 @@ export const useFaceDetectionFrameProcessor = ({
49
49
  captured: false,
50
50
  showCodeScanner: showCodeScanner,
51
51
  isActive: isActive,
52
- livenessLevel: livenessLevel,
52
+ livenessLevel: livenessLevel || 0, // Ensure default value
53
53
  livenessStep: 0, // 0=center, 1=left/center, 2=right/blink, 3=blink/capture, 4=capture
54
54
  leftTurnVerified: false,
55
55
  rightTurnVerified: false,
@@ -66,7 +66,7 @@ export const useFaceDetectionFrameProcessor = ({
66
66
  // update flags without recreating object
67
67
  sharedState.value.showCodeScanner = !!showCodeScanner;
68
68
  sharedState.value.isActive = !!isActive;
69
- sharedState.value.livenessLevel = livenessLevel;
69
+ sharedState.value.livenessLevel = livenessLevel || 0;
70
70
 
71
71
  if (isActive && sharedState.value.captured) {
72
72
  // reset minimal fields
@@ -134,8 +134,9 @@ export const useFaceDetectionFrameProcessor = ({
134
134
  const frameProcessor = useFrameProcessor(
135
135
  (frame) => {
136
136
  'worklet';
137
+ console.log('qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',JSON.stringify(frame))
137
138
  const state = sharedState.value;
138
-
139
+
139
140
  // quick exits — do not call frame.release here; we'll release in finally
140
141
  if (state.showCodeScanner || state.captured || isLoading || !state.isActive) {
141
142
  frame.release?.();
@@ -193,11 +194,13 @@ export const useFaceDetectionFrameProcessor = ({
193
194
  } = state;
194
195
 
195
196
  // ---- Liveness Logic Based on Level ----
197
+ // Level 0: Basic face detection with stability check only
196
198
  if (livenessLevel === 0) {
197
199
  // No liveness check - only stability
198
200
  // Skip all liveness steps and go directly to capture when stable
199
201
  livenessStep = 0;
200
- }
202
+ }
203
+ // Level 1: Face detection with blink verification
201
204
  else if (livenessLevel === 1) {
202
205
  // Blink detection only
203
206
  if (livenessStep === 0) {
@@ -348,13 +351,13 @@ export const useFaceDetectionFrameProcessor = ({
348
351
 
349
352
  // Capture condition based on liveness level
350
353
  let shouldCapture = false;
351
-
354
+
355
+ // Level 0: Basic face detection - capture when stable
352
356
  if (livenessLevel === 0) {
353
- // No liveness - capture when stable
354
357
  shouldCapture = newStableCount >= FACE_STABILITY_THRESHOLD;
355
358
  }
359
+ // Level 1: Blink verification - capture after blinks and stable
356
360
  else if (livenessLevel === 1) {
357
- // Blink only - capture after blinks and stable
358
361
  shouldCapture = livenessStep === 2 && blinkCount >= REQUIRED_BLINKS && newStableCount >= FACE_STABILITY_THRESHOLD;
359
362
  }
360
363
  else if (livenessLevel === 2) {
@@ -381,7 +384,7 @@ export const useFaceDetectionFrameProcessor = ({
381
384
  // protect worklet from throwing
382
385
  try {
383
386
  console.error('FrameProcessor worklet error:', err);
384
- } catch (e) {}
387
+ } catch (e) { }
385
388
  } finally {
386
389
  // Always release the frame exactly once
387
390
  frame.release?.();
package/src/index.js CHANGED
@@ -294,7 +294,7 @@ const BiometricModal = React.memo(
294
294
  body
295
295
  );
296
296
 
297
- console.log("📨 API Response:", response);
297
+ console.log("📨 API Response:", JSON.stringify(response));
298
298
 
299
299
  if (response?.httpstatus === 200) {
300
300
  console.log("✅ Face recognition successful");
@@ -326,9 +326,9 @@ const BiometricModal = React.memo(
326
326
  }, 1200);
327
327
  }
328
328
  } else {
329
- console.warn("⚠️ Face recognition failed:", response?.data?.error);
329
+ console.warn("⚠️ Face recognition failed:", response?.httpstatus);
330
330
  handleProcessError(
331
- response?.data?.error?.message ||
331
+ response?.data?.message ||
332
332
  "Face not recognized. Please try again."
333
333
  );
334
334
  }
@@ -543,13 +543,8 @@ const BiometricModal = React.memo(
543
543
  }
544
544
  }, [data, modalVisible, startProcess]);
545
545
 
546
- // Loader source memoization
547
- const loaderSource = useMemo(
548
- () =>
549
- state.isLoading &&
550
- getLoaderGif(state.animationState, state.currentStep, apiurl, imageurl),
551
- [state.isLoading, state.animationState, state.currentStep, apiurl, imageurl]
552
- );
546
+
547
+
553
548
 
554
549
  // Determine if camera should be shown
555
550
  const shouldShowCamera =
@@ -626,10 +621,9 @@ const BiometricModal = React.memo(
626
621
  currentTime={countdown}
627
622
  />
628
623
  </View>
629
-
630
624
  <Loader
631
- gifSource={loaderSource}
632
- visible={state.isLoading}
625
+ state={state}
626
+ gifSource={getLoaderGif(state.animationState, state.currentStep, apiurl, imageurl)}
633
627
  />
634
628
  </View>
635
629
  </Modal>
@@ -1,8 +1,8 @@
1
1
  export class Global {
2
2
  // ====== APP CONSTANTS ======
3
3
  static AppTheme = {
4
- primary: '#6C63FF',
5
- primaryLight: '#8A85FF',
4
+ primary: '#094485',
5
+ primaryLight: '#1b89ffff',
6
6
  success: '#4CAF50',
7
7
  error: '#F44336',
8
8
  warning: '#FF9800',
@@ -6,11 +6,11 @@ import { Global } from "./Global";
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,APIURL,imageurl ='file/getCommonFile/image/') => {
9
+ export const getLoaderGif = (animationState, currentStep,apiurl,imageurl ='file/getCommonFile/image/') => {
10
10
  const FaceGifUrl =
11
- `${APIURL}${imageurl}Face.gif`;
11
+ `${apiurl}${imageurl}Face.gif`;
12
12
  const LocationGifUrl =
13
- `${APIURL}${imageurl}Location.gif`;
13
+ `${apiurl}${imageurl}Location.gif`;
14
14
 
15
15
  if (
16
16
  animationState === Global.AnimationStates.faceScan ||