react-native-biometric-verifier 0.0.33 → 0.0.38

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.33",
3
+ "version": "0.0.38",
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,7 +24,7 @@ const CaptureImageWithoutEdit = React.memo(
24
24
  showCodeScanner = false,
25
25
  isLoading = false,
26
26
  frameProcessorFps = 1,
27
- livenessLevel = 0, // 0 = anti-spoof only, 1 = anti-spoof + blinking
27
+ livenessLevel = 0,
28
28
  }) => {
29
29
  const cameraRef = useRef(null);
30
30
  const [cameraDevice, setCameraDevice] = useState(null);
@@ -42,6 +42,7 @@ const CaptureImageWithoutEdit = React.memo(
42
42
  const [antiSpoofConfidence, setAntiSpoofConfidence] = useState(0);
43
43
  const [isFaceCentered, setIsFaceCentered] = useState(false);
44
44
  const [hasSingleFace, setHasSingleFace] = useState(false);
45
+ const [recognitionImageBase64, setRecognitionImageBase64] = useState(null);
45
46
 
46
47
  const captured = useRef(false);
47
48
  const isMounted = useRef(true);
@@ -60,6 +61,7 @@ const CaptureImageWithoutEdit = React.memo(
60
61
  setAntiSpoofConfidence(0);
61
62
  setIsFaceCentered(false);
62
63
  setHasSingleFace(false);
64
+ setRecognitionImageBase64(null);
63
65
  }, []);
64
66
 
65
67
  const codeScanner = useCodeScanner({
@@ -76,38 +78,13 @@ const CaptureImageWithoutEdit = React.memo(
76
78
  });
77
79
 
78
80
  const onStableFaceDetected = useCallback(
79
- async (faceRect) => {
81
+ async (faceRect, Result) => {
80
82
  if (!isMounted.current) return;
81
83
  if (captured.current) return;
82
-
83
84
  captured.current = true;
84
85
  setFaces([faceRect]);
85
-
86
86
  try {
87
- if (!cameraRef.current) {
88
- throw new Error('Camera ref not available');
89
- }
90
-
91
- const photo = await cameraRef.current.takePhoto({
92
- flash: 'off',
93
- qualityPrioritization: 'quality',
94
- enableShutterSound: false,
95
- skipMetadata: true,
96
- });
97
-
98
- if (!photo || !photo.path) {
99
- throw new Error('Failed to capture photo - no path returned');
100
- }
101
-
102
- const photopath = `file://${photo.path}`;
103
- const fileName = photopath.substr(photopath.lastIndexOf('/') + 1);
104
- const photoData = {
105
- uri: photopath,
106
- filename: fileName,
107
- filetype: 'image/jpeg',
108
- };
109
-
110
- onCapture(photoData, faceRect);
87
+ onCapture(Result?.recognitionImageBase64);
111
88
  } catch (e) {
112
89
  console.error('Capture error:', e);
113
90
  captured.current = false;
@@ -130,6 +107,7 @@ const CaptureImageWithoutEdit = React.memo(
130
107
  setAntiSpoofConfidence(antiSpoofState.confidence || 0);
131
108
  setIsFaceCentered(antiSpoofState.isFaceCentered || false);
132
109
  setHasSingleFace(antiSpoofState.hasSingleFace || false);
110
+ setRecognitionImageBase64(antiSpoofState.recognitionImageBase64 || null);
133
111
  }
134
112
 
135
113
  if (count === 1) {
@@ -163,6 +141,11 @@ const CaptureImageWithoutEdit = React.memo(
163
141
  const onAntiSpoofUpdate = useCallback((result) => {
164
142
  if (!isMounted.current) return;
165
143
  try {
144
+ // Update recognition image when available
145
+ if (result?.recognitionImageBase64) {
146
+ setRecognitionImageBase64(result.recognitionImageBase64);
147
+ }
148
+
166
149
  // Animate live indicator when face becomes live
167
150
  if (result?.isLive && !isFaceLive) {
168
151
  Animated.spring(liveIndicatorAnim, {
@@ -109,6 +109,7 @@ export const useFaceDetectionFrameProcessor = ({
109
109
  lastResult: null,
110
110
  isLive: false,
111
111
  confidence: 0,
112
+ recognitionImageBase64: null, // Added to store base64 image
112
113
  },
113
114
 
114
115
  // Face centering
@@ -147,6 +148,7 @@ export const useFaceDetectionFrameProcessor = ({
147
148
  state.antiSpoof.lastResult = null;
148
149
  state.antiSpoof.isLive = false;
149
150
  state.antiSpoof.confidence = 0;
151
+ state.antiSpoof.recognitionImageBase64 = null; // Reset base64 image
150
152
  state.flags.hasSingleFace = false;
151
153
  state.centering.centeredFrames = 0;
152
154
  state.flags.isFaceCentered = false;
@@ -300,6 +302,7 @@ export const useFaceDetectionFrameProcessor = ({
300
302
  consecutiveLiveFrames: 0,
301
303
  isFaceCentered: false,
302
304
  hasSingleFace: false,
305
+ recognitionImageBase64: null,
303
306
  });
304
307
  return;
305
308
  }
@@ -314,6 +317,7 @@ export const useFaceDetectionFrameProcessor = ({
314
317
  consecutiveLiveFrames: 0,
315
318
  isFaceCentered: false,
316
319
  hasSingleFace: false,
320
+ recognitionImageBase64: null,
317
321
  });
318
322
  return;
319
323
  }
@@ -360,19 +364,28 @@ export const useFaceDetectionFrameProcessor = ({
360
364
  if (state.flags.isFaceCentered) {
361
365
  try {
362
366
  antiSpoofResult = faceAntiSpoofFrameProcessor?.(frame);
367
+
363
368
  if (antiSpoofResult != null) {
364
369
  state.antiSpoof.lastResult = antiSpoofResult;
365
370
 
366
371
  const isLive = antiSpoofResult.isLive === true;
367
372
  const confidence = antiSpoofResult.combinedScore || antiSpoofResult.neuralNetworkScore || 0;
373
+ const recognitionImageBase64 = antiSpoofResult.recognitionImageBase64 || null;
368
374
 
375
+ // Store the base64 image when face is live
369
376
  if (isLive && confidence > ANTI_SPOOF_CONFIDENCE_THRESHOLD) {
370
377
  state.antiSpoof.consecutiveLiveFrames = Math.min(
371
378
  REQUIRED_CONSECUTIVE_LIVE_FRAMES,
372
379
  state.antiSpoof.consecutiveLiveFrames + 1
373
380
  );
381
+ // Store the base64 image when we have a live face
382
+ if (recognitionImageBase64) {
383
+ state.antiSpoof.recognitionImageBase64 = recognitionImageBase64;
384
+ }
374
385
  } else {
375
386
  state.antiSpoof.consecutiveLiveFrames = Math.max(0, state.antiSpoof.consecutiveLiveFrames - 1);
387
+ // Clear base64 image if face is not live
388
+ state.antiSpoof.recognitionImageBase64 = null;
376
389
  }
377
390
  state.antiSpoof.isLive = state.antiSpoof.consecutiveLiveFrames >= REQUIRED_CONSECUTIVE_LIVE_FRAMES;
378
391
  state.antiSpoof.confidence = confidence;
@@ -385,16 +398,19 @@ export const useFaceDetectionFrameProcessor = ({
385
398
  rawResult: antiSpoofResult,
386
399
  consecutiveLiveFrames: state.antiSpoof.consecutiveLiveFrames,
387
400
  isFaceCentered: state.flags.isFaceCentered,
401
+ recognitionImageBase64: state.antiSpoof.recognitionImageBase64,
388
402
  });
389
403
  }
390
404
  }
391
405
  } catch (antiSpoofError) {
392
406
  // Silent error handling
407
+ console.log('Anti-spoof error:', antiSpoofError);
393
408
  }
394
409
  } else {
395
410
  // Reset anti-spoof if face not centered
396
411
  state.antiSpoof.consecutiveLiveFrames = 0;
397
412
  state.antiSpoof.isLive = false;
413
+ state.antiSpoof.recognitionImageBase64 = null;
398
414
  }
399
415
 
400
416
  // Liveness logic - optimized
@@ -461,6 +477,7 @@ export const useFaceDetectionFrameProcessor = ({
461
477
  consecutiveLiveFrames: state.antiSpoof.consecutiveLiveFrames,
462
478
  isFaceCentered: state.flags.isFaceCentered,
463
479
  hasSingleFace: true,
480
+ recognitionImageBase64: state.antiSpoof.recognitionImageBase64,
464
481
  });
465
482
  }
466
483
 
@@ -479,9 +496,13 @@ export const useFaceDetectionFrameProcessor = ({
479
496
 
480
497
  if (shouldCapture) {
481
498
  state.flags.captured = true;
499
+ // Include the base64 image in the stable face detection callback
482
500
  runOnStable(
483
501
  { x, y, width, height },
484
- state.antiSpoof.lastResult
502
+ {
503
+ ...state.antiSpoof.lastResult,
504
+ recognitionImageBase64: state.antiSpoof.recognitionImageBase64
505
+ }
485
506
  );
486
507
  }
487
508
  } else {
@@ -492,6 +513,7 @@ export const useFaceDetectionFrameProcessor = ({
492
513
  state.flags.hasSingleFace = false;
493
514
  state.centering.centeredFrames = 0;
494
515
  state.flags.isFaceCentered = false;
516
+ state.antiSpoof.recognitionImageBase64 = null;
495
517
 
496
518
  runOnFaces(detected.length, 0, state.liveness.step, false, {
497
519
  isLive: false,
@@ -499,10 +521,12 @@ export const useFaceDetectionFrameProcessor = ({
499
521
  consecutiveLiveFrames: 0,
500
522
  isFaceCentered: false,
501
523
  hasSingleFace: false,
524
+ recognitionImageBase64: null,
502
525
  });
503
526
  }
504
527
  } catch (err) {
505
528
  // Error boundary - ensure frame is released
529
+ console.log('Frame processor error:', err);
506
530
  } finally {
507
531
  frame.release?.();
508
532
  }
@@ -527,6 +551,7 @@ export const useFaceDetectionFrameProcessor = ({
527
551
  state.antiSpoof.lastResult = null;
528
552
  state.antiSpoof.isLive = false;
529
553
  state.antiSpoof.confidence = 0;
554
+ state.antiSpoof.recognitionImageBase64 = null; // Reset base64 image
530
555
  state.flags.hasSingleFace = false;
531
556
  state.centering.centeredFrames = 0;
532
557
  state.flags.isFaceCentered = false;
@@ -561,6 +586,7 @@ export const useFaceDetectionFrameProcessor = ({
561
586
  lastResult: null,
562
587
  isLive: false,
563
588
  confidence: 0,
589
+ recognitionImageBase64: null, // Reset base64 image
564
590
  },
565
591
  centering: {
566
592
  centeredFrames: 0,
@@ -612,6 +638,7 @@ export const useFaceDetectionFrameProcessor = ({
612
638
  lastResult: sharedState.value.antiSpoof.lastResult,
613
639
  hasSingleFace: sharedState.value.flags.hasSingleFace,
614
640
  isFaceCentered: sharedState.value.flags.isFaceCentered,
641
+ recognitionImageBase64: sharedState.value.antiSpoof.recognitionImageBase64,
615
642
  },
616
643
  };
617
644
  };
package/src/index.js CHANGED
@@ -216,7 +216,7 @@ const BiometricModal = React.memo(
216
216
 
217
217
  // Face scan upload
218
218
  const uploadFaceScan = useCallback(
219
- async (selfie) => {
219
+ async (base64) => {
220
220
  if (!validateApiUrl()) return;
221
221
  const currentData = dataRef.current;
222
222
 
@@ -231,81 +231,66 @@ const BiometricModal = React.memo(
231
231
  animationState: Global.AnimationStates.processing,
232
232
  });
233
233
 
234
- InteractionManager.runAfterInteractions(async () => {
235
- let base64;
236
-
237
- try {
238
- updateState({
239
- loadingType: Global.LoadingTypes.imageProcessing,
240
- });
241
-
242
- base64 = await convertImageToBase64(selfie?.uri);
243
- } catch (err) {
244
- console.error("Image conversion failed:", err);
245
- handleProcessError("Image conversion failed.", err);
246
- return;
247
- }
234
+ if (!base64) {
235
+ handleProcessError("Failed to process image.");
236
+ return;
237
+ }
248
238
 
249
- if (!base64) {
250
- handleProcessError("Failed to process image.");
251
- return;
252
- }
239
+ try {
240
+ const body = { image: base64 };
241
+ const header = { faceid: currentData };
242
+ const buttonapi = `${apiurl}python/recognize`;
253
243
 
254
- try {
255
- const body = { image: base64 };
256
- const header = { faceid: currentData };
257
- const buttonapi = `${apiurl}python/recognize`;
244
+ updateState({
245
+ loadingType: Global.LoadingTypes.networkRequest,
246
+ });
247
+ console.log("API URL:", buttonapi);
248
+ console.log("Sending request to API...", JSON.stringify(body));
249
+ const response = await networkServiceCall(
250
+ "POST",
251
+ buttonapi,
252
+ header,
253
+ body
254
+ );
255
+ console.log("API Response:", JSON.stringify(response));
256
+ if (response?.httpstatus === 200 && response?.data?.data) {
257
+ responseRef.current = response;
258
258
 
259
259
  updateState({
260
- loadingType: Global.LoadingTypes.networkRequest,
260
+ employeeData: response.data?.data || null,
261
+ animationState: Global.AnimationStates.success,
262
+ isLoading: false,
263
+ loadingType: Global.LoadingTypes.none,
261
264
  });
262
265
 
263
- const response = await networkServiceCall(
264
- "POST",
265
- buttonapi,
266
- header,
267
- body
268
- );
269
-
270
- if (response?.httpstatus === 200) {
271
- responseRef.current = response;
272
-
273
- updateState({
274
- employeeData: response.data?.data || null,
275
- animationState: Global.AnimationStates.success,
276
- isLoading: false,
277
- loadingType: Global.LoadingTypes.none,
278
- });
266
+ notifyMessage("Identity verified successfully!", "success");
279
267
 
280
- notifyMessage("Identity verified successfully!", "success");
281
-
282
- if (qrscan) {
283
- setTimeout(() => startQRCodeScan(), 1200);
284
- } else {
285
- safeCallback(responseRef.current);
286
-
287
- if (resetTimeoutRef.current) {
288
- clearTimeout(resetTimeoutRef.current);
289
- }
268
+ if (qrscan) {
269
+ setTimeout(() => startQRCodeScan(), 1200);
270
+ } else {
271
+ safeCallback(responseRef.current);
290
272
 
291
- resetTimeoutRef.current = setTimeout(() => {
292
- resetState();
293
- }, 1200);
273
+ if (resetTimeoutRef.current) {
274
+ clearTimeout(resetTimeoutRef.current);
294
275
  }
295
- } else {
296
- handleProcessError(
297
- response?.data?.message ||
298
- "Face not recognized. Please try again."
299
- );
276
+
277
+ resetTimeoutRef.current = setTimeout(() => {
278
+ resetState();
279
+ }, 1200);
300
280
  }
301
- } catch (error) {
302
- console.error("Network request failed:", error);
281
+ } else {
303
282
  handleProcessError(
304
- "Connection error. Please check your network.",
305
- error
283
+ response?.data?.message ||
284
+ "Face not recognized. Please try again."
306
285
  );
307
286
  }
308
- });
287
+ } catch (error) {
288
+ console.error("Network request failed:", error);
289
+ handleProcessError(
290
+ "Connection error. Please check your network.",
291
+ error
292
+ );
293
+ }
309
294
  },
310
295
  [
311
296
  convertImageToBase64,