react-native-biometric-verifier 0.0.46 → 0.0.48

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.
@@ -1,299 +1,220 @@
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
- * Enhanced Geolocation Hook with better accuracy
6
+ * Geolocation Hook for GPS functionality
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
- /**
18
- * Request high-accuracy permissions
19
- */
20
- const requestPermissions = useCallback(async () => {
21
- console.log('[Permissions] Requesting enhanced permissions...');
22
-
23
- if (Platform.OS === 'android') {
24
- try {
25
- const granted = await PermissionsAndroid.requestMultiple([
26
- PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
27
- PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
28
- PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
29
- PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
30
- ]);
11
+ /* -------------------------------------------------------------------------- */
12
+ /* PERMISSIONS */
13
+ /* -------------------------------------------------------------------------- */
14
+ const requestLocationPermission = useCallback(async () => {
15
+ try {
16
+ if (Platform.OS !== 'android') return true;
31
17
 
32
- // Check Android version for background location
33
- if (Platform.Version >= 29) {
34
- await PermissionsAndroid.request(
35
- PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
36
- );
37
- }
18
+ const permissions = [
19
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
20
+ PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
21
+ ];
38
22
 
39
- return Object.values(granted).every(
40
- status => status === PermissionsAndroid.RESULTS.GRANTED
41
- );
42
- } catch (error) {
43
- console.error('[Permissions] Error:', error);
44
- return false;
23
+ if (Platform.Version >= 31) {
24
+ permissions.push(PermissionsAndroid.PERMISSIONS.NEARBY_WIFI_DEVICES);
45
25
  }
46
- }
47
- return true; // iOS handles permissions differently
48
- }, []);
49
-
50
- /**
51
- * Get high-accuracy location with multiple attempts
52
- */
53
- const getCurrentLocation = useCallback(
54
- () =>
55
- new Promise(async (resolve, reject) => {
56
- console.log('[Location] Fetching enhanced location...');
57
-
58
- let bestLocation = null;
59
- let attempts = 0;
60
- const maxAttempts = 3;
61
-
62
- const locationOptions = {
63
- enableHighAccuracy: true,
64
- timeout: 20000,
65
- maximumAge: 0,
66
- distanceFilter: 0,
67
- forceRequestLocation: true,
68
- showLocationDialog: true,
69
- };
70
26
 
71
- const watchLocationForAccuracy = () => {
72
- return new Promise((resolveWatch, rejectWatch) => {
73
- let bestAccuracy = Infinity;
74
- let bestCoords = null;
75
- let watchTimer;
27
+ const result = await PermissionsAndroid.requestMultiple(permissions);
76
28
 
77
- locationWatchId.current = Geolocation.watchPosition(
78
- (position) => {
79
- const { coords } = position;
80
-
81
- // Track best accuracy
82
- if (coords.accuracy < bestAccuracy) {
83
- bestAccuracy = coords.accuracy;
84
- bestCoords = coords;
85
- console.log(`[Location] Improved accuracy: ${coords.accuracy}m`);
86
- }
29
+ const granted = Object.values(result).every(
30
+ status => status === PermissionsAndroid.RESULTS.GRANTED
31
+ );
87
32
 
88
- // Stop if we reach desired accuracy
89
- if (coords.accuracy <= 5) {
90
- clearTimeout(watchTimer);
91
- Geolocation.clearWatch(locationWatchId.current);
92
- resolveWatch(coords);
93
- }
94
- },
95
- (error) => {
96
- clearTimeout(watchTimer);
97
- Geolocation.clearWatch(locationWatchId.current);
98
- rejectWatch(error);
99
- },
100
- locationOptions
101
- );
102
-
103
- // Timeout after 15 seconds
104
- watchTimer = setTimeout(() => {
105
- Geolocation.clearWatch(locationWatchId.current);
106
- resolveWatch(bestCoords);
107
- }, 15000);
108
- });
109
- };
110
-
111
- try {
112
- // First try: Single high-accuracy reading
113
- const singleLocation = await new Promise((resolveSingle, rejectSingle) => {
114
- Geolocation.getCurrentPosition(
115
- resolveSingle,
116
- rejectSingle,
117
- locationOptions
118
- );
119
- });
120
-
121
- bestLocation = singleLocation.coords;
122
- console.log(`[Location] Initial accuracy: ${bestLocation.accuracy}m`);
123
-
124
- // If accuracy > 20m, try watching for improvement
125
- if (bestLocation.accuracy > 20) {
126
- console.log('[Location] Accuracy insufficient, starting watch...');
127
- const watchedCoords = await watchLocationForAccuracy();
128
- if (watchedCoords && watchedCoords.accuracy < bestLocation.accuracy) {
129
- bestLocation = watchedCoords;
130
- }
131
- }
132
-
133
- // Validate location quality
134
- if (!bestLocation || bestLocation.accuracy > 50) {
135
- notifyMessage?.('Location accuracy is low. Please move to an open area.', 'warning');
136
- }
137
-
138
- resolve(bestLocation);
139
- } catch (error) {
140
- console.error('[Location] Error:', error);
141
- reject(error);
142
- }
143
- }),
144
- [notifyMessage]
145
- );
33
+ if (!granted) {
34
+ notifyMessage?.('Location permissions missing', 'warning');
35
+ }
146
36
 
147
- /**
148
- * Stop Bluetooth scan
149
- */
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;
37
+ return granted;
38
+ } catch (err) {
39
+ console.error('[Geolocation Permissions] Error:', err);
40
+ return false;
41
+ }
42
+ }, [notifyMessage]);
43
+
44
+ /* -------------------------------------------------------------------------- */
45
+ /* STOP LOCATION WATCH */
46
+ /* -------------------------------------------------------------------------- */
47
+ const stopLocationWatching = useCallback(() => {
48
+ if (locationWatchId.current !== null) {
49
+ Geolocation.clearWatch(locationWatchId.current);
50
+ locationWatchId.current = null;
156
51
  }
157
52
  }, []);
158
53
 
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
- }
54
+ /* -------------------------------------------------------------------------- */
55
+ /* HIGH ACCURACY GPS */
56
+ /* -------------------------------------------------------------------------- */
57
+ const getCurrentLocation = useCallback((options = {}) => {
58
+ return new Promise(async (resolve, reject) => {
59
+ let bestCoords = null;
60
+ let bestAccuracy = Infinity;
61
+
62
+ const defaultOptions = {
63
+ enableHighAccuracy: true,
64
+ timeout: 20000,
65
+ maximumAge: 0,
66
+ forceRequestLocation: true,
67
+ showLocationDialog: true,
68
+ ...options,
69
+ };
169
70
 
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
- };
71
+ try {
72
+ const pos = await new Promise((res, rej) => {
73
+ Geolocation.getCurrentPosition(res, rej, defaultOptions);
74
+ });
177
75
 
178
- manager.startDeviceScan(null, scanOptions, (error, device) => {
179
- if (error) {
180
- console.error('[BLE] Scan error:', error);
181
- stopBluetoothScan();
182
- return;
183
- }
76
+ if (!pos.coords || typeof pos.coords.latitude !== 'number' || typeof pos.coords.longitude !== 'number') {
77
+ reject(new Error('Invalid GPS coordinates received'));
78
+ return;
79
+ }
184
80
 
185
- if (device && device.name && device.rssi) {
186
- const distance = estimateDistance(device.rssi);
81
+ bestCoords = {
82
+ latitude: pos.coords.latitude,
83
+ longitude: pos.coords.longitude,
84
+ altitude: pos.coords.altitude || 0,
85
+ accuracy: pos.coords.accuracy,
86
+ speed: pos.coords.speed || 0,
87
+ heading: pos.coords.heading || 0,
88
+ timestamp: pos.timestamp,
89
+ };
187
90
 
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
- });
91
+ bestAccuracy = pos.coords.accuracy;
92
+
93
+ if (bestAccuracy > 15) {
94
+ locationWatchId.current = Geolocation.watchPosition(
95
+ (p) => {
96
+ if (
97
+ p.coords &&
98
+ typeof p.coords.latitude === 'number' &&
99
+ typeof p.coords.longitude === 'number' &&
100
+ p.coords.accuracy < bestAccuracy
101
+ ) {
102
+ bestAccuracy = p.coords.accuracy;
103
+ bestCoords = {
104
+ latitude: p.coords.latitude,
105
+ longitude: p.coords.longitude,
106
+ altitude: p.coords.altitude || 0,
107
+ accuracy: p.coords.accuracy,
108
+ speed: p.coords.speed || 0,
109
+ heading: p.coords.heading || 0,
110
+ timestamp: p.timestamp,
111
+ };
112
+ }
113
+
114
+ if (bestAccuracy <= 5) {
115
+ stopLocationWatching();
116
+ }
117
+ },
118
+ (err) => {
119
+ console.error('[Geolocation Watch Error]:', err);
120
+ stopLocationWatching();
121
+ },
122
+ defaultOptions
123
+ );
124
+
125
+ setTimeout(() => stopLocationWatching(), 12000);
222
126
  }
127
+
128
+ resolve({
129
+ latitude: bestCoords.latitude,
130
+ longitude: bestCoords.longitude,
131
+ altitude: bestCoords.altitude,
132
+ accuracy: bestAccuracy,
133
+ speed: bestCoords.speed,
134
+ heading: bestCoords.heading,
135
+ timestamp: bestCoords.timestamp,
136
+ });
137
+ } catch (err) {
138
+ console.error('[Geolocation] Failed to get location:', err);
139
+ stopLocationWatching();
140
+ notifyMessage?.('Failed to get location', 'error');
141
+ reject(err);
223
142
  }
224
143
  });
144
+ }, [notifyMessage, stopLocationWatching]);
225
145
 
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
- }, []);
146
+ /* -------------------------------------------------------------------------- */
147
+ /* DISTANCE CALCULATION */
148
+ /* -------------------------------------------------------------------------- */
149
+ const calculateEnhancedDistance = useCallback(
150
+ (lat1, lon1, lat2, lon2, alt1 = 0, alt2 = 0) => {
151
+ try {
152
+ if (
153
+ typeof lat1 !== 'number' || typeof lon1 !== 'number' ||
154
+ typeof lat2 !== 'number' || typeof lon2 !== 'number' ||
155
+ isNaN(lat1) || isNaN(lon1) || isNaN(lat2) || isNaN(lon2)
156
+ ) return Infinity;
157
+
158
+ const toRad = (deg) => deg * Math.PI / 180;
159
+ const R = 6371e3; // meters
160
+ const φ1 = toRad(lat1);
161
+ const φ2 = toRad(lat2);
162
+ const Δφ = toRad(lat2 - lat1);
163
+ const Δλ = toRad(lon2 - lon1);
164
+
165
+ const a = Math.sin(Δφ / 2) ** 2 +
166
+ Math.cos(φ1) * Math.cos(φ2) *
167
+ Math.sin(Δλ / 2) ** 2;
168
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
169
+ const surfaceDistance = R * c;
170
+
171
+ const heightDifference = Math.abs(alt1 - alt2);
172
+ return Math.sqrt(surfaceDistance ** 2 + heightDifference ** 2);
173
+ } catch (err) {
174
+ console.error('[Geolocation Distance] Error:', err);
175
+ return Infinity;
176
+ }
177
+ },
178
+ []
179
+ );
264
180
 
265
- /**
266
- * Calculate distance between coordinates using Haversine formula with altitude
267
- */
268
- const calculateEnhancedDistance = useCallback((lat1, lon1, lat2, lon2, alt1 = 0, alt2 = 0) => {
269
- const R = 6371e3; // Earth's radius in meters
270
-
271
- const φ1 = lat1 * Math.PI/180;
272
- const φ2 = lat2 * Math.PI/180;
273
- const Δφ = (lat2-lat1) * Math.PI/180;
274
- const Δλ = (lon2-lon1) * Math.PI/180;
181
+ const calculateSafeDistance = useCallback(
182
+ (point1, point2) => {
183
+ try {
184
+ const lat1 = typeof point1.latitude === 'number' ? point1.latitude :
185
+ typeof point1.lat === 'number' ? point1.lat : 0;
186
+ const lon1 = typeof point1.longitude === 'number' ? point1.longitude :
187
+ typeof point1.lng === 'number' ? point1.lng :
188
+ typeof point1.lon === 'number' ? point1.lon : 0;
189
+ const alt1 = point1.altitude || point1.alt || 0;
190
+
191
+ const lat2 = typeof point2.latitude === 'number' ? point2.latitude :
192
+ typeof point2.lat === 'number' ? point2.lat : 0;
193
+ const lon2 = typeof point2.longitude === 'number' ? point2.longitude :
194
+ typeof point2.lng === 'number' ? point2.lng :
195
+ typeof point2.lon === 'number' ? point2.lon : 0;
196
+ const alt2 = point2.altitude || point2.alt || 0;
197
+
198
+ return calculateEnhancedDistance(lat1, lon1, lat2, lon2, alt1, alt2);
199
+ } catch (err) {
200
+ console.error('[Geolocation SafeDistance] Error:', err);
201
+ return Infinity;
202
+ }
203
+ },
204
+ [calculateEnhancedDistance]
205
+ );
275
206
 
276
- // Haversine formula for surface distance
277
- const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
278
- Math.cos(φ1) * Math.cos(φ2) *
279
- Math.sin(Δλ/2) * Math.sin(Δλ/2);
280
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
281
- const surfaceDistance = R * c;
282
-
283
- // Add altitude difference (Pythagorean theorem)
284
- const altitudeDiff = Math.abs(alt1 - alt2);
285
- const totalDistance = Math.sqrt(Math.pow(surfaceDistance, 2) + Math.pow(altitudeDiff, 2));
286
-
287
- return totalDistance;
288
- }, []);
207
+ /* -------------------------------------------------------------------------- */
208
+ /* GETTERS */
209
+ /* -------------------------------------------------------------------------- */
210
+ const getCurrentWatchId = useCallback(() => locationWatchId.current, []);
289
211
 
290
212
  return {
291
- requestLocationPermission: requestPermissions,
213
+ requestLocationPermission,
292
214
  getCurrentLocation,
293
- startBluetoothScan,
294
- stopBluetoothScan,
295
- nearbyDevices,
215
+ stopLocationWatching,
216
+ getCurrentWatchId,
296
217
  calculateEnhancedDistance,
297
- filterStaleDevices,
218
+ calculateSafeDistance,
298
219
  };
299
- };
220
+ };
@@ -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
+ };