react-native-biometric-verifier 0.0.44 → 0.0.45
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/useGeolocation.js +277 -49
- package/src/index.js +592 -490
package/package.json
CHANGED
|
@@ -1,71 +1,299 @@
|
|
|
1
|
-
|
|
1
|
+
// Enhanced location service
|
|
2
|
+
import { useCallback, useState, useRef } from 'react';
|
|
2
3
|
import { Platform, PermissionsAndroid } from 'react-native';
|
|
3
4
|
import Geolocation from 'react-native-geolocation-service';
|
|
5
|
+
import { BleManager } from 'react-native-ble-plx';
|
|
6
|
+
|
|
7
|
+
const manager = new BleManager();
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param {Function} notifyMessage - Callback to show notifications (message, type).
|
|
9
|
-
* @returns {Object} { requestLocationPermission, getCurrentLocation }
|
|
10
|
+
* Enhanced Geolocation Hook with better accuracy
|
|
10
11
|
*/
|
|
11
12
|
export const useGeolocation = (notifyMessage) => {
|
|
13
|
+
const [nearbyDevices, setNearbyDevices] = useState([]);
|
|
14
|
+
const scanTimeoutRef = useRef(null);
|
|
15
|
+
const locationWatchId = useRef(null);
|
|
16
|
+
|
|
12
17
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @returns {Promise<boolean>} - True if permission granted, else false.
|
|
18
|
+
* Request high-accuracy permissions
|
|
15
19
|
*/
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
PermissionsAndroid.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
);
|
|
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
|
+
]);
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
// Check Android version for background location
|
|
33
|
+
if (Platform.Version >= 29) {
|
|
34
|
+
await PermissionsAndroid.request(
|
|
35
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
|
|
36
|
+
);
|
|
37
|
+
}
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
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;
|
|
34
45
|
}
|
|
35
|
-
|
|
36
|
-
return hasPermission;
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error('Location permission error:', error);
|
|
39
|
-
notifyMessage?.('Location permission error.', 'error');
|
|
40
|
-
return false;
|
|
41
46
|
}
|
|
42
|
-
|
|
47
|
+
return true; // iOS handles permissions differently
|
|
48
|
+
}, []);
|
|
43
49
|
|
|
44
50
|
/**
|
|
45
|
-
*
|
|
46
|
-
* @returns {Promise<GeolocationCoordinates>}
|
|
51
|
+
* Get high-accuracy location with multiple attempts
|
|
47
52
|
*/
|
|
48
53
|
const getCurrentLocation = useCallback(
|
|
49
54
|
() =>
|
|
50
|
-
new Promise((resolve, reject) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
|
|
71
|
+
const watchLocationForAccuracy = () => {
|
|
72
|
+
return new Promise((resolveWatch, rejectWatch) => {
|
|
73
|
+
let bestAccuracy = Infinity;
|
|
74
|
+
let bestCoords = null;
|
|
75
|
+
let watchTimer;
|
|
76
|
+
|
|
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
|
+
}
|
|
87
|
+
|
|
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
|
+
}
|
|
64
131
|
}
|
|
65
|
-
|
|
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
|
+
}
|
|
66
143
|
}),
|
|
67
144
|
[notifyMessage]
|
|
68
145
|
);
|
|
69
146
|
|
|
70
|
-
|
|
71
|
-
|
|
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;
|
|
156
|
+
}
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Enhanced BLE scanning with filtering
|
|
161
|
+
*/
|
|
162
|
+
const startBluetoothScan = useCallback(async () => {
|
|
163
|
+
console.log('[BLE] Starting enhanced Bluetooth scan...');
|
|
164
|
+
const permission = await requestPermissions();
|
|
165
|
+
if (!permission) {
|
|
166
|
+
notifyMessage?.('Permissions required for Bluetooth scanning', 'error');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setNearbyDevices([]);
|
|
171
|
+
|
|
172
|
+
// Configure scan for better accuracy
|
|
173
|
+
const scanOptions = {
|
|
174
|
+
allowDuplicates: true,
|
|
175
|
+
scanMode: 2, // SCAN_MODE_LOW_LATENCY for Android
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
manager.startDeviceScan(null, scanOptions, (error, device) => {
|
|
179
|
+
if (error) {
|
|
180
|
+
console.error('[BLE] Scan error:', error);
|
|
181
|
+
stopBluetoothScan();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (device && device.name && device.rssi) {
|
|
186
|
+
const distance = estimateDistance(device.rssi);
|
|
187
|
+
|
|
188
|
+
// Filter out weak signals and calculate moving average
|
|
189
|
+
if (distance > 0 && distance <= 20) {
|
|
190
|
+
setNearbyDevices(prev => {
|
|
191
|
+
const existingIndex = prev.findIndex(d => d.id === device.id);
|
|
192
|
+
|
|
193
|
+
if (existingIndex >= 0) {
|
|
194
|
+
// Update existing device with moving average
|
|
195
|
+
const existing = prev[existingIndex];
|
|
196
|
+
const avgDistance = (parseFloat(existing.distance) + distance) / 2;
|
|
197
|
+
|
|
198
|
+
const updated = [...prev];
|
|
199
|
+
updated[existingIndex] = {
|
|
200
|
+
...existing,
|
|
201
|
+
rssi: device.rssi,
|
|
202
|
+
distance: avgDistance.toFixed(2),
|
|
203
|
+
lastSeen: Date.now(),
|
|
204
|
+
count: existing.count + 1,
|
|
205
|
+
};
|
|
206
|
+
return updated;
|
|
207
|
+
} else {
|
|
208
|
+
// Add new device
|
|
209
|
+
const newDevice = {
|
|
210
|
+
id: device.id,
|
|
211
|
+
name: device.name,
|
|
212
|
+
rssi: device.rssi,
|
|
213
|
+
distance: distance.toFixed(2),
|
|
214
|
+
lastSeen: Date.now(),
|
|
215
|
+
count: 1,
|
|
216
|
+
manufacturerData: device.manufacturerData,
|
|
217
|
+
serviceUUIDs: device.serviceUUIDs,
|
|
218
|
+
};
|
|
219
|
+
return [...prev, newDevice];
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Stop after optimized duration
|
|
227
|
+
scanTimeoutRef.current = setTimeout(() => {
|
|
228
|
+
stopBluetoothScan();
|
|
229
|
+
filterStaleDevices();
|
|
230
|
+
}, 10000);
|
|
231
|
+
}, [notifyMessage, requestPermissions, stopBluetoothScan, filterStaleDevices]);
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Filter out stale BLE devices
|
|
235
|
+
*/
|
|
236
|
+
const filterStaleDevices = useCallback(() => {
|
|
237
|
+
setNearbyDevices(prev =>
|
|
238
|
+
prev.filter(device => {
|
|
239
|
+
const isRecent = Date.now() - device.lastSeen < 10000; // 10 seconds
|
|
240
|
+
const hasMultipleReadings = device.count >= 3;
|
|
241
|
+
return isRecent && hasMultipleReadings;
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
}, []);
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Enhanced distance calculation using multiple methods
|
|
248
|
+
*/
|
|
249
|
+
const estimateDistance = useCallback((rssi, txPower = -59) => {
|
|
250
|
+
if (!rssi || rssi >= 0) return -1;
|
|
251
|
+
|
|
252
|
+
// Method 1: Log-distance path loss model (most common)
|
|
253
|
+
const n = 2.0; // Path loss exponent (2 for free space, 2.7-3.5 for indoor)
|
|
254
|
+
const distance1 = Math.pow(10, (txPower - rssi) / (10 * n));
|
|
255
|
+
|
|
256
|
+
// Method 2: Quadratic approximation for short distances
|
|
257
|
+
const distance2 = 0.89976 * Math.pow(Math.abs(rssi), 0.80976) + 0.111;
|
|
258
|
+
|
|
259
|
+
// Average the methods for better accuracy
|
|
260
|
+
const avgDistance = (distance1 + distance2) / 2;
|
|
261
|
+
|
|
262
|
+
return Math.max(0.1, avgDistance); // Minimum 0.1m
|
|
263
|
+
}, []);
|
|
264
|
+
|
|
265
|
+
/**
|
|
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;
|
|
275
|
+
|
|
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
|
+
}, []);
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
requestLocationPermission: requestPermissions,
|
|
292
|
+
getCurrentLocation,
|
|
293
|
+
startBluetoothScan,
|
|
294
|
+
stopBluetoothScan,
|
|
295
|
+
nearbyDevices,
|
|
296
|
+
calculateEnhancedDistance,
|
|
297
|
+
filterStaleDevices,
|
|
298
|
+
};
|
|
299
|
+
};
|