react-native-biometric-verifier 0.0.47 → 0.0.49

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.47",
3
+ "version": "0.0.49",
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,
@@ -11,74 +11,60 @@ export const useBluetoothService = (notifyMessage) => {
11
11
  const [nearbyDevices, setNearbyDevices] = useState([]);
12
12
  const scanTimeoutRef = useRef(null);
13
13
 
14
- /**
15
- * Request Bluetooth permissions
16
- */
14
+ /* -------------------------------------------------------------------------- */
15
+ /* BLUETOOTH PERMISSIONS */
16
+ /* -------------------------------------------------------------------------- */
17
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
- }
18
+ if (Platform.OS !== 'android') return true;
19
+
20
+ try {
21
+ let permissions = [];
22
+ if (Platform.Version >= 31) {
23
+ permissions = [
24
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
25
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
26
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
27
+ ];
28
+ } else {
29
+ permissions = [PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION];
30
+ }
37
31
 
38
- const granted = await PermissionsAndroid.requestMultiple(permissions);
32
+ const granted = await PermissionsAndroid.requestMultiple(permissions);
33
+ const allGranted = Object.values(granted).every(
34
+ status => status === PermissionsAndroid.RESULTS.GRANTED
35
+ );
39
36
 
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;
37
+ if (!allGranted) {
38
+ notifyMessage?.('Bluetooth permissions are required for device scanning', 'warning');
53
39
  }
40
+
41
+ return allGranted;
42
+ } catch (error) {
43
+ console.error('[Permissions] Error:', error);
44
+ notifyMessage?.('Failed to request Bluetooth permissions', 'error');
45
+ return false;
54
46
  }
55
- return true; // iOS handles permissions differently
56
47
  }, [notifyMessage]);
57
48
 
58
- /**
59
- * Enhanced distance calculation using multiple methods
60
- */
49
+ /* -------------------------------------------------------------------------- */
50
+ /* DISTANCE ESTIMATION */
51
+ /* -------------------------------------------------------------------------- */
61
52
  const estimateDistance = useCallback((rssi, txPower = -59) => {
62
53
  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)
54
+
55
+ const n = 2.0;
66
56
  const distance1 = Math.pow(10, (txPower - rssi) / (10 * n));
67
-
68
- // Method 2: Quadratic approximation for short distances
69
57
  const distance2 = 0.89976 * Math.pow(Math.abs(rssi), 0.80976) + 0.111;
70
-
71
- // Average the methods for better accuracy
72
58
  const avgDistance = (distance1 + distance2) / 2;
73
-
74
- return Math.max(0.1, avgDistance); // Minimum 0.1m
59
+
60
+ return Math.max(0.1, avgDistance);
75
61
  }, []);
76
62
 
77
- /**
78
- * Filter out stale BLE devices
79
- */
63
+ /* -------------------------------------------------------------------------- */
64
+ /* FILTER STALE DEVICES */
65
+ /* -------------------------------------------------------------------------- */
80
66
  const filterStaleDevices = useCallback(() => {
81
- setNearbyDevices(prev =>
67
+ setNearbyDevices(prev =>
82
68
  prev.filter(device => {
83
69
  const isRecent = Date.now() - device.lastSeen < 10000; // 10 seconds
84
70
  const hasMultipleReadings = device.count >= 3;
@@ -87,11 +73,10 @@ export const useBluetoothService = (notifyMessage) => {
87
73
  );
88
74
  }, []);
89
75
 
90
- /**
91
- * Stop Bluetooth scan
92
- */
76
+ /* -------------------------------------------------------------------------- */
77
+ /* STOP SCAN */
78
+ /* -------------------------------------------------------------------------- */
93
79
  const stopBluetoothScan = useCallback(() => {
94
- console.log('[BLE] Stopping Bluetooth scan...');
95
80
  manager.stopDeviceScan();
96
81
  if (scanTimeoutRef.current) {
97
82
  clearTimeout(scanTimeoutRef.current);
@@ -99,56 +84,43 @@ export const useBluetoothService = (notifyMessage) => {
99
84
  }
100
85
  }, []);
101
86
 
102
- /**
103
- * Enhanced BLE scanning with filtering and signal processing
104
- */
87
+ /* -------------------------------------------------------------------------- */
88
+ /* START BLE SCAN */
89
+ /* -------------------------------------------------------------------------- */
105
90
  const startBluetoothScan = useCallback(async () => {
106
- console.log('[BLE] Starting enhanced Bluetooth scan...');
107
-
108
91
  const permission = await requestBluetoothPermissions();
109
- if (!permission) {
110
- return;
111
- }
92
+ if (!permission) return;
112
93
 
113
- // Clear previous devices
114
94
  setNearbyDevices([]);
115
-
116
- // Configure scan for better accuracy
95
+
117
96
  const scanOptions = {
118
97
  allowDuplicates: true,
119
- scanMode: 2, // SCAN_MODE_LOW_LATENCY for Android
98
+ scanMode: 2,
120
99
  };
121
100
 
122
101
  manager.startDeviceScan(null, scanOptions, (error, device) => {
123
102
  if (error) {
124
103
  console.error('[BLE] Scan error:', error);
125
104
  stopBluetoothScan();
126
-
127
- // Provide user-friendly error messages
105
+
128
106
  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
-
107
+ if (error.errorCode === 102) errorMessage = 'Bluetooth is not enabled';
108
+ else if (error.errorCode === 103) errorMessage = 'Location services required for scanning';
109
+
135
110
  notifyMessage?.(errorMessage, 'error');
136
111
  return;
137
112
  }
138
113
 
139
114
  if (device && device.name && device.rssi) {
140
115
  const distance = estimateDistance(device.rssi);
141
-
142
- // Filter out weak signals and calculate moving average
116
+
143
117
  if (distance > 0 && distance <= 20) {
144
118
  setNearbyDevices(prev => {
145
119
  const existingIndex = prev.findIndex(d => d.id === device.id);
146
-
147
120
  if (existingIndex >= 0) {
148
- // Update existing device with moving average
149
121
  const existing = prev[existingIndex];
150
122
  const avgDistance = (parseFloat(existing.distance) + distance) / 2;
151
-
123
+
152
124
  const updated = [...prev];
153
125
  updated[existingIndex] = {
154
126
  ...existing,
@@ -160,64 +132,52 @@ export const useBluetoothService = (notifyMessage) => {
160
132
  };
161
133
  return updated;
162
134
  } 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];
135
+ return [
136
+ ...prev,
137
+ {
138
+ id: device.id,
139
+ name: device.name || 'Unknown Device',
140
+ rssi: device.rssi,
141
+ distance: distance.toFixed(2),
142
+ lastSeen: Date.now(),
143
+ count: 1,
144
+ manufacturerData: device.manufacturerData,
145
+ serviceUUIDs: device.serviceUUIDs,
146
+ txPowerLevel: device.txPowerLevel,
147
+ isConnectable: device.isConnectable,
148
+ },
149
+ ];
177
150
  }
178
151
  });
179
152
  }
180
153
  }
181
154
  });
182
155
 
183
- // Stop after optimized duration
184
156
  scanTimeoutRef.current = setTimeout(() => {
185
157
  stopBluetoothScan();
186
158
  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
159
  }, 10000);
160
+ }, [notifyMessage, requestBluetoothPermissions, stopBluetoothScan, filterStaleDevices, estimateDistance, nearbyDevices]);
194
161
 
195
- notifyMessage?.('Scanning for nearby Bluetooth devices...', 'info');
196
- }, [notifyMessage, requestBluetoothPermissions, stopBluetoothScan, filterStaleDevices, estimateDistance]);
197
-
198
- /**
199
- * Get device details by ID
200
- */
162
+ /* -------------------------------------------------------------------------- */
163
+ /* DEVICE DETAILS */
164
+ /* -------------------------------------------------------------------------- */
201
165
  const getDeviceDetails = useCallback(async (deviceId) => {
202
166
  try {
203
167
  const device = await manager.connectToDevice(deviceId);
204
168
  await device.discoverAllServicesAndCharacteristics();
205
169
  const services = await device.services();
206
-
207
- return {
208
- ...device,
209
- services,
210
- isConnected: true,
211
- };
170
+
171
+ return { ...device, services, isConnected: true };
212
172
  } catch (error) {
213
173
  console.error('[BLE] Error getting device details:', error);
214
174
  return null;
215
175
  }
216
176
  }, []);
217
177
 
218
- /**
219
- * Clear all scanned devices
220
- */
178
+ /* -------------------------------------------------------------------------- */
179
+ /* CLEAR DEVICES */
180
+ /* -------------------------------------------------------------------------- */
221
181
  const clearDevices = useCallback(() => {
222
182
  setNearbyDevices([]);
223
183
  }, []);
@@ -232,4 +192,4 @@ export const useBluetoothService = (notifyMessage) => {
232
192
  getDeviceDetails,
233
193
  estimateDistance,
234
194
  };
235
- };
195
+ };
@@ -3,200 +3,251 @@ import { Platform, PermissionsAndroid } from 'react-native';
3
3
  import Geolocation from 'react-native-geolocation-service';
4
4
 
5
5
  /**
6
- * Enhanced Geolocation Hook with better accuracy
6
+ * FAST + ACCURATE Geolocation Hook (Optimized for Verification)
7
7
  */
8
8
  export const useGeolocation = (notifyMessage) => {
9
9
  const locationWatchId = useRef(null);
10
10
 
11
- /**
12
- * Request high-accuracy location permissions
13
- */
14
- const requestLocationPermissions = useCallback(async () => {
15
- console.log('[Permissions] Requesting enhanced location permissions...');
16
-
17
- if (Platform.OS === 'android') {
18
- try {
19
- const granted = await PermissionsAndroid.requestMultiple([
20
- PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
21
- PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
22
- ]);
23
-
24
- // Check Android version for background location
25
- if (Platform.Version >= 29) {
26
- await PermissionsAndroid.request(
27
- PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
28
- );
29
- }
30
-
31
- const allGranted = Object.values(granted).every(
32
- status => status === PermissionsAndroid.RESULTS.GRANTED
33
- );
34
-
35
- if (!allGranted) {
36
- notifyMessage?.('Location permissions are required for accurate positioning', 'warning');
37
- }
38
-
39
- return allGranted;
40
- } catch (error) {
41
- console.error('[Permissions] Error:', error);
42
- notifyMessage?.('Failed to request location permissions', 'error');
43
- return false;
11
+ /* -------------------------------------------------------------------------- */
12
+ /* PERMISSIONS */
13
+ /* -------------------------------------------------------------------------- */
14
+ const requestLocationPermission = useCallback(async () => {
15
+ try {
16
+ if (Platform.OS !== 'android') return true;
17
+
18
+ const permissions = [
19
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
20
+ PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
21
+ ];
22
+
23
+ if (Platform.Version >= 31) {
24
+ permissions.push(PermissionsAndroid.PERMISSIONS.NEARBY_WIFI_DEVICES);
44
25
  }
45
- }
46
- return true; // iOS handles permissions differently
47
- }, [notifyMessage]);
48
26
 
49
- /**
50
- * Get high-accuracy location with multiple attempts
51
- */
52
- const getCurrentLocation = useCallback(
53
- () =>
54
- new Promise(async (resolve, reject) => {
55
- console.log('[Location] Fetching enhanced location...');
56
-
57
- let bestLocation = null;
58
-
59
- const locationOptions = {
60
- enableHighAccuracy: true,
61
- timeout: 20000,
62
- maximumAge: 0,
63
- distanceFilter: 0,
64
- forceRequestLocation: true,
65
- showLocationDialog: true,
66
- };
67
-
68
- const watchLocationForAccuracy = () => {
69
- return new Promise((resolveWatch, rejectWatch) => {
70
- let bestAccuracy = Infinity;
71
- let bestCoords = null;
72
- let watchTimer;
73
-
74
- locationWatchId.current = Geolocation.watchPosition(
75
- (position) => {
76
- const { coords } = position;
77
-
78
- // Track best accuracy
79
- if (coords.accuracy < bestAccuracy) {
80
- bestAccuracy = coords.accuracy;
81
- bestCoords = coords;
82
- console.log(`[Location] Improved accuracy: ${coords.accuracy}m`);
83
- }
84
-
85
- // Stop if we reach desired accuracy
86
- if (coords.accuracy <= 5) {
87
- clearTimeout(watchTimer);
88
- Geolocation.clearWatch(locationWatchId.current);
89
- resolveWatch(coords);
90
- }
91
- },
92
- (error) => {
93
- clearTimeout(watchTimer);
94
- Geolocation.clearWatch(locationWatchId.current);
95
- rejectWatch(error);
96
- },
97
- locationOptions
98
- );
99
-
100
- // Timeout after 15 seconds
101
- watchTimer = setTimeout(() => {
102
- Geolocation.clearWatch(locationWatchId.current);
103
- resolveWatch(bestCoords);
104
- }, 15000);
105
- });
106
- };
107
-
108
- try {
109
- // First try: Single high-accuracy reading
110
- const singleLocation = await new Promise((resolveSingle, rejectSingle) => {
111
- Geolocation.getCurrentPosition(
112
- resolveSingle,
113
- rejectSingle,
114
- locationOptions
115
- );
116
- });
117
-
118
- bestLocation = singleLocation.coords;
119
- console.log(`[Location] Initial accuracy: ${bestLocation.accuracy}m`);
120
-
121
- // If accuracy > 20m, try watching for improvement
122
- if (bestLocation.accuracy > 20) {
123
- console.log('[Location] Accuracy insufficient, starting watch...');
124
- const watchedCoords = await watchLocationForAccuracy();
125
- if (watchedCoords && watchedCoords.accuracy < bestLocation.accuracy) {
126
- bestLocation = watchedCoords;
127
- }
128
- }
27
+ const result = await PermissionsAndroid.requestMultiple(permissions);
129
28
 
130
- // Validate location quality
131
- if (!bestLocation || bestLocation.accuracy > 50) {
132
- notifyMessage?.('Location accuracy is low. Please move to an open area.', 'warning');
133
- }
29
+ const granted = Object.values(result).every(
30
+ status => status === PermissionsAndroid.RESULTS.GRANTED
31
+ );
134
32
 
135
- resolve(bestLocation);
136
- } catch (error) {
137
- console.error('[Location] Error:', 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));
155
- }
156
- }),
157
- [notifyMessage]
158
- );
33
+ if (!granted) {
34
+ notifyMessage?.('Location permissions missing', 'warning');
35
+ }
36
+
37
+ return granted;
38
+ } catch (err) {
39
+ console.error('[Geo Permissions]', err);
40
+ return false;
41
+ }
42
+ }, [notifyMessage]);
159
43
 
160
- /**
161
- * Stop location watching
162
- */
44
+ /* -------------------------------------------------------------------------- */
45
+ /* STOP LOCATION WATCH */
46
+ /* -------------------------------------------------------------------------- */
163
47
  const stopLocationWatching = useCallback(() => {
164
48
  if (locationWatchId.current !== null) {
165
- console.log('[Location] Stopping location watch...');
166
49
  Geolocation.clearWatch(locationWatchId.current);
167
50
  locationWatchId.current = null;
168
51
  }
169
52
  }, []);
170
53
 
171
- /**
172
- * Calculate distance between coordinates using Haversine formula with altitude
173
- */
174
- const calculateEnhancedDistance = useCallback((lat1, lon1, lat2, lon2, alt1 = 0, alt2 = 0) => {
175
- const R = 6371e3; // Earth's radius in meters
176
-
177
- const φ1 = lat1 * Math.PI/180;
178
- const φ2 = lat2 * Math.PI/180;
179
- const Δφ = (lat2-lat1) * Math.PI/180;
180
- const Δλ = (lon2-lon1) * Math.PI/180;
181
-
182
- // Haversine formula for surface distance
183
- const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
184
- Math.cos(φ1) * Math.cos(φ2) *
185
- Math.sin(Δλ/2) * Math.sin(Δλ/2);
186
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
187
- const surfaceDistance = R * c;
188
-
189
- // Add altitude difference (Pythagorean theorem)
190
- const altitudeDiff = Math.abs(alt1 - alt2);
191
- const totalDistance = Math.sqrt(Math.pow(surfaceDistance, 2) + Math.pow(altitudeDiff, 2));
192
-
193
- return totalDistance;
194
- }, []);
54
+ /* -------------------------------------------------------------------------- */
55
+ /* FAST + ACCURATE LOCATION FETCH */
56
+ /* -------------------------------------------------------------------------- */
57
+ const getCurrentLocation = useCallback((options = {}) => {
58
+ return new Promise((resolve, reject) => {
59
+ let samples = [];
60
+ let resolved = false;
61
+
62
+ const defaultOptions = {
63
+ enableHighAccuracy: true,
64
+ timeout: 8000, // ⏱ fast
65
+ maximumAge: 2000, // allow cached fix
66
+ forceRequestLocation: true,
67
+ showLocationDialog: true,
68
+ interval: 800,
69
+ fastestInterval: 500,
70
+ distanceFilter: 0,
71
+ ...options,
72
+ };
73
+
74
+ try {
75
+ /* ---------------- FAST FIRST FIX ---------------- */
76
+ Geolocation.getCurrentPosition(
77
+ (pos) => {
78
+ if (!pos?.coords || resolved) return;
79
+
80
+ const {
81
+ latitude,
82
+ longitude,
83
+ accuracy,
84
+ altitude = 0,
85
+ speed = 0,
86
+ heading = 0,
87
+ } = pos.coords;
88
+
89
+ // Accept immediately if good enough for verification
90
+ if (accuracy <= 15) {
91
+ resolved = true;
92
+ resolve({
93
+ latitude,
94
+ longitude,
95
+ altitude,
96
+ accuracy,
97
+ speed,
98
+ heading,
99
+ timestamp: pos.timestamp,
100
+ });
101
+ }
102
+
103
+ samples.push({
104
+ latitude,
105
+ longitude,
106
+ altitude,
107
+ accuracy,
108
+ speed,
109
+ heading,
110
+ timestamp: pos.timestamp,
111
+ });
112
+ },
113
+ (err) => {
114
+ console.warn('[Geo Fast Fix Failed]', err);
115
+ },
116
+ defaultOptions
117
+ );
118
+
119
+ /* ---------------- BACKGROUND IMPROVEMENT ---------------- */
120
+ locationWatchId.current = Geolocation.watchPosition(
121
+ (p) => {
122
+ if (!p?.coords) return;
123
+
124
+ const {
125
+ latitude,
126
+ longitude,
127
+ accuracy,
128
+ altitude = 0,
129
+ speed = 0,
130
+ heading = 0,
131
+ } = p.coords;
132
+
133
+ /* -------- LIGHT NOISE FILTERS -------- */
134
+ if (accuracy > 40) return;
135
+ if (speed > 50) return;
136
+
137
+ samples.push({
138
+ latitude,
139
+ longitude,
140
+ altitude,
141
+ accuracy,
142
+ speed,
143
+ heading,
144
+ timestamp: p.timestamp,
145
+ });
146
+
147
+ if (samples.length > 8) samples.shift();
148
+
149
+ samples.sort((a, b) => a.accuracy - b.accuracy);
150
+ const best = samples[0];
151
+
152
+ // Upgrade accuracy silently
153
+ if (best.accuracy <= 6 && !resolved) {
154
+ resolved = true;
155
+ stopLocationWatching();
156
+ resolve(best);
157
+ }
158
+ },
159
+ (err) => {
160
+ console.error('[Geo Watch Error]', err);
161
+ stopLocationWatching();
162
+ },
163
+ defaultOptions
164
+ );
165
+
166
+ /* ---------------- HARD STOP ---------------- */
167
+ setTimeout(() => {
168
+ if (!resolved && samples.length) {
169
+ resolved = true;
170
+ stopLocationWatching();
171
+ samples.sort((a, b) => a.accuracy - b.accuracy);
172
+ resolve(samples[0]);
173
+ }
174
+ }, 5000);
175
+ } catch (err) {
176
+ stopLocationWatching();
177
+ notifyMessage?.('Failed to get location', 'error');
178
+ reject(err);
179
+ }
180
+ });
181
+ }, [notifyMessage, stopLocationWatching]);
182
+
183
+ /* -------------------------------------------------------------------------- */
184
+ /* DISTANCE CALCULATION */
185
+ /* -------------------------------------------------------------------------- */
186
+ const calculateEnhancedDistance = useCallback(
187
+ (lat1, lon1, lat2, lon2, alt1 = 0, alt2 = 0) => {
188
+ try {
189
+ if (
190
+ typeof lat1 !== 'number' ||
191
+ typeof lon1 !== 'number' ||
192
+ typeof lat2 !== 'number' ||
193
+ typeof lon2 !== 'number'
194
+ ) return Infinity;
195
+
196
+ const toRad = deg => deg * Math.PI / 180;
197
+ const R = 6371e3;
198
+
199
+ const φ1 = toRad(lat1);
200
+ const φ2 = toRad(lat2);
201
+ const Δφ = toRad(lat2 - lat1);
202
+ const Δλ = toRad(lon2 - lon1);
203
+
204
+ const a =
205
+ Math.sin(Δφ / 2) ** 2 +
206
+ Math.cos(φ1) * Math.cos(φ2) *
207
+ Math.sin(Δλ / 2) ** 2;
208
+
209
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
210
+ const surfaceDistance = R * c;
211
+
212
+ const heightDiff = Math.abs(alt1 - alt2);
213
+ return Math.sqrt(surfaceDistance ** 2 + heightDiff ** 2);
214
+ } catch {
215
+ return Infinity;
216
+ }
217
+ },
218
+ []
219
+ );
220
+
221
+ const calculateSafeDistance = useCallback(
222
+ (p1, p2) => {
223
+ try {
224
+ const lat1 = p1.latitude ?? p1.lat;
225
+ const lon1 = p1.longitude ?? p1.lng ?? p1.lon;
226
+ const alt1 = p1.altitude ?? p1.alt ?? 0;
227
+
228
+ const lat2 = p2.latitude ?? p2.lat;
229
+ const lon2 = p2.longitude ?? p2.lng ?? p2.lon;
230
+ const alt2 = p2.altitude ?? p2.alt ?? 0;
231
+
232
+ return calculateEnhancedDistance(
233
+ lat1, lon1, lat2, lon2, alt1, alt2
234
+ );
235
+ } catch {
236
+ return Infinity;
237
+ }
238
+ },
239
+ [calculateEnhancedDistance]
240
+ );
195
241
 
242
+ /* -------------------------------------------------------------------------- */
243
+ /* API */
244
+ /* -------------------------------------------------------------------------- */
196
245
  return {
197
- requestLocationPermission: requestLocationPermissions,
246
+ requestLocationPermission,
198
247
  getCurrentLocation,
199
248
  stopLocationWatching,
249
+ getCurrentWatchId: () => locationWatchId.current,
200
250
  calculateEnhancedDistance,
251
+ calculateSafeDistance,
201
252
  };
202
- };
253
+ };
@@ -0,0 +1,175 @@
1
+ import { useCallback } from 'react';
2
+ import { Platform, PermissionsAndroid } from 'react-native';
3
+ import WifiManager from 'react-native-wifi-reborn';
4
+
5
+ /**
6
+ * WiFi Service Hook for WiFi scanning and fingerprinting
7
+ */
8
+ export const useWifiService = (notifyMessage) => {
9
+
10
+ /* -------------------------------------------------------------------------- */
11
+ /* WIFI PERMISSIONS */
12
+ /* -------------------------------------------------------------------------- */
13
+ const requestWifiPermissions = useCallback(async () => {
14
+ try {
15
+ if (Platform.OS !== 'android') return false;
16
+
17
+ const permissions = [PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION];
18
+
19
+ if (Platform.Version >= 31) {
20
+ permissions.push(PermissionsAndroid.PERMISSIONS.NEARBY_WIFI_DEVICES);
21
+ }
22
+
23
+ const result = await PermissionsAndroid.requestMultiple(permissions);
24
+
25
+ const granted = Object.values(result).every(
26
+ status => status === PermissionsAndroid.RESULTS.GRANTED
27
+ );
28
+
29
+ if (!granted) {
30
+ notifyMessage?.('WiFi scanning permissions missing', 'warning');
31
+ }
32
+
33
+ return granted;
34
+ } catch (err) {
35
+ console.error('[WiFi Permissions] Error:', err);
36
+ return false;
37
+ }
38
+ }, [notifyMessage]);
39
+
40
+ /* -------------------------------------------------------------------------- */
41
+ /* WIFI SCANNING */
42
+ /* -------------------------------------------------------------------------- */
43
+ const scanWifiFingerprint = useCallback(async () => {
44
+ if (Platform.OS !== 'android') return [];
45
+
46
+ try {
47
+ const hasPermission = await requestWifiPermissions();
48
+ if (!hasPermission) return [];
49
+
50
+ const list = await WifiManager.loadWifiList();
51
+
52
+ const fingerprint = list.map(ap => ({
53
+ bssid: ap.BSSID,
54
+ ssid: ap.SSID || 'Unknown',
55
+ rssi: ap.level,
56
+ frequency: ap.frequency || 0,
57
+ capabilities: ap.capabilities || '',
58
+ timestamp: Date.now(),
59
+ }));
60
+
61
+ return fingerprint;
62
+ } catch (err) {
63
+ console.error('[WiFi] Scan failed:', err);
64
+ notifyMessage?.('WiFi scan failed', 'error');
65
+ return [];
66
+ }
67
+ }, [requestWifiPermissions, notifyMessage]);
68
+
69
+ /* -------------------------------------------------------------------------- */
70
+ /* WIFI FINGERPRINT MATCHING */
71
+ /* -------------------------------------------------------------------------- */
72
+ const matchWifiFingerprint = useCallback((scan, reference, options = {}) => {
73
+ const { rssiThreshold = 10, matchWeight = 1, partialMatchWeight = 0.5 } = options;
74
+
75
+ if (!scan || !reference || !Array.isArray(scan) || !Array.isArray(reference)) return 0;
76
+
77
+ let score = 0;
78
+ let matches = 0;
79
+ const totalPossibleMatches = Math.min(scan.length, reference.length);
80
+
81
+ scan.forEach(ap => {
82
+ const match = reference.find(r => r.bssid === ap.bssid);
83
+ if (match) {
84
+ const diff = Math.abs((match.rssi || 0) - ap.rssi);
85
+ score += diff <= rssiThreshold ? matchWeight : partialMatchWeight;
86
+ matches++;
87
+ }
88
+ });
89
+
90
+ const matchPercentage = totalPossibleMatches > 0 ? (matches / totalPossibleMatches) * 100 : 0;
91
+
92
+ return {
93
+ score,
94
+ matches,
95
+ totalPossibleMatches,
96
+ matchPercentage,
97
+ };
98
+ }, []);
99
+
100
+ /* -------------------------------------------------------------------------- */
101
+ /* WIFI UTILITIES */
102
+ /* -------------------------------------------------------------------------- */
103
+ const getWifiNetworksByStrength = useCallback((networks, limit = 5) => {
104
+ if (!Array.isArray(networks)) return [];
105
+ return networks
106
+ .filter(network => network && typeof network.rssi === 'number')
107
+ .sort((a, b) => b.rssi - a.rssi)
108
+ .slice(0, limit);
109
+ }, []);
110
+
111
+ const filterWifiNetworks = useCallback((networks, options = {}) => {
112
+ const { minRssi = -90, maxRssi = -30, excludeHidden = true } = options;
113
+ if (!Array.isArray(networks)) return [];
114
+
115
+ return networks.filter(network => {
116
+ if (!network || !network.bssid) return false;
117
+ if (network.rssi < minRssi || network.rssi > maxRssi) return false;
118
+ if (excludeHidden && (!network.ssid || network.ssid === '' || network.ssid === '<unknown ssid>')) return false;
119
+ return true;
120
+ });
121
+ }, []);
122
+
123
+ const getCurrentWifiInfo = useCallback(async () => {
124
+ if (Platform.OS !== 'android') return null;
125
+
126
+ try {
127
+ const hasPermission = await requestWifiPermissions();
128
+ if (!hasPermission) return null;
129
+
130
+ const currentWifi = await WifiManager.getCurrentWifiSSID();
131
+ return { ssid: currentWifi, timestamp: Date.now() };
132
+ } catch (err) {
133
+ console.error('[WiFi] Failed to get current WiFi:', err);
134
+ return null;
135
+ }
136
+ }, [requestWifiPermissions]);
137
+
138
+ /* -------------------------------------------------------------------------- */
139
+ /* COMBINED LOCATION SCAN */
140
+ /* -------------------------------------------------------------------------- */
141
+ const getLocationWithWifi = useCallback(async (geolocationHook) => {
142
+ try {
143
+ const wifiPromise = scanWifiFingerprint();
144
+
145
+ let locationResult;
146
+ try {
147
+ locationResult = await geolocationHook.getCurrentLocation();
148
+ } catch {
149
+ locationResult = null; // continue with WiFi only
150
+ }
151
+
152
+ const wifiFingerprint = await wifiPromise;
153
+
154
+ return {
155
+ ...(locationResult || {}),
156
+ wifi: wifiFingerprint,
157
+ timestamp: Date.now(),
158
+ source: locationResult ? 'gps+wifi' : 'wifi-only',
159
+ };
160
+ } catch (err) {
161
+ console.error('[WiFi+Location] Combined scan failed:', err);
162
+ throw err;
163
+ }
164
+ }, [scanWifiFingerprint]);
165
+
166
+ return {
167
+ requestWifiPermissions,
168
+ scanWifiFingerprint,
169
+ getCurrentWifiInfo,
170
+ matchWifiFingerprint,
171
+ getWifiNetworksByStrength,
172
+ filterWifiNetworks,
173
+ getLocationWithWifi,
174
+ };
175
+ };
package/src/index.js CHANGED
@@ -18,10 +18,9 @@ import {
18
18
  } from "react-native";
19
19
  import Icon from "react-native-vector-icons/MaterialIcons";
20
20
 
21
- // Custom hooks
21
+ // Custom hooks - Removed unnecessary ones
22
22
  import { useCountdown } from "./hooks/useCountdown";
23
23
  import { useGeolocation } from "./hooks/useGeolocation";
24
- import { useBluetoothService } from "./hooks/useBluetoothService";
25
24
  import { useImageProcessing } from "./hooks/useImageProcessing";
26
25
  import { useNotifyMessage } from "./hooks/useNotifyMessage";
27
26
  import { useSafeCallback } from "./hooks/useSafeCallback";
@@ -51,42 +50,35 @@ const BiometricModal = forwardRef(({
51
50
  fileurl,
52
51
  imageurl,
53
52
  navigation,
54
- MaxDistanceMeters = 50,
55
- duration = 100
53
+ MaxDistanceMeters = 30,
54
+ duration = 100,
56
55
  }, ref) => {
57
- // Custom hooks - Initialize notification hook first so notifyMessage is available for other hooks
56
+ // Custom hooks - Initialize notification hook first
58
57
  const { notification, fadeAnim, slideAnim, notifyMessage, clearNotification } = useNotifyMessage();
59
58
  const { countdown, startCountdown, resetCountdown, pauseCountdown, resumeCountdown } = useCountdown(duration);
60
-
61
- // Separate hooks for geolocation and bluetooth
59
+
60
+ // Only keep geolocation hook
62
61
  const {
63
62
  requestLocationPermission,
64
63
  getCurrentLocation,
65
64
  stopLocationWatching,
66
- calculateEnhancedDistance,
65
+ calculateSafeDistance,
67
66
  } = useGeolocation(notifyMessage);
68
-
69
- const {
70
- requestBluetoothPermission,
71
- startBluetoothScan,
72
- stopBluetoothScan,
73
- nearbyDevices,
74
- clearDevices,
75
- } = useBluetoothService(notifyMessage);
76
-
67
+
77
68
  const { convertImageToBase64 } = useImageProcessing();
78
69
  const safeCallback = useSafeCallback(callback, notifyMessage);
79
70
 
80
- // State
71
+ // State - Simplified
81
72
  const [modalVisible, setModalVisible] = useState(false);
82
- const [cameraType, setCameraType] = useState("back"); // Start with back camera for QR scan
73
+ const [cameraType, setCameraType] = useState("back");
83
74
  const [state, setState] = useState({
84
75
  isLoading: false,
85
76
  loadingType: Global.LoadingTypes.none,
86
77
  currentStep: "Start",
87
78
  employeeData: null,
88
- animationState: Global.AnimationStates.qrScan, // Start with QR scan animation
89
- qrData: null, // Store QR data for later use in face recognition
79
+ animationState: Global.AnimationStates.qrScan,
80
+ qrData: null,
81
+ // Removed: wifiReferenceScan
90
82
  });
91
83
 
92
84
  // Refs
@@ -95,8 +87,8 @@ const BiometricModal = forwardRef(({
95
87
  const responseRef = useRef(null);
96
88
  const processedRef = useRef(false);
97
89
  const resetTimeoutRef = useRef(null);
98
- const bleScanTimeoutRef = useRef(null);
99
-
90
+ // Removed: bleScanTimeoutRef, wifiScanTimeoutRef
91
+
100
92
  // Animation values
101
93
  const iconScaleAnim = useRef(new Animated.Value(1)).current;
102
94
  const iconOpacityAnim = useRef(new Animated.Value(0)).current;
@@ -109,7 +101,7 @@ const BiometricModal = forwardRef(({
109
101
  getStatus: () => state,
110
102
  }));
111
103
 
112
- // Cleanup on unmount
104
+ // Cleanup on unmount - Simplified
113
105
  useEffect(() => {
114
106
  return () => {
115
107
  mountedRef.current = false;
@@ -117,17 +109,11 @@ const BiometricModal = forwardRef(({
117
109
  if (resetTimeoutRef.current) {
118
110
  clearTimeout(resetTimeoutRef.current);
119
111
  }
120
-
121
- if (bleScanTimeoutRef.current) {
122
- clearTimeout(bleScanTimeoutRef.current);
123
- }
124
-
125
- stopBluetoothScan();
112
+
126
113
  stopLocationWatching();
127
- clearDevices();
128
114
  clearNotification();
129
115
  };
130
- }, [stopBluetoothScan, stopLocationWatching, clearDevices, clearNotification]);
116
+ }, [stopLocationWatching, clearNotification]);
131
117
 
132
118
  // Update dataRef when data changes
133
119
  useEffect(() => {
@@ -136,11 +122,9 @@ const BiometricModal = forwardRef(({
136
122
 
137
123
  // Animation helper
138
124
  const animateIcon = useCallback(() => {
139
- // Reset animation
140
125
  iconScaleAnim.setValue(1);
141
126
  iconOpacityAnim.setValue(0);
142
127
 
143
- // Start animation sequence
144
128
  Animated.sequence([
145
129
  Animated.parallel([
146
130
  Animated.timing(iconOpacityAnim, {
@@ -169,7 +153,6 @@ const BiometricModal = forwardRef(({
169
153
  const merged = { ...prev, ...newState };
170
154
 
171
155
  if (JSON.stringify(prev) !== JSON.stringify(merged)) {
172
- // Pause/resume countdown based on loading state
173
156
  if (newState.isLoading !== undefined) {
174
157
  if (newState.isLoading) {
175
158
  pauseCountdown();
@@ -178,7 +161,6 @@ const BiometricModal = forwardRef(({
178
161
  }
179
162
  }
180
163
 
181
- // Animate icon when step changes
182
164
  if (newState.currentStep && newState.currentStep !== prev.currentStep) {
183
165
  animateIcon();
184
166
  }
@@ -191,7 +173,7 @@ const BiometricModal = forwardRef(({
191
173
  }
192
174
  }, [animateIcon, pauseCountdown, resumeCountdown]);
193
175
 
194
- // Reset state helper
176
+ // Reset state helper - Simplified
195
177
  const resetState = useCallback(() => {
196
178
  if (onclose) {
197
179
  onclose(false);
@@ -209,16 +191,14 @@ const BiometricModal = forwardRef(({
209
191
  setModalVisible(false);
210
192
  processedRef.current = false;
211
193
  resetCountdown();
212
- stopBluetoothScan();
213
194
  stopLocationWatching();
214
- clearDevices();
215
195
  clearNotification();
216
196
 
217
197
  if (resetTimeoutRef.current) {
218
198
  clearTimeout(resetTimeoutRef.current);
219
199
  resetTimeoutRef.current = null;
220
200
  }
221
- }, [resetCountdown, stopBluetoothScan, stopLocationWatching, clearDevices, clearNotification, onclose]);
201
+ }, [resetCountdown, stopLocationWatching, clearNotification, onclose]);
222
202
 
223
203
  // Error handler
224
204
  const handleProcessError = useCallback(
@@ -264,49 +244,7 @@ const BiometricModal = forwardRef(({
264
244
  return true;
265
245
  }, [apiurl, handleProcessError]);
266
246
 
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
-
308
- // QR code processing - FIRST STEP
309
- // Enhanced QR processing
247
+ // Simplified QR scanning handler - GPS + Key verification only
310
248
  const handleQRScanned = useCallback(
311
249
  async (qrCodeData) => {
312
250
  if (!validateApiUrl()) return;
@@ -318,7 +256,7 @@ const BiometricModal = forwardRef(({
318
256
  });
319
257
 
320
258
  try {
321
- // 1. Request permissions
259
+ // 1. Request location permission
322
260
  updateState({ loadingType: Global.LoadingTypes.locationPermission });
323
261
  const hasLocationPermission = await requestLocationPermission();
324
262
  if (!hasLocationPermission) {
@@ -326,12 +264,6 @@ const BiometricModal = forwardRef(({
326
264
  return;
327
265
  }
328
266
 
329
- const hasBluetoothPermission = await requestBluetoothPermission();
330
- if (!hasBluetoothPermission) {
331
- notifyMessage("Bluetooth scanning may not work", "warning");
332
- // Continue anyway, Bluetooth is optional
333
- }
334
-
335
267
  // 2. Parse QR data with validation
336
268
  const qrString = typeof qrCodeData === "object" ? qrCodeData?.data : qrCodeData;
337
269
  if (!qrString || typeof qrString !== "string") {
@@ -339,7 +271,7 @@ const BiometricModal = forwardRef(({
339
271
  return;
340
272
  }
341
273
 
342
- // 3. Parse and validate QR coordinates in format: latitude,longitude,depkey
274
+ // 3. Parse and validate QR coordinates
343
275
  const parts = qrString.split(",");
344
276
 
345
277
  if (parts.length < 3) {
@@ -349,10 +281,8 @@ const BiometricModal = forwardRef(({
349
281
 
350
282
  const latStr = parts[0];
351
283
  const lngStr = parts[1];
352
- const qrDepKey = parts[2]; // This is your 1234 from the QR code
353
-
354
- // Optional: QR code accuracy if provided as fourth parameter
355
- const qrAccuracy = parts.length > 3 ? parseFloat(parts[3]) : 5; // Default 5m accuracy
284
+ const qrDepKey = parts[2];
285
+ const qrAccuracy = parts.length > 3 ? parseFloat(parts[3]) : 5;
356
286
 
357
287
  const qrLat = parseFloat(latStr);
358
288
  const qrLng = parseFloat(lngStr);
@@ -362,55 +292,56 @@ const BiometricModal = forwardRef(({
362
292
  return;
363
293
  }
364
294
 
365
- // 3. Get high-accuracy location
295
+ // 4. Get high-accuracy location
366
296
  updateState({ loadingType: Global.LoadingTypes.gettingLocation });
367
- const location = await getCurrentLocation();
297
+
298
+ let location;
299
+ try {
300
+ location = await getCurrentLocation();
301
+ } catch (locationError) {
302
+ console.error('Location error:', locationError);
303
+ handleProcessError("Failed to get location. Please try again.");
304
+ return;
305
+ }
306
+
307
+ // Validate location object
308
+ if (!location || typeof location !== 'object') {
309
+ handleProcessError("Invalid location data received.");
310
+ return;
311
+ }
312
+
313
+ // Validate location coordinates
314
+ if (typeof location.latitude !== 'number' || typeof location.longitude !== 'number' ||
315
+ isNaN(location.latitude) || isNaN(location.longitude)) {
316
+ handleProcessError("Invalid GPS coordinates received.");
317
+ return;
318
+ }
368
319
 
369
320
  // Validate location accuracy
370
321
  if (location.accuracy > 50) {
371
322
  notifyMessage(`Location accuracy is ${location.accuracy.toFixed(1)}m. For best results, move to an open area.`, 'warning');
372
323
  }
373
324
 
374
- // 4. Enhanced BLE scanning (optional - can be removed if not needed)
375
- updateState({ loadingType: Global.LoadingTypes.bleScan });
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();
398
- }
399
-
400
- // 5. Calculate distance (no altitude in your QR code)
325
+ // 5. Calculate distance using safe calculation
401
326
  updateState({ loadingType: Global.LoadingTypes.calculateDistance });
402
327
 
403
- // Use the enhanced distance calculation from geolocation hook
404
- const distance = calculateEnhancedDistance(
405
- qrLat, qrLng,
406
- location.latitude, location.longitude
328
+ const distance = calculateSafeDistance(
329
+ { latitude: qrLat, longitude: qrLng },
330
+ { latitude: location.latitude, longitude: location.longitude }
407
331
  );
408
332
 
333
+ // Validate distance calculation
334
+ if (distance === Infinity || isNaN(distance)) {
335
+ handleProcessError("Failed to calculate distance. Please try again.");
336
+ return;
337
+ }
338
+
409
339
  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) {
340
+
341
+ // Simple verification criteria - GPS + Key only
342
+ const verificationPassed = distanceWithinThreshold && qrDepKey === depkey;
343
+
344
+ if (verificationPassed) {
414
345
  const locationDetails = {
415
346
  qrLocation: {
416
347
  latitude: qrLat,
@@ -422,15 +353,15 @@ const BiometricModal = forwardRef(({
422
353
  longitude: location.longitude,
423
354
  altitude: location.altitude || 0,
424
355
  accuracy: location.accuracy,
425
- speed: location.speed,
426
- heading: location.heading,
427
- timestamp: location.timestamp
356
+ speed: location.speed || 0,
357
+ heading: location.heading || 0,
358
+ timestamp: location.timestamp || Date.now()
428
359
  },
429
360
  distanceMeters: distance,
430
361
  accuracyBuffer: MaxDistanceMeters,
431
362
  verified: true,
363
+ verificationMethod: "GPS+Key",
432
364
  verifiedAt: new Date().toISOString(),
433
- bleDevicesDetected: consistentDevices,
434
365
  locationMethod: location.provider || 'unknown',
435
366
  qrData: qrString,
436
367
  qrKey: qrDepKey
@@ -445,38 +376,34 @@ const BiometricModal = forwardRef(({
445
376
  loadingType: Global.LoadingTypes.none,
446
377
  });
447
378
 
448
- notifyMessage(`Location verified! Distance: ${distance.toFixed(1)}m (±${Math.max(location.accuracy, qrAccuracy).toFixed(1)}m)`, "success");
379
+ const successMessage = `Location verified! Distance: ${distance.toFixed(1)}m (±${Math.max(location.accuracy || 0, qrAccuracy).toFixed(1)}m)`;
380
+ notifyMessage(successMessage, "success");
449
381
  setTimeout(() => startFaceRecognition(), 1200);
450
382
  } else {
451
- let errorMsg = `Location mismatch: ${distance.toFixed(1)}m away`;
383
+ let errorMsg = `Verification failed: ${distance.toFixed(1)}m away`;
452
384
  if (qrDepKey !== depkey) errorMsg += " (Key mismatch)";
453
385
  handleProcessError(errorMsg);
454
386
  }
455
387
  } catch (error) {
456
- console.error("Enhanced verification failed:", error);
457
- handleProcessError("Unable to verify location or BLE proximity.", error);
388
+ console.error("Location verification failed:", error);
389
+ handleProcessError("Unable to verify location. Please try again.", error);
458
390
  }
459
391
  },
460
392
  [
461
393
  validateApiUrl,
462
394
  updateState,
463
395
  requestLocationPermission,
464
- requestBluetoothPermission,
465
396
  getCurrentLocation,
466
- startBluetoothScan,
467
- stopBluetoothScan,
468
- nearbyDevices,
469
- findConsistentBLEDevices,
470
397
  notifyMessage,
471
398
  handleProcessError,
472
399
  startFaceRecognition,
473
400
  depkey,
474
401
  MaxDistanceMeters,
475
- calculateEnhancedDistance,
402
+ calculateSafeDistance,
476
403
  ]
477
404
  );
478
405
 
479
- // Face scan upload - SECOND STEP
406
+ // Face scan upload - SECOND STEP (simplified, no WiFi revalidation)
480
407
  const uploadFaceScan = useCallback(
481
408
  async (selfie) => {
482
409
  if (!validateApiUrl()) return;
@@ -539,7 +466,7 @@ const BiometricModal = forwardRef(({
539
466
  if (response?.httpstatus === 200) {
540
467
  // Combine face recognition response with QR location data
541
468
  responseRef.current = {
542
- ...responseRef.current, // Contains location data from QR scan
469
+ ...responseRef.current,
543
470
  ...response.data?.data || {},
544
471
  faceRecognition: response.data?.data || null,
545
472
  };
@@ -587,7 +514,7 @@ const BiometricModal = forwardRef(({
587
514
  safeCallback,
588
515
  handleProcessError,
589
516
  state.qrData,
590
- apiurl
517
+ apiurl,
591
518
  ]
592
519
  );
593
520
 
@@ -758,6 +685,7 @@ const styles = StyleSheet.create({
758
685
  position: 'absolute',
759
686
  bottom: Platform.OS === 'ios' ? 50 : 30,
760
687
  left: 0,
688
+ right: 0,
761
689
  zIndex: 10,
762
690
  },
763
691
  headerContainer: {
@@ -793,6 +721,16 @@ const styles = StyleSheet.create({
793
721
  textShadowOffset: { width: 1, height: 1 },
794
722
  textShadowRadius: 2,
795
723
  },
724
+ wifiIndicator: {
725
+ fontSize: 12,
726
+ color: Global.AppTheme.primary || '#4CAF50',
727
+ marginTop: 4,
728
+ fontWeight: '500',
729
+ fontFamily: Platform.OS === 'ios' ? 'Helvetica Neue' : 'sans-serif',
730
+ textShadowColor: 'rgba(0, 0, 0, 0.2)',
731
+ textShadowOffset: { width: 1, height: 1 },
732
+ textShadowRadius: 1,
733
+ },
796
734
  closeButton: {
797
735
  position: 'absolute',
798
736
  top: Platform.OS === 'ios' ? 40 : 20,