react-native-biometric-verifier 0.0.42 → 0.0.43
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/StepIndicator.js +34 -32
- package/src/index.js +154 -145
package/package.json
CHANGED
|
@@ -7,45 +7,17 @@ import { Global } from '../utils/Global';
|
|
|
7
7
|
const StepIndicator = ({ currentStep, qrscan }) => {
|
|
8
8
|
return (
|
|
9
9
|
<View style={styles.statusContainer}>
|
|
10
|
-
{/* Identity Step */}
|
|
11
|
-
<View style={styles.statusItem}>
|
|
12
|
-
<Icon
|
|
13
|
-
name="face"
|
|
14
|
-
size={20}
|
|
15
|
-
color={
|
|
16
|
-
currentStep === "Identity Verification" ||
|
|
17
|
-
currentStep === "Location Verification" ||
|
|
18
|
-
currentStep === "Complete"
|
|
19
|
-
? Global.AppTheme.primary
|
|
20
|
-
: Global.AppTheme.light
|
|
21
|
-
}
|
|
22
|
-
style={styles.statusIcon}
|
|
23
|
-
/>
|
|
24
|
-
<Text
|
|
25
|
-
style={[
|
|
26
|
-
styles.statusText,
|
|
27
|
-
(currentStep === "Identity Verification" ||
|
|
28
|
-
currentStep === "Location Verification" ||
|
|
29
|
-
currentStep === "Complete") && styles.statusTextActive,
|
|
30
|
-
]}
|
|
31
|
-
>
|
|
32
|
-
Identity
|
|
33
|
-
</Text>
|
|
34
|
-
</View>
|
|
35
|
-
|
|
36
|
-
{/* Show Location only if qrscan = true */}
|
|
37
10
|
{qrscan && (
|
|
38
11
|
<>
|
|
39
|
-
<View style={styles.statusSeparator} />
|
|
40
12
|
<View style={styles.statusItem}>
|
|
41
13
|
<Icon
|
|
42
14
|
name="location-on"
|
|
43
15
|
size={20}
|
|
44
16
|
color={
|
|
45
17
|
currentStep === "Location Verification" ||
|
|
46
|
-
|
|
47
|
-
? Global.AppTheme.
|
|
48
|
-
: Global.AppTheme.
|
|
18
|
+
currentStep === "Complete"
|
|
19
|
+
? Global.AppTheme.light
|
|
20
|
+
: Global.AppTheme.primary
|
|
49
21
|
}
|
|
50
22
|
style={styles.statusIcon}
|
|
51
23
|
/>
|
|
@@ -56,11 +28,41 @@ const StepIndicator = ({ currentStep, qrscan }) => {
|
|
|
56
28
|
currentStep === "Complete") && styles.statusTextActive,
|
|
57
29
|
]}
|
|
58
30
|
>
|
|
59
|
-
|
|
31
|
+
QR
|
|
60
32
|
</Text>
|
|
61
33
|
</View>
|
|
34
|
+
<View style={styles.statusSeparator} />
|
|
62
35
|
</>
|
|
63
36
|
)}
|
|
37
|
+
|
|
38
|
+
{/* Identity Step */}
|
|
39
|
+
<View style={styles.statusItem}>
|
|
40
|
+
<Icon
|
|
41
|
+
name="face"
|
|
42
|
+
size={20}
|
|
43
|
+
color={
|
|
44
|
+
currentStep === "Identity Verification" ||
|
|
45
|
+
currentStep === "Location Verification" ||
|
|
46
|
+
currentStep === "Complete"
|
|
47
|
+
? Global.AppTheme.light
|
|
48
|
+
: Global.AppTheme.primary
|
|
49
|
+
}
|
|
50
|
+
style={styles.statusIcon}
|
|
51
|
+
/>
|
|
52
|
+
<Text
|
|
53
|
+
style={[
|
|
54
|
+
styles.statusText,
|
|
55
|
+
(currentStep === "Identity Verification" ||
|
|
56
|
+
currentStep === "Location Verification" ||
|
|
57
|
+
currentStep === "Complete") && styles.statusTextActive,
|
|
58
|
+
]}
|
|
59
|
+
>
|
|
60
|
+
ID
|
|
61
|
+
</Text>
|
|
62
|
+
</View>
|
|
63
|
+
|
|
64
|
+
{/* Show Location only if qrscan = true */}
|
|
65
|
+
|
|
64
66
|
</View>
|
|
65
67
|
);
|
|
66
68
|
};
|
package/src/index.js
CHANGED
|
@@ -38,7 +38,7 @@ import CaptureImageWithoutEdit from "./components/CaptureImageWithoutEdit";
|
|
|
38
38
|
import StepIndicator from "./components/StepIndicator";
|
|
39
39
|
|
|
40
40
|
const BiometricModal = React.memo(
|
|
41
|
-
({ data, qrscan = false, callback, apiurl, onclose, frameProcessorFps, livenessLevel, fileurl, imageurl, navigation, MaxDistanceMeters =
|
|
41
|
+
({ data, depkey, qrscan = false, callback, apiurl, onclose, frameProcessorFps, livenessLevel, fileurl, imageurl, navigation, MaxDistanceMeters = 50, duration = 100 }) => {
|
|
42
42
|
// Custom hooks
|
|
43
43
|
const { countdown, startCountdown, resetCountdown, pauseCountdown, resumeCountdown } = useCountdown(duration);
|
|
44
44
|
const { requestLocationPermission, getCurrentLocation } = useGeolocation();
|
|
@@ -48,13 +48,14 @@ const BiometricModal = React.memo(
|
|
|
48
48
|
|
|
49
49
|
// State
|
|
50
50
|
const [modalVisible, setModalVisible] = useState(false);
|
|
51
|
-
const [cameraType, setCameraType] = useState("
|
|
51
|
+
const [cameraType, setCameraType] = useState("back"); // Start with back camera for QR scan
|
|
52
52
|
const [state, setState] = useState({
|
|
53
53
|
isLoading: false,
|
|
54
54
|
loadingType: Global.LoadingTypes.none,
|
|
55
55
|
currentStep: "Start",
|
|
56
56
|
employeeData: null,
|
|
57
|
-
animationState: Global.AnimationStates.
|
|
57
|
+
animationState: Global.AnimationStates.qrScan, // Start with QR scan animation
|
|
58
|
+
qrData: null, // Store QR data for later use in face recognition
|
|
58
59
|
});
|
|
59
60
|
|
|
60
61
|
// Refs
|
|
@@ -63,7 +64,6 @@ const BiometricModal = React.memo(
|
|
|
63
64
|
const responseRef = useRef(null);
|
|
64
65
|
const processedRef = useRef(false);
|
|
65
66
|
const resetTimeoutRef = useRef(null);
|
|
66
|
-
|
|
67
67
|
// Animation values
|
|
68
68
|
const iconScaleAnim = useRef(new Animated.Value(1)).current;
|
|
69
69
|
const iconOpacityAnim = useRef(new Animated.Value(0)).current;
|
|
@@ -152,7 +152,8 @@ const BiometricModal = React.memo(
|
|
|
152
152
|
loadingType: Global.LoadingTypes.none,
|
|
153
153
|
currentStep: "Start",
|
|
154
154
|
employeeData: null,
|
|
155
|
-
animationState: Global.AnimationStates.
|
|
155
|
+
animationState: Global.AnimationStates.qrScan,
|
|
156
|
+
qrData: null,
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
setModalVisible(false);
|
|
@@ -210,112 +211,7 @@ const BiometricModal = React.memo(
|
|
|
210
211
|
return true;
|
|
211
212
|
}, [apiurl, handleProcessError]);
|
|
212
213
|
|
|
213
|
-
//
|
|
214
|
-
const uploadFaceScan = useCallback(
|
|
215
|
-
async (selfie) => {
|
|
216
|
-
if (!validateApiUrl()) return;
|
|
217
|
-
const currentData = dataRef.current;
|
|
218
|
-
|
|
219
|
-
if (!currentData) {
|
|
220
|
-
handleProcessError("Employee data not found.");
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
updateState({
|
|
225
|
-
isLoading: true,
|
|
226
|
-
loadingType: Global.LoadingTypes.faceRecognition,
|
|
227
|
-
animationState: Global.AnimationStates.processing,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
InteractionManager.runAfterInteractions(async () => {
|
|
231
|
-
let base64;
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
updateState({
|
|
235
|
-
loadingType: Global.LoadingTypes.imageProcessing,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
base64 = await convertImageToBase64(selfie?.uri);
|
|
239
|
-
} catch (err) {
|
|
240
|
-
console.error("Image conversion failed:", err);
|
|
241
|
-
handleProcessError("Image conversion failed.", err);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (!base64) {
|
|
246
|
-
handleProcessError("Failed to process image.");
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
const body = { image: base64 };
|
|
252
|
-
const header = { faceid: currentData };
|
|
253
|
-
const buttonapi = `${apiurl}python/recognize`;
|
|
254
|
-
|
|
255
|
-
updateState({
|
|
256
|
-
loadingType: Global.LoadingTypes.networkRequest,
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
const response = await networkServiceCall(
|
|
260
|
-
"POST",
|
|
261
|
-
buttonapi,
|
|
262
|
-
header,
|
|
263
|
-
body
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
if (response?.httpstatus === 200) {
|
|
267
|
-
responseRef.current = response;
|
|
268
|
-
|
|
269
|
-
updateState({
|
|
270
|
-
employeeData: response.data?.data || null,
|
|
271
|
-
animationState: Global.AnimationStates.success,
|
|
272
|
-
isLoading: false,
|
|
273
|
-
loadingType: Global.LoadingTypes.none,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
notifyMessage("Identity verified successfully!", "success");
|
|
277
|
-
|
|
278
|
-
if (qrscan) {
|
|
279
|
-
setTimeout(() => startQRCodeScan(), 1200);
|
|
280
|
-
} else {
|
|
281
|
-
safeCallback(responseRef.current);
|
|
282
|
-
|
|
283
|
-
if (resetTimeoutRef.current) {
|
|
284
|
-
clearTimeout(resetTimeoutRef.current);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
resetTimeoutRef.current = setTimeout(() => {
|
|
288
|
-
resetState();
|
|
289
|
-
}, 1200);
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
handleProcessError(
|
|
293
|
-
response?.data?.message ||
|
|
294
|
-
"Face not recognized. Please try again."
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
} catch (error) {
|
|
298
|
-
console.error("Network request failed:", error);
|
|
299
|
-
handleProcessError(
|
|
300
|
-
"Connection error. Please check your network.",
|
|
301
|
-
error
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
},
|
|
306
|
-
[
|
|
307
|
-
convertImageToBase64,
|
|
308
|
-
notifyMessage,
|
|
309
|
-
qrscan,
|
|
310
|
-
resetState,
|
|
311
|
-
updateState,
|
|
312
|
-
validateApiUrl,
|
|
313
|
-
safeCallback,
|
|
314
|
-
handleProcessError
|
|
315
|
-
]
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
// QR code processing
|
|
214
|
+
// QR code processing - FIRST STEP
|
|
319
215
|
const handleQRScanned = useCallback(
|
|
320
216
|
async (qrCodeData) => {
|
|
321
217
|
if (!validateApiUrl()) return;
|
|
@@ -352,14 +248,12 @@ const BiometricModal = React.memo(
|
|
|
352
248
|
|
|
353
249
|
const location = await getCurrentLocation();
|
|
354
250
|
|
|
355
|
-
const [latStr, lngStr] = qrString.split(",");
|
|
251
|
+
const [latStr, lngStr, qrkey] = qrString.split(",");
|
|
356
252
|
const lat = parseFloat(latStr);
|
|
357
253
|
const lng = parseFloat(lngStr);
|
|
358
254
|
const validCoords = !isNaN(lat) && !isNaN(lng);
|
|
359
|
-
const validDev =
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (validCoords && validDev) {
|
|
255
|
+
const validDev = !isNaN(location?.latitude) && !isNaN(location?.longitude);
|
|
256
|
+
if (validCoords && validDev && qrkey === depkey) {
|
|
363
257
|
updateState({
|
|
364
258
|
loadingType: Global.LoadingTypes.calculateDistance,
|
|
365
259
|
});
|
|
@@ -370,7 +264,6 @@ const BiometricModal = React.memo(
|
|
|
370
264
|
location.latitude,
|
|
371
265
|
location.longitude
|
|
372
266
|
);
|
|
373
|
-
|
|
374
267
|
if (distance <= MaxDistanceMeters) {
|
|
375
268
|
const locationDetails = {
|
|
376
269
|
qrLocation: {
|
|
@@ -387,27 +280,24 @@ const BiometricModal = React.memo(
|
|
|
387
280
|
verifiedAt: new Date().toISOString(),
|
|
388
281
|
};
|
|
389
282
|
|
|
283
|
+
// Store location details and QR data for face recognition
|
|
390
284
|
responseRef.current = {
|
|
391
|
-
...(responseRef.current || {}), // existing faceData
|
|
392
285
|
location: locationDetails,
|
|
393
286
|
};
|
|
394
287
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
notifyMessage("Location verified successfully!", "success");
|
|
398
|
-
|
|
288
|
+
// Store QR data in state for face recognition step
|
|
399
289
|
updateState({
|
|
290
|
+
qrData: qrString,
|
|
400
291
|
animationState: Global.AnimationStates.success,
|
|
401
292
|
isLoading: false,
|
|
402
293
|
loadingType: Global.LoadingTypes.none,
|
|
403
294
|
});
|
|
404
295
|
|
|
405
|
-
|
|
406
|
-
clearTimeout(resetTimeoutRef.current);
|
|
407
|
-
}
|
|
296
|
+
notifyMessage("Location verified successfully!", "success");
|
|
408
297
|
|
|
409
|
-
|
|
410
|
-
|
|
298
|
+
// Start face recognition after a brief delay
|
|
299
|
+
setTimeout(() => {
|
|
300
|
+
startFaceRecognition();
|
|
411
301
|
}, 1200);
|
|
412
302
|
}
|
|
413
303
|
else {
|
|
@@ -430,49 +320,165 @@ const BiometricModal = React.memo(
|
|
|
430
320
|
getCurrentLocation,
|
|
431
321
|
notifyMessage,
|
|
432
322
|
requestLocationPermission,
|
|
323
|
+
updateState,
|
|
324
|
+
validateApiUrl,
|
|
325
|
+
handleProcessError
|
|
326
|
+
]
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Face scan upload - SECOND STEP
|
|
330
|
+
const uploadFaceScan = useCallback(
|
|
331
|
+
async (selfie) => {
|
|
332
|
+
if (!validateApiUrl()) return;
|
|
333
|
+
|
|
334
|
+
// Check if QR scan was completed successfully
|
|
335
|
+
if (!state.qrData) {
|
|
336
|
+
handleProcessError("Please complete QR scan first.");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const currentData = dataRef.current;
|
|
341
|
+
|
|
342
|
+
if (!currentData) {
|
|
343
|
+
handleProcessError("Employee data not found.");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
updateState({
|
|
348
|
+
isLoading: true,
|
|
349
|
+
loadingType: Global.LoadingTypes.faceRecognition,
|
|
350
|
+
animationState: Global.AnimationStates.processing,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
InteractionManager.runAfterInteractions(async () => {
|
|
354
|
+
let base64;
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
updateState({
|
|
358
|
+
loadingType: Global.LoadingTypes.imageProcessing,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
base64 = await convertImageToBase64(selfie?.uri);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
console.error("Image conversion failed:", err);
|
|
364
|
+
handleProcessError("Image conversion failed.", err);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!base64) {
|
|
369
|
+
handleProcessError("Failed to process image.");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const body = { image: base64 };
|
|
375
|
+
const header = { faceid: currentData };
|
|
376
|
+
const buttonapi = `${apiurl}python/recognize`;
|
|
377
|
+
|
|
378
|
+
updateState({
|
|
379
|
+
loadingType: Global.LoadingTypes.networkRequest,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const response = await networkServiceCall(
|
|
383
|
+
"POST",
|
|
384
|
+
buttonapi,
|
|
385
|
+
header,
|
|
386
|
+
body
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
if (response?.httpstatus === 200) {
|
|
390
|
+
// Combine face recognition response with QR location data
|
|
391
|
+
responseRef.current = {
|
|
392
|
+
...responseRef.current, // Contains location data from QR scan
|
|
393
|
+
...response.data?.data || {},
|
|
394
|
+
faceRecognition: response.data?.data || null,
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
updateState({
|
|
398
|
+
employeeData: response.data?.data || null,
|
|
399
|
+
animationState: Global.AnimationStates.success,
|
|
400
|
+
isLoading: false,
|
|
401
|
+
loadingType: Global.LoadingTypes.none,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
notifyMessage("Identity verified successfully!", "success");
|
|
405
|
+
|
|
406
|
+
// Call the callback with combined data
|
|
407
|
+
safeCallback(responseRef.current);
|
|
408
|
+
|
|
409
|
+
if (resetTimeoutRef.current) {
|
|
410
|
+
clearTimeout(resetTimeoutRef.current);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
resetTimeoutRef.current = setTimeout(() => {
|
|
414
|
+
resetState();
|
|
415
|
+
}, 1200);
|
|
416
|
+
} else {
|
|
417
|
+
handleProcessError(
|
|
418
|
+
response?.data?.message ||
|
|
419
|
+
"Face not recognized. Please try again."
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error("Network request failed:", error);
|
|
424
|
+
handleProcessError(
|
|
425
|
+
"Connection error. Please check your network.",
|
|
426
|
+
error
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
},
|
|
431
|
+
[
|
|
432
|
+
convertImageToBase64,
|
|
433
|
+
notifyMessage,
|
|
433
434
|
resetState,
|
|
434
435
|
updateState,
|
|
435
436
|
validateApiUrl,
|
|
436
437
|
safeCallback,
|
|
437
|
-
handleProcessError
|
|
438
|
+
handleProcessError,
|
|
439
|
+
state.qrData
|
|
438
440
|
]
|
|
439
441
|
);
|
|
440
442
|
|
|
441
443
|
// Image capture handler
|
|
442
444
|
const handleImageCapture = useCallback(
|
|
443
445
|
async (capturedData) => {
|
|
444
|
-
if (state.currentStep === "
|
|
445
|
-
uploadFaceScan(capturedData);
|
|
446
|
-
} else if (state.currentStep === "Location Verification") {
|
|
446
|
+
if (state.currentStep === "Location Verification") {
|
|
447
447
|
handleQRScanned(capturedData);
|
|
448
|
+
} else if (state.currentStep === "Identity Verification") {
|
|
449
|
+
uploadFaceScan(capturedData);
|
|
448
450
|
}
|
|
449
451
|
},
|
|
450
452
|
[state.currentStep, uploadFaceScan, handleQRScanned]
|
|
451
453
|
);
|
|
452
454
|
|
|
453
|
-
// Start
|
|
454
|
-
const
|
|
455
|
+
// Start QR code scan - FIRST STEP
|
|
456
|
+
const handleStartQRScan = useCallback(() => {
|
|
455
457
|
updateState({
|
|
456
|
-
currentStep: "
|
|
457
|
-
animationState: Global.AnimationStates.
|
|
458
|
+
currentStep: "Location Verification",
|
|
459
|
+
animationState: Global.AnimationStates.qrScan,
|
|
458
460
|
});
|
|
459
|
-
setCameraType("
|
|
461
|
+
setCameraType("back");
|
|
460
462
|
}, [updateState]);
|
|
461
463
|
|
|
462
|
-
// Start
|
|
463
|
-
const
|
|
464
|
+
// Start face recognition - SECOND STEP
|
|
465
|
+
const startFaceRecognition = useCallback(() => {
|
|
464
466
|
updateState({
|
|
465
|
-
currentStep: "
|
|
466
|
-
animationState: Global.AnimationStates.
|
|
467
|
+
currentStep: "Identity Verification",
|
|
468
|
+
animationState: Global.AnimationStates.faceScan,
|
|
467
469
|
});
|
|
468
|
-
setCameraType("
|
|
470
|
+
setCameraType("front");
|
|
469
471
|
}, [updateState]);
|
|
470
472
|
|
|
471
473
|
// Start the verification process
|
|
472
474
|
const startProcess = useCallback(() => {
|
|
473
475
|
startCountdown(handleCountdownFinish);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
+
if (qrscan) {
|
|
477
|
+
handleStartQRScan();
|
|
478
|
+
} else {
|
|
479
|
+
startFaceRecognition();
|
|
480
|
+
}
|
|
481
|
+
}, [handleCountdownFinish, handleStartQRScan, startCountdown, startFaceRecognition]);
|
|
476
482
|
|
|
477
483
|
// Open modal when data is received
|
|
478
484
|
useEffect(() => {
|
|
@@ -481,7 +487,7 @@ const BiometricModal = React.memo(
|
|
|
481
487
|
setModalVisible(true);
|
|
482
488
|
startProcess();
|
|
483
489
|
}
|
|
484
|
-
}, [data, modalVisible, startProcess]);
|
|
490
|
+
}, [data, modalVisible, startProcess, qrscan]);
|
|
485
491
|
|
|
486
492
|
// Determine if camera should be shown
|
|
487
493
|
const shouldShowCamera =
|
|
@@ -535,7 +541,10 @@ const BiometricModal = React.memo(
|
|
|
535
541
|
</View>
|
|
536
542
|
|
|
537
543
|
<View style={styles.topContainerstep}>
|
|
538
|
-
<StepIndicator
|
|
544
|
+
<StepIndicator
|
|
545
|
+
currentStep={state.currentStep}
|
|
546
|
+
qrscan={qrscan}
|
|
547
|
+
/>
|
|
539
548
|
</View>
|
|
540
549
|
|
|
541
550
|
{state.employeeData && (
|