react-native-biometric-verifier 0.0.45 → 0.0.47

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.45",
3
+ "version": "0.0.47",
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
  "private": false,
@@ -0,0 +1,235 @@
1
+ import { useCallback, useState, useRef } from 'react';
2
+ import { Platform, PermissionsAndroid } from 'react-native';
3
+ import { BleManager } from 'react-native-ble-plx';
4
+
5
+ const manager = new BleManager();
6
+
7
+ /**
8
+ * Bluetooth Service Hook for device scanning and distance estimation
9
+ */
10
+ export const useBluetoothService = (notifyMessage) => {
11
+ const [nearbyDevices, setNearbyDevices] = useState([]);
12
+ const scanTimeoutRef = useRef(null);
13
+
14
+ /**
15
+ * Request Bluetooth permissions
16
+ */
17
+ const requestBluetoothPermissions = useCallback(async () => {
18
+ console.log('[Permissions] Requesting Bluetooth permissions...');
19
+
20
+ if (Platform.OS === 'android') {
21
+ try {
22
+ let permissions = [];
23
+
24
+ if (Platform.Version >= 31) {
25
+ // Android 12+ requires BLUETOOTH_SCAN and BLUETOOTH_CONNECT
26
+ permissions = [
27
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
28
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
29
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
30
+ ];
31
+ } else {
32
+ // Older Android versions
33
+ permissions = [
34
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
35
+ ];
36
+ }
37
+
38
+ const granted = await PermissionsAndroid.requestMultiple(permissions);
39
+
40
+ const allGranted = Object.values(granted).every(
41
+ status => status === PermissionsAndroid.RESULTS.GRANTED
42
+ );
43
+
44
+ if (!allGranted) {
45
+ notifyMessage?.('Bluetooth permissions are required for device scanning', 'warning');
46
+ }
47
+
48
+ return allGranted;
49
+ } catch (error) {
50
+ console.error('[Permissions] Error:', error);
51
+ notifyMessage?.('Failed to request Bluetooth permissions', 'error');
52
+ return false;
53
+ }
54
+ }
55
+ return true; // iOS handles permissions differently
56
+ }, [notifyMessage]);
57
+
58
+ /**
59
+ * Enhanced distance calculation using multiple methods
60
+ */
61
+ const estimateDistance = useCallback((rssi, txPower = -59) => {
62
+ if (!rssi || rssi >= 0) return -1;
63
+
64
+ // Method 1: Log-distance path loss model (most common)
65
+ const n = 2.0; // Path loss exponent (2 for free space, 2.7-3.5 for indoor)
66
+ const distance1 = Math.pow(10, (txPower - rssi) / (10 * n));
67
+
68
+ // Method 2: Quadratic approximation for short distances
69
+ const distance2 = 0.89976 * Math.pow(Math.abs(rssi), 0.80976) + 0.111;
70
+
71
+ // Average the methods for better accuracy
72
+ const avgDistance = (distance1 + distance2) / 2;
73
+
74
+ return Math.max(0.1, avgDistance); // Minimum 0.1m
75
+ }, []);
76
+
77
+ /**
78
+ * Filter out stale BLE devices
79
+ */
80
+ const filterStaleDevices = useCallback(() => {
81
+ setNearbyDevices(prev =>
82
+ prev.filter(device => {
83
+ const isRecent = Date.now() - device.lastSeen < 10000; // 10 seconds
84
+ const hasMultipleReadings = device.count >= 3;
85
+ return isRecent && hasMultipleReadings;
86
+ })
87
+ );
88
+ }, []);
89
+
90
+ /**
91
+ * Stop Bluetooth scan
92
+ */
93
+ const stopBluetoothScan = useCallback(() => {
94
+ console.log('[BLE] Stopping Bluetooth scan...');
95
+ manager.stopDeviceScan();
96
+ if (scanTimeoutRef.current) {
97
+ clearTimeout(scanTimeoutRef.current);
98
+ scanTimeoutRef.current = null;
99
+ }
100
+ }, []);
101
+
102
+ /**
103
+ * Enhanced BLE scanning with filtering and signal processing
104
+ */
105
+ const startBluetoothScan = useCallback(async () => {
106
+ console.log('[BLE] Starting enhanced Bluetooth scan...');
107
+
108
+ const permission = await requestBluetoothPermissions();
109
+ if (!permission) {
110
+ return;
111
+ }
112
+
113
+ // Clear previous devices
114
+ setNearbyDevices([]);
115
+
116
+ // Configure scan for better accuracy
117
+ const scanOptions = {
118
+ allowDuplicates: true,
119
+ scanMode: 2, // SCAN_MODE_LOW_LATENCY for Android
120
+ };
121
+
122
+ manager.startDeviceScan(null, scanOptions, (error, device) => {
123
+ if (error) {
124
+ console.error('[BLE] Scan error:', error);
125
+ stopBluetoothScan();
126
+
127
+ // Provide user-friendly error messages
128
+ let errorMessage = 'Bluetooth scan failed';
129
+ if (error.errorCode === 102) {
130
+ errorMessage = 'Bluetooth is not enabled';
131
+ } else if (error.errorCode === 103) {
132
+ errorMessage = 'Location services required for scanning';
133
+ }
134
+
135
+ notifyMessage?.(errorMessage, 'error');
136
+ return;
137
+ }
138
+
139
+ if (device && device.name && device.rssi) {
140
+ const distance = estimateDistance(device.rssi);
141
+
142
+ // Filter out weak signals and calculate moving average
143
+ if (distance > 0 && distance <= 20) {
144
+ setNearbyDevices(prev => {
145
+ const existingIndex = prev.findIndex(d => d.id === device.id);
146
+
147
+ if (existingIndex >= 0) {
148
+ // Update existing device with moving average
149
+ const existing = prev[existingIndex];
150
+ const avgDistance = (parseFloat(existing.distance) + distance) / 2;
151
+
152
+ const updated = [...prev];
153
+ updated[existingIndex] = {
154
+ ...existing,
155
+ rssi: device.rssi,
156
+ distance: avgDistance.toFixed(2),
157
+ lastSeen: Date.now(),
158
+ count: existing.count + 1,
159
+ txPowerLevel: device.txPowerLevel || existing.txPowerLevel,
160
+ };
161
+ return updated;
162
+ } else {
163
+ // Add new device
164
+ const newDevice = {
165
+ id: device.id,
166
+ name: device.name || 'Unknown Device',
167
+ rssi: device.rssi,
168
+ distance: distance.toFixed(2),
169
+ lastSeen: Date.now(),
170
+ count: 1,
171
+ manufacturerData: device.manufacturerData,
172
+ serviceUUIDs: device.serviceUUIDs,
173
+ txPowerLevel: device.txPowerLevel,
174
+ isConnectable: device.isConnectable,
175
+ };
176
+ return [...prev, newDevice];
177
+ }
178
+ });
179
+ }
180
+ }
181
+ });
182
+
183
+ // Stop after optimized duration
184
+ scanTimeoutRef.current = setTimeout(() => {
185
+ stopBluetoothScan();
186
+ filterStaleDevices();
187
+
188
+ if (nearbyDevices.length === 0) {
189
+ notifyMessage?.('No nearby Bluetooth devices found', 'info');
190
+ } else {
191
+ console.log(`[BLE] Found ${nearbyDevices.length} nearby devices`);
192
+ }
193
+ }, 10000);
194
+
195
+ notifyMessage?.('Scanning for nearby Bluetooth devices...', 'info');
196
+ }, [notifyMessage, requestBluetoothPermissions, stopBluetoothScan, filterStaleDevices, estimateDistance]);
197
+
198
+ /**
199
+ * Get device details by ID
200
+ */
201
+ const getDeviceDetails = useCallback(async (deviceId) => {
202
+ try {
203
+ const device = await manager.connectToDevice(deviceId);
204
+ await device.discoverAllServicesAndCharacteristics();
205
+ const services = await device.services();
206
+
207
+ return {
208
+ ...device,
209
+ services,
210
+ isConnected: true,
211
+ };
212
+ } catch (error) {
213
+ console.error('[BLE] Error getting device details:', error);
214
+ return null;
215
+ }
216
+ }, []);
217
+
218
+ /**
219
+ * Clear all scanned devices
220
+ */
221
+ const clearDevices = useCallback(() => {
222
+ setNearbyDevices([]);
223
+ }, []);
224
+
225
+ return {
226
+ requestBluetoothPermission: requestBluetoothPermissions,
227
+ startBluetoothScan,
228
+ stopBluetoothScan,
229
+ nearbyDevices,
230
+ filterStaleDevices,
231
+ clearDevices,
232
+ getDeviceDetails,
233
+ estimateDistance,
234
+ };
235
+ };
@@ -1,32 +1,24 @@
1
- // Enhanced location service
2
- import { useCallback, useState, useRef } from 'react';
1
+ import { useCallback, useRef } from 'react';
3
2
  import { Platform, PermissionsAndroid } from 'react-native';
4
3
  import Geolocation from 'react-native-geolocation-service';
5
- import { BleManager } from 'react-native-ble-plx';
6
-
7
- const manager = new BleManager();
8
4
 
9
5
  /**
10
6
  * Enhanced Geolocation Hook with better accuracy
11
7
  */
12
8
  export const useGeolocation = (notifyMessage) => {
13
- const [nearbyDevices, setNearbyDevices] = useState([]);
14
- const scanTimeoutRef = useRef(null);
15
9
  const locationWatchId = useRef(null);
16
10
 
17
11
  /**
18
- * Request high-accuracy permissions
12
+ * Request high-accuracy location permissions
19
13
  */
20
- const requestPermissions = useCallback(async () => {
21
- console.log('[Permissions] Requesting enhanced permissions...');
14
+ const requestLocationPermissions = useCallback(async () => {
15
+ console.log('[Permissions] Requesting enhanced location permissions...');
22
16
 
23
17
  if (Platform.OS === 'android') {
24
18
  try {
25
19
  const granted = await PermissionsAndroid.requestMultiple([
26
20
  PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
27
21
  PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
28
- PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
29
- PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
30
22
  ]);
31
23
 
32
24
  // Check Android version for background location
@@ -36,16 +28,23 @@ export const useGeolocation = (notifyMessage) => {
36
28
  );
37
29
  }
38
30
 
39
- return Object.values(granted).every(
31
+ const allGranted = Object.values(granted).every(
40
32
  status => status === PermissionsAndroid.RESULTS.GRANTED
41
33
  );
34
+
35
+ if (!allGranted) {
36
+ notifyMessage?.('Location permissions are required for accurate positioning', 'warning');
37
+ }
38
+
39
+ return allGranted;
42
40
  } catch (error) {
43
41
  console.error('[Permissions] Error:', error);
42
+ notifyMessage?.('Failed to request location permissions', 'error');
44
43
  return false;
45
44
  }
46
45
  }
47
46
  return true; // iOS handles permissions differently
48
- }, []);
47
+ }, [notifyMessage]);
49
48
 
50
49
  /**
51
50
  * Get high-accuracy location with multiple attempts
@@ -56,8 +55,6 @@ export const useGeolocation = (notifyMessage) => {
56
55
  console.log('[Location] Fetching enhanced location...');
57
56
 
58
57
  let bestLocation = null;
59
- let attempts = 0;
60
- const maxAttempts = 3;
61
58
 
62
59
  const locationOptions = {
63
60
  enableHighAccuracy: true,
@@ -138,130 +135,39 @@ export const useGeolocation = (notifyMessage) => {
138
135
  resolve(bestLocation);
139
136
  } catch (error) {
140
137
  console.error('[Location] Error:', error);
141
- reject(error);
138
+
139
+ // Provide user-friendly error messages
140
+ let errorMessage = 'Failed to get location';
141
+ switch (error.code) {
142
+ case 1:
143
+ errorMessage = 'Location permission denied';
144
+ break;
145
+ case 2:
146
+ errorMessage = 'Location unavailable';
147
+ break;
148
+ case 3:
149
+ errorMessage = 'Location request timed out';
150
+ break;
151
+ }
152
+
153
+ notifyMessage?.(errorMessage, 'error');
154
+ reject(new Error(errorMessage));
142
155
  }
143
156
  }),
144
157
  [notifyMessage]
145
158
  );
146
159
 
147
160
  /**
148
- * Stop Bluetooth scan
161
+ * Stop location watching
149
162
  */
150
- const stopBluetoothScan = useCallback(() => {
151
- console.log('[BLE] Stopping Bluetooth scan...');
152
- manager.stopDeviceScan();
153
- if (scanTimeoutRef.current) {
154
- clearTimeout(scanTimeoutRef.current);
155
- scanTimeoutRef.current = null;
163
+ const stopLocationWatching = useCallback(() => {
164
+ if (locationWatchId.current !== null) {
165
+ console.log('[Location] Stopping location watch...');
166
+ Geolocation.clearWatch(locationWatchId.current);
167
+ locationWatchId.current = null;
156
168
  }
157
169
  }, []);
158
170
 
159
- /**
160
- * Enhanced BLE scanning with filtering
161
- */
162
- const startBluetoothScan = useCallback(async () => {
163
- console.log('[BLE] Starting enhanced Bluetooth scan...');
164
- const permission = await requestPermissions();
165
- if (!permission) {
166
- notifyMessage?.('Permissions required for Bluetooth scanning', 'error');
167
- return;
168
- }
169
-
170
- setNearbyDevices([]);
171
-
172
- // Configure scan for better accuracy
173
- const scanOptions = {
174
- allowDuplicates: true,
175
- scanMode: 2, // SCAN_MODE_LOW_LATENCY for Android
176
- };
177
-
178
- manager.startDeviceScan(null, scanOptions, (error, device) => {
179
- if (error) {
180
- console.error('[BLE] Scan error:', error);
181
- stopBluetoothScan();
182
- return;
183
- }
184
-
185
- if (device && device.name && device.rssi) {
186
- const distance = estimateDistance(device.rssi);
187
-
188
- // Filter out weak signals and calculate moving average
189
- if (distance > 0 && distance <= 20) {
190
- setNearbyDevices(prev => {
191
- const existingIndex = prev.findIndex(d => d.id === device.id);
192
-
193
- if (existingIndex >= 0) {
194
- // Update existing device with moving average
195
- const existing = prev[existingIndex];
196
- const avgDistance = (parseFloat(existing.distance) + distance) / 2;
197
-
198
- const updated = [...prev];
199
- updated[existingIndex] = {
200
- ...existing,
201
- rssi: device.rssi,
202
- distance: avgDistance.toFixed(2),
203
- lastSeen: Date.now(),
204
- count: existing.count + 1,
205
- };
206
- return updated;
207
- } else {
208
- // Add new device
209
- const newDevice = {
210
- id: device.id,
211
- name: device.name,
212
- rssi: device.rssi,
213
- distance: distance.toFixed(2),
214
- lastSeen: Date.now(),
215
- count: 1,
216
- manufacturerData: device.manufacturerData,
217
- serviceUUIDs: device.serviceUUIDs,
218
- };
219
- return [...prev, newDevice];
220
- }
221
- });
222
- }
223
- }
224
- });
225
-
226
- // Stop after optimized duration
227
- scanTimeoutRef.current = setTimeout(() => {
228
- stopBluetoothScan();
229
- filterStaleDevices();
230
- }, 10000);
231
- }, [notifyMessage, requestPermissions, stopBluetoothScan, filterStaleDevices]);
232
-
233
- /**
234
- * Filter out stale BLE devices
235
- */
236
- const filterStaleDevices = useCallback(() => {
237
- setNearbyDevices(prev =>
238
- prev.filter(device => {
239
- const isRecent = Date.now() - device.lastSeen < 10000; // 10 seconds
240
- const hasMultipleReadings = device.count >= 3;
241
- return isRecent && hasMultipleReadings;
242
- })
243
- );
244
- }, []);
245
-
246
- /**
247
- * Enhanced distance calculation using multiple methods
248
- */
249
- const estimateDistance = useCallback((rssi, txPower = -59) => {
250
- if (!rssi || rssi >= 0) return -1;
251
-
252
- // Method 1: Log-distance path loss model (most common)
253
- const n = 2.0; // Path loss exponent (2 for free space, 2.7-3.5 for indoor)
254
- const distance1 = Math.pow(10, (txPower - rssi) / (10 * n));
255
-
256
- // Method 2: Quadratic approximation for short distances
257
- const distance2 = 0.89976 * Math.pow(Math.abs(rssi), 0.80976) + 0.111;
258
-
259
- // Average the methods for better accuracy
260
- const avgDistance = (distance1 + distance2) / 2;
261
-
262
- return Math.max(0.1, avgDistance); // Minimum 0.1m
263
- }, []);
264
-
265
171
  /**
266
172
  * Calculate distance between coordinates using Haversine formula with altitude
267
173
  */
@@ -288,12 +194,9 @@ export const useGeolocation = (notifyMessage) => {
288
194
  }, []);
289
195
 
290
196
  return {
291
- requestLocationPermission: requestPermissions,
197
+ requestLocationPermission: requestLocationPermissions,
292
198
  getCurrentLocation,
293
- startBluetoothScan,
294
- stopBluetoothScan,
295
- nearbyDevices,
199
+ stopLocationWatching,
296
200
  calculateEnhancedDistance,
297
- filterStaleDevices,
298
201
  };
299
202
  };
package/src/index.js CHANGED
@@ -17,15 +17,16 @@ import {
17
17
  Animated,
18
18
  } from "react-native";
19
19
  import Icon from "react-native-vector-icons/MaterialIcons";
20
+
20
21
  // Custom hooks
21
22
  import { useCountdown } from "./hooks/useCountdown";
22
23
  import { useGeolocation } from "./hooks/useGeolocation";
24
+ import { useBluetoothService } from "./hooks/useBluetoothService";
23
25
  import { useImageProcessing } from "./hooks/useImageProcessing";
24
26
  import { useNotifyMessage } from "./hooks/useNotifyMessage";
25
27
  import { useSafeCallback } from "./hooks/useSafeCallback";
26
28
 
27
29
  // Utils
28
- import { getDistanceInMeters } from "./utils/distanceCalculator";
29
30
  import { Global } from "./utils/Global";
30
31
  import networkServiceCall from "./utils/NetworkServiceCall";
31
32
  import { getLoaderGif } from "./utils/getLoaderGif";
@@ -56,13 +57,23 @@ const BiometricModal = forwardRef(({
56
57
  // Custom hooks - Initialize notification hook first so notifyMessage is available for other hooks
57
58
  const { notification, fadeAnim, slideAnim, notifyMessage, clearNotification } = useNotifyMessage();
58
59
  const { countdown, startCountdown, resetCountdown, pauseCountdown, resumeCountdown } = useCountdown(duration);
60
+
61
+ // Separate hooks for geolocation and bluetooth
59
62
  const {
60
63
  requestLocationPermission,
61
64
  getCurrentLocation,
65
+ stopLocationWatching,
66
+ calculateEnhancedDistance,
67
+ } = useGeolocation(notifyMessage);
68
+
69
+ const {
70
+ requestBluetoothPermission,
62
71
  startBluetoothScan,
63
72
  stopBluetoothScan,
64
73
  nearbyDevices,
65
- } = useGeolocation(notifyMessage);
74
+ clearDevices,
75
+ } = useBluetoothService(notifyMessage);
76
+
66
77
  const { convertImageToBase64 } = useImageProcessing();
67
78
  const safeCallback = useSafeCallback(callback, notifyMessage);
68
79
 
@@ -84,6 +95,8 @@ const BiometricModal = forwardRef(({
84
95
  const responseRef = useRef(null);
85
96
  const processedRef = useRef(false);
86
97
  const resetTimeoutRef = useRef(null);
98
+ const bleScanTimeoutRef = useRef(null);
99
+
87
100
  // Animation values
88
101
  const iconScaleAnim = useRef(new Animated.Value(1)).current;
89
102
  const iconOpacityAnim = useRef(new Animated.Value(0)).current;
@@ -104,10 +117,17 @@ const BiometricModal = forwardRef(({
104
117
  if (resetTimeoutRef.current) {
105
118
  clearTimeout(resetTimeoutRef.current);
106
119
  }
107
-
120
+
121
+ if (bleScanTimeoutRef.current) {
122
+ clearTimeout(bleScanTimeoutRef.current);
123
+ }
124
+
125
+ stopBluetoothScan();
126
+ stopLocationWatching();
127
+ clearDevices();
108
128
  clearNotification();
109
129
  };
110
- }, []);
130
+ }, [stopBluetoothScan, stopLocationWatching, clearDevices, clearNotification]);
111
131
 
112
132
  // Update dataRef when data changes
113
133
  useEffect(() => {
@@ -189,13 +209,16 @@ const BiometricModal = forwardRef(({
189
209
  setModalVisible(false);
190
210
  processedRef.current = false;
191
211
  resetCountdown();
212
+ stopBluetoothScan();
213
+ stopLocationWatching();
214
+ clearDevices();
192
215
  clearNotification();
193
216
 
194
217
  if (resetTimeoutRef.current) {
195
218
  clearTimeout(resetTimeoutRef.current);
196
219
  resetTimeoutRef.current = null;
197
220
  }
198
- }, [resetCountdown, clearNotification, onclose]);
221
+ }, [resetCountdown, stopBluetoothScan, stopLocationWatching, clearDevices, clearNotification, onclose]);
199
222
 
200
223
  // Error handler
201
224
  const handleProcessError = useCallback(
@@ -241,6 +264,47 @@ const BiometricModal = forwardRef(({
241
264
  return true;
242
265
  }, [apiurl, handleProcessError]);
243
266
 
267
+ /**
268
+ * Find consistent BLE devices across multiple samples
269
+ */
270
+ const findConsistentBLEDevices = useCallback((samples) => {
271
+ const deviceMap = new Map();
272
+
273
+ samples.forEach((sample, sampleIndex) => {
274
+ sample.forEach(device => {
275
+ if (!deviceMap.has(device.id)) {
276
+ deviceMap.set(device.id, {
277
+ ...device,
278
+ distances: [],
279
+ samples: 0
280
+ });
281
+ }
282
+ const entry = deviceMap.get(device.id);
283
+ entry.distances.push(parseFloat(device.distance));
284
+ entry.samples++;
285
+ });
286
+ });
287
+
288
+ // Filter for devices seen in at least 2 samples
289
+ return Array.from(deviceMap.values())
290
+ .filter(device => device.samples >= 2)
291
+ .map(device => ({
292
+ ...device,
293
+ avgDistance: device.distances.reduce((a, b) => a + b, 0) / device.distances.length,
294
+ stdDev: calculateStdDev(device.distances)
295
+ }))
296
+ .filter(device => device.stdDev < 2); // Filter out inconsistent readings
297
+ }, []);
298
+
299
+ /**
300
+ * Calculate standard deviation
301
+ */
302
+ const calculateStdDev = (array) => {
303
+ const n = array.length;
304
+ const mean = array.reduce((a, b) => a + b) / n;
305
+ return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
306
+ };
307
+
244
308
  // QR code processing - FIRST STEP
245
309
  // Enhanced QR processing
246
310
  const handleQRScanned = useCallback(
@@ -256,12 +320,18 @@ const BiometricModal = forwardRef(({
256
320
  try {
257
321
  // 1. Request permissions
258
322
  updateState({ loadingType: Global.LoadingTypes.locationPermission });
259
- const hasPermission = await requestLocationPermission();
260
- if (!hasPermission) {
323
+ const hasLocationPermission = await requestLocationPermission();
324
+ if (!hasLocationPermission) {
261
325
  handleProcessError("Location permission not granted.");
262
326
  return;
263
327
  }
264
328
 
329
+ const hasBluetoothPermission = await requestBluetoothPermission();
330
+ if (!hasBluetoothPermission) {
331
+ notifyMessage("Bluetooth scanning may not work", "warning");
332
+ // Continue anyway, Bluetooth is optional
333
+ }
334
+
265
335
  // 2. Parse QR data with validation
266
336
  const qrString = typeof qrCodeData === "object" ? qrCodeData?.data : qrCodeData;
267
337
  if (!qrString || typeof qrString !== "string") {
@@ -303,41 +373,48 @@ const BiometricModal = forwardRef(({
303
373
 
304
374
  // 4. Enhanced BLE scanning (optional - can be removed if not needed)
305
375
  updateState({ loadingType: Global.LoadingTypes.bleScan });
306
- startBluetoothScan();
307
-
308
- // Collect BLE data with multiple samples
309
- const bleSamples = [];
310
- for (let i = 0; i < 3; i++) {
311
- await new Promise(resolve => setTimeout(resolve, 2000));
312
- bleSamples.push([...nearbyDevices]);
376
+
377
+ // Start BLE scan and collect samples
378
+ let consistentDevices = [];
379
+ let isBLENearby = false;
380
+
381
+ try {
382
+ startBluetoothScan();
383
+
384
+ // Collect BLE data with multiple samples
385
+ const bleSamples = [];
386
+ for (let i = 0; i < 3; i++) {
387
+ await new Promise(resolve => setTimeout(resolve, 2000));
388
+ bleSamples.push([...nearbyDevices]);
389
+ }
390
+
391
+ consistentDevices = findConsistentBLEDevices(bleSamples);
392
+ isBLENearby = consistentDevices.length > 0 && consistentDevices.some(d => d.avgDistance <= 5);
393
+ } catch (bleError) {
394
+ console.warn("BLE scanning failed:", bleError);
395
+ // Continue without BLE data
396
+ } finally {
397
+ stopBluetoothScan();
313
398
  }
314
399
 
315
- stopBluetoothScan();
316
-
317
- // Process BLE data to find consistent nearby devices
318
- const consistentDevices = findConsistentBLEDevices(bleSamples);
319
- const isBLENearby = consistentDevices.length > 0 && consistentDevices.some(d => d.avgDistance <= 5);
320
-
321
400
  // 5. Calculate distance (no altitude in your QR code)
322
401
  updateState({ loadingType: Global.LoadingTypes.calculateDistance });
323
402
 
324
- // Simple 2D distance calculation (since no altitude in QR)
403
+ // Use the enhanced distance calculation from geolocation hook
325
404
  const distance = calculateEnhancedDistance(
326
405
  qrLat, qrLng,
327
406
  location.latitude, location.longitude
328
407
  );
329
408
 
330
- // 6. Enhanced verification with accuracy buffer
331
- const maxAllowedDistance = MaxDistanceMeters + Math.max(location.accuracy, qrAccuracy);
332
- const distanceWithinThreshold = distance <= maxAllowedDistance;
333
-
334
- // Verify: distance within threshold AND BLE devices nearby AND depkey matches
335
- if (distanceWithinThreshold && isBLENearby && qrDepKey === depkey) {
409
+ const distanceWithinThreshold = distance <= MaxDistanceMeters;
410
+ console.log('distanceWithinThreshold', distance, MaxDistanceMeters)
411
+
412
+ // Verify: distance within threshold AND depkey matches (BLE is optional)
413
+ if (distanceWithinThreshold && qrDepKey === depkey) {
336
414
  const locationDetails = {
337
415
  qrLocation: {
338
416
  latitude: qrLat,
339
417
  longitude: qrLng,
340
- // No altitude in your QR code format
341
418
  accuracy: qrAccuracy
342
419
  },
343
420
  deviceLocation: {
@@ -350,7 +427,7 @@ const BiometricModal = forwardRef(({
350
427
  timestamp: location.timestamp
351
428
  },
352
429
  distanceMeters: distance,
353
- accuracyBuffer: maxAllowedDistance,
430
+ accuracyBuffer: MaxDistanceMeters,
354
431
  verified: true,
355
432
  verifiedAt: new Date().toISOString(),
356
433
  bleDevicesDetected: consistentDevices,
@@ -372,7 +449,6 @@ const BiometricModal = forwardRef(({
372
449
  setTimeout(() => startFaceRecognition(), 1200);
373
450
  } else {
374
451
  let errorMsg = `Location mismatch: ${distance.toFixed(1)}m away`;
375
- if (!isBLENearby) errorMsg += " (No nearby BLE devices)";
376
452
  if (qrDepKey !== depkey) errorMsg += " (Key mismatch)";
377
453
  handleProcessError(errorMsg);
378
454
  }
@@ -385,75 +461,21 @@ const BiometricModal = forwardRef(({
385
461
  validateApiUrl,
386
462
  updateState,
387
463
  requestLocationPermission,
464
+ requestBluetoothPermission,
388
465
  getCurrentLocation,
389
466
  startBluetoothScan,
390
467
  stopBluetoothScan,
391
468
  nearbyDevices,
469
+ findConsistentBLEDevices,
392
470
  notifyMessage,
393
471
  handleProcessError,
394
472
  startFaceRecognition,
395
473
  depkey,
396
474
  MaxDistanceMeters,
475
+ calculateEnhancedDistance,
397
476
  ]
398
477
  );
399
478
 
400
- /**
401
- * Find consistent BLE devices across multiple samples
402
- */
403
- const findConsistentBLEDevices = (samples) => {
404
- const deviceMap = new Map();
405
-
406
- samples.forEach((sample, sampleIndex) => {
407
- sample.forEach(device => {
408
- if (!deviceMap.has(device.id)) {
409
- deviceMap.set(device.id, {
410
- ...device,
411
- distances: [],
412
- samples: 0
413
- });
414
- }
415
- const entry = deviceMap.get(device.id);
416
- entry.distances.push(parseFloat(device.distance));
417
- entry.samples++;
418
- });
419
- });
420
-
421
- // Filter for devices seen in at least 2 samples
422
- return Array.from(deviceMap.values())
423
- .filter(device => device.samples >= 2)
424
- .map(device => ({
425
- ...device,
426
- avgDistance: device.distances.reduce((a, b) => a + b, 0) / device.distances.length,
427
- stdDev: calculateStdDev(device.distances)
428
- }))
429
- .filter(device => device.stdDev < 2); // Filter out inconsistent readings
430
- };
431
-
432
- /**
433
- * Calculate standard deviation
434
- */
435
- const calculateStdDev = (array) => {
436
- const n = array.length;
437
- const mean = array.reduce((a, b) => a + b) / n;
438
- return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
439
- };
440
-
441
- /**
442
- * Calculate enhanced distance with altitude
443
- */
444
- const calculateEnhancedDistance = (lat1, lng1, lat2, lng2, alt1 = 0, alt2 = 0) => {
445
- const R = 6371000; // Earth's radius in meters
446
- const dLat = (lat2 - lat1) * Math.PI / 180;
447
- const dLng = (lng2 - lng1) * Math.PI / 180;
448
- const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
449
- Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
450
- Math.sin(dLng / 2) * Math.sin(dLng / 2);
451
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
452
- const horizontalDistance = R * c;
453
- const verticalDistance = Math.abs(alt2 - alt1);
454
- return Math.sqrt(horizontalDistance * horizontalDistance + verticalDistance * verticalDistance);
455
- };
456
-
457
479
  // Face scan upload - SECOND STEP
458
480
  const uploadFaceScan = useCallback(
459
481
  async (selfie) => {
@@ -703,8 +725,7 @@ const BiometricModal = forwardRef(({
703
725
  </View>
704
726
  </Modal>
705
727
  );
706
- }
707
- );
728
+ });
708
729
 
709
730
  // Wrap with memo after forwardRef
710
731
  const MemoizedBiometricModal = React.memo(BiometricModal);