react-native-biometric-verifier 0.0.47 → 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.
- package/package.json +1 -1
- package/src/hooks/useBluetoothService.js +79 -119
- package/src/hooks/useGeolocation.js +192 -174
- package/src/hooks/useWifiService.js +175 -0
- package/src/index.js +260 -55
|
@@ -3,200 +3,218 @@ import { Platform, PermissionsAndroid } from 'react-native';
|
|
|
3
3
|
import Geolocation from 'react-native-geolocation-service';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Geolocation Hook for GPS functionality
|
|
7
7
|
*/
|
|
8
8
|
export const useGeolocation = (notifyMessage) => {
|
|
9
9
|
const locationWatchId = useRef(null);
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
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
|
-
}
|
|
11
|
+
/* -------------------------------------------------------------------------- */
|
|
12
|
+
/* PERMISSIONS */
|
|
13
|
+
/* -------------------------------------------------------------------------- */
|
|
14
|
+
const requestLocationPermission = useCallback(async () => {
|
|
15
|
+
try {
|
|
16
|
+
if (Platform.OS !== 'android') return true;
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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;
|
|
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
|
-
};
|
|
27
|
+
const result = await PermissionsAndroid.requestMultiple(permissions);
|
|
67
28
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
};
|
|
29
|
+
const granted = Object.values(result).every(
|
|
30
|
+
status => status === PermissionsAndroid.RESULTS.GRANTED
|
|
31
|
+
);
|
|
107
32
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
}
|
|
129
|
-
|
|
130
|
-
// Validate location quality
|
|
131
|
-
if (!bestLocation || bestLocation.accuracy > 50) {
|
|
132
|
-
notifyMessage?.('Location accuracy is low. Please move to an open area.', 'warning');
|
|
133
|
-
}
|
|
134
|
-
|
|
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
|
+
}
|
|
159
36
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
/* -------------------------------------------------------------------------- */
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
};
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const pos = await new Promise((res, rej) => {
|
|
73
|
+
Geolocation.getCurrentPosition(res, rej, defaultOptions);
|
|
74
|
+
});
|
|
75
|
+
|
|
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
|
+
}
|
|
80
|
+
|
|
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
|
+
};
|
|
90
|
+
|
|
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);
|
|
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);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}, [notifyMessage, stopLocationWatching]);
|
|
145
|
+
|
|
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
|
+
);
|
|
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
|
+
);
|
|
206
|
+
|
|
207
|
+
/* -------------------------------------------------------------------------- */
|
|
208
|
+
/* GETTERS */
|
|
209
|
+
/* -------------------------------------------------------------------------- */
|
|
210
|
+
const getCurrentWatchId = useCallback(() => locationWatchId.current, []);
|
|
195
211
|
|
|
196
212
|
return {
|
|
197
|
-
requestLocationPermission
|
|
213
|
+
requestLocationPermission,
|
|
198
214
|
getCurrentLocation,
|
|
199
215
|
stopLocationWatching,
|
|
216
|
+
getCurrentWatchId,
|
|
200
217
|
calculateEnhancedDistance,
|
|
218
|
+
calculateSafeDistance,
|
|
201
219
|
};
|
|
202
|
-
};
|
|
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
|
+
};
|