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.
- package/package.json +1 -1
- package/src/hooks/useBluetoothService.js +195 -0
- package/src/hooks/useGeolocation.js +187 -266
- package/src/hooks/useWifiService.js +175 -0
- package/src/index.js +343 -114
|
@@ -1,299 +1,220 @@
|
|
|
1
|
-
|
|
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
|
+
* 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
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
}
|
|
18
|
+
const permissions = [
|
|
19
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
20
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
|
|
21
|
+
];
|
|
38
22
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
213
|
+
requestLocationPermission,
|
|
292
214
|
getCurrentLocation,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
nearbyDevices,
|
|
215
|
+
stopLocationWatching,
|
|
216
|
+
getCurrentWatchId,
|
|
296
217
|
calculateEnhancedDistance,
|
|
297
|
-
|
|
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
|
+
};
|