foreground-location 0.0.1
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/ForegroundLocation.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +931 -0
- package/android/build.gradle +74 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/java/in/xconcepts/foreground/location/APIService.java +449 -0
- package/android/src/main/java/in/xconcepts/foreground/location/ForeGroundLocation.java +58 -0
- package/android/src/main/java/in/xconcepts/foreground/location/ForeGroundLocationPlugin.java +650 -0
- package/android/src/main/java/in/xconcepts/foreground/location/LocationForegroundService.java +526 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/esm/definitions.d.ts +240 -0
- package/dist/esm/definitions.js +26 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +16 -0
- package/dist/esm/web.js +97 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +138 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +141 -0
- package/dist/plugin.js.map +1 -0
- package/docs/CHANGES-SUMMARY.md +69 -0
- package/docs/setup-and-examples.md +2851 -0
- package/ios/Sources/ForeGroundLocationPlugin/ForeGroundLocation.swift +75 -0
- package/ios/Sources/ForeGroundLocationPlugin/ForeGroundLocationPlugin.swift +125 -0
- package/ios/Tests/ForeGroundLocationPluginTests/ForeGroundLocationPluginTests.swift +36 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
# Capacitor Foreground Location Plugin
|
|
2
|
+
|
|
3
|
+
A robust Capacitor plugin that provides foreground location tracking with optional API service integration. This plugin enables continuous location tracking with a foreground service, real-time updates to your Ionic/Capacitor app, and optional batching of location data to remote API endpoints.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Foreground Service**: Continuous location tracking with persistent notification
|
|
8
|
+
- 📍 **High Accuracy**: Support for multiple location priority levels
|
|
9
|
+
- 📱 **Real-time Updates**: Live location updates via event listeners
|
|
10
|
+
- 🌐 **API Integration**: Batch and send location data to remote endpoints with retry logic
|
|
11
|
+
- ⚡ **Power Efficient**: Configurable intervals and priorities for battery optimization
|
|
12
|
+
- 🔄 **Resilient API Service**: Circuit breaker pattern and exponential backoff
|
|
13
|
+
- 💾 **Memory Safe**: Configurable buffer management
|
|
14
|
+
- 🛡️ **Permission Management**: Comprehensive permission handling for all platforms
|
|
15
|
+
- 📊 **Service Monitoring**: Real-time service and API status monitoring
|
|
16
|
+
- 🎯 **Cross-platform**: Full support for Android and iOS (web stub implementation)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install foreground-location
|
|
22
|
+
npx cap sync
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Platform Setup
|
|
26
|
+
|
|
27
|
+
### Android Configuration
|
|
28
|
+
|
|
29
|
+
Add the following permissions to your `android/app/src/main/AndroidManifest.xml`:
|
|
30
|
+
|
|
31
|
+
```xml
|
|
32
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
33
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
34
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
35
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
36
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
37
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### iOS Configuration
|
|
41
|
+
|
|
42
|
+
Add the following to your `ios/App/App/Info.plist`:
|
|
43
|
+
|
|
44
|
+
```xml
|
|
45
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
46
|
+
<string>This app needs location access to track your location in the foreground</string>
|
|
47
|
+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
|
48
|
+
<string>This app needs location access to track your location continuously</string>
|
|
49
|
+
<key>UIBackgroundModes</key>
|
|
50
|
+
<array>
|
|
51
|
+
<string>location</string>
|
|
52
|
+
</array>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### Basic Location Tracking
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { ForeGroundLocation } from 'foreground-location';
|
|
61
|
+
|
|
62
|
+
// Check and request permissions
|
|
63
|
+
const checkPermissions = async () => {
|
|
64
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
65
|
+
|
|
66
|
+
if (permissions.location !== 'granted') {
|
|
67
|
+
const requested = await ForeGroundLocation.requestPermissions();
|
|
68
|
+
if (requested.location !== 'granted') {
|
|
69
|
+
throw new Error('Location permission required');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Start location tracking
|
|
75
|
+
const startTracking = async () => {
|
|
76
|
+
try {
|
|
77
|
+
await checkPermissions();
|
|
78
|
+
|
|
79
|
+
// Add location listener
|
|
80
|
+
await ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
81
|
+
console.log('New location:', location);
|
|
82
|
+
// Handle location update in your app
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Start the foreground service
|
|
86
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
87
|
+
interval: 10000, // Update every 10 seconds
|
|
88
|
+
fastestInterval: 5000, // But not faster than 5 seconds
|
|
89
|
+
priority: 'HIGH_ACCURACY',
|
|
90
|
+
notification: {
|
|
91
|
+
title: 'Location Tracking',
|
|
92
|
+
text: 'Tracking your location in the background',
|
|
93
|
+
icon: 'location_on', // Optional: your notification icon
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log('Location tracking started');
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Failed to start location tracking:', error);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Stop location tracking
|
|
104
|
+
const stopTracking = async () => {
|
|
105
|
+
try {
|
|
106
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
107
|
+
await ForeGroundLocation.removeAllListeners();
|
|
108
|
+
console.log('Location tracking stopped');
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Failed to stop location tracking:', error);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Location Tracking with API Integration
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { ForeGroundLocation } from 'foreground-location';
|
|
119
|
+
|
|
120
|
+
const startTrackingWithAPI = async () => {
|
|
121
|
+
try {
|
|
122
|
+
await checkPermissions();
|
|
123
|
+
|
|
124
|
+
// Listen for location updates
|
|
125
|
+
await ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
126
|
+
console.log('Real-time location:', location);
|
|
127
|
+
// Update your UI immediately
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Listen for service status changes
|
|
131
|
+
await ForeGroundLocation.addListener('serviceStatusChanged', (status) => {
|
|
132
|
+
console.log('Service status:', status);
|
|
133
|
+
if (status.error) {
|
|
134
|
+
console.error('Service error:', status.error);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Start tracking with API integration
|
|
139
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
140
|
+
interval: 30000, // 30 seconds
|
|
141
|
+
fastestInterval: 15000,
|
|
142
|
+
priority: 'HIGH_ACCURACY',
|
|
143
|
+
notification: {
|
|
144
|
+
title: 'Location Tracking Active',
|
|
145
|
+
text: 'Sending location data to server',
|
|
146
|
+
},
|
|
147
|
+
api: {
|
|
148
|
+
url: 'https://api.yourservice.com/locations',
|
|
149
|
+
type: 'POST',
|
|
150
|
+
header: {
|
|
151
|
+
Authorization: 'Bearer your-token',
|
|
152
|
+
'Content-Type': 'application/json',
|
|
153
|
+
},
|
|
154
|
+
apiInterval: 5, // Send to API every 5 minutes
|
|
155
|
+
additionalParams: {
|
|
156
|
+
userId: 'user123',
|
|
157
|
+
deviceId: 'device456',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
console.log('Location tracking with API started');
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('Failed to start tracking:', error);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## API Reference
|
|
170
|
+
|
|
171
|
+
### Methods
|
|
172
|
+
|
|
173
|
+
#### `checkPermissions(): Promise<LocationPermissionStatus>`
|
|
174
|
+
|
|
175
|
+
Check the current permission status for location services.
|
|
176
|
+
|
|
177
|
+
**Returns:**
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
interface LocationPermissionStatus {
|
|
181
|
+
location: PermissionState; // 'granted' | 'denied' | 'prompt'
|
|
182
|
+
backgroundLocation: PermissionState; // Android 10+ background location
|
|
183
|
+
notifications: PermissionState; // Android 13+ notification permission
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### `requestPermissions(): Promise<LocationPermissionStatus>`
|
|
188
|
+
|
|
189
|
+
Request location and notification permissions from the user.
|
|
190
|
+
|
|
191
|
+
#### `startForegroundLocationService(options: LocationServiceOptions): Promise<void>`
|
|
192
|
+
|
|
193
|
+
Start the foreground location service with the specified configuration.
|
|
194
|
+
|
|
195
|
+
**Parameters:**
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
interface LocationServiceOptions {
|
|
199
|
+
interval?: number; // Update interval in ms (default: 60000)
|
|
200
|
+
fastestInterval?: number; // Fastest interval in ms (default: 30000)
|
|
201
|
+
priority?: 'HIGH_ACCURACY' | 'BALANCED_POWER' | 'LOW_POWER' | 'NO_POWER';
|
|
202
|
+
notification: {
|
|
203
|
+
// REQUIRED for foreground service
|
|
204
|
+
title: string; // Notification title
|
|
205
|
+
text: string; // Notification content
|
|
206
|
+
icon?: string; // Optional icon resource name
|
|
207
|
+
};
|
|
208
|
+
enableHighAccuracy?: boolean; // Enable GPS (default: true)
|
|
209
|
+
distanceFilter?: number; // Min distance in meters for updates
|
|
210
|
+
api?: ApiServiceConfig; // Optional API integration
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### `stopForegroundLocationService(): Promise<void>`
|
|
215
|
+
|
|
216
|
+
Stop the foreground location service and clear all location tracking.
|
|
217
|
+
|
|
218
|
+
#### `isServiceRunning(): Promise<{ isRunning: boolean }>`
|
|
219
|
+
|
|
220
|
+
Check if the location service is currently running.
|
|
221
|
+
|
|
222
|
+
#### `getCurrentLocation(): Promise<LocationResult>`
|
|
223
|
+
|
|
224
|
+
Get a single location update without starting the service.
|
|
225
|
+
|
|
226
|
+
**Returns:**
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
interface LocationResult {
|
|
230
|
+
latitude: number;
|
|
231
|
+
longitude: number;
|
|
232
|
+
accuracy: number;
|
|
233
|
+
altitude?: number;
|
|
234
|
+
bearing?: number;
|
|
235
|
+
speed?: number;
|
|
236
|
+
timestamp: string; // ISO 8601 format
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### `updateLocationSettings(options: LocationServiceOptions): Promise<void>`
|
|
241
|
+
|
|
242
|
+
Update the location service settings while it's running.
|
|
243
|
+
|
|
244
|
+
#### `getApiServiceStatus(): Promise<ApiServiceStatus>`
|
|
245
|
+
|
|
246
|
+
Get the current status of the API service integration.
|
|
247
|
+
|
|
248
|
+
**Returns:**
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
interface ApiServiceStatus {
|
|
252
|
+
isEnabled: boolean; // API service is configured and active
|
|
253
|
+
bufferSize: number; // Number of locations waiting to be sent
|
|
254
|
+
isHealthy: boolean; // Service is healthy (not in circuit breaker state)
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### `clearApiBuffers(): Promise<void>`
|
|
259
|
+
|
|
260
|
+
Clear all buffered location data waiting to be sent to the API.
|
|
261
|
+
|
|
262
|
+
#### `resetApiCircuitBreaker(): Promise<void>`
|
|
263
|
+
|
|
264
|
+
Reset the API service circuit breaker to allow requests again.
|
|
265
|
+
|
|
266
|
+
### Event Listeners
|
|
267
|
+
|
|
268
|
+
#### `addListener(eventName: 'locationUpdate', callback: (location: LocationResult) => void): Promise<PluginListenerHandle>`
|
|
269
|
+
|
|
270
|
+
Listen for real-time location updates.
|
|
271
|
+
|
|
272
|
+
#### `addListener(eventName: 'serviceStatusChanged', callback: (status: ServiceStatus) => void): Promise<PluginListenerHandle>`
|
|
273
|
+
|
|
274
|
+
Listen for service status changes.
|
|
275
|
+
|
|
276
|
+
**ServiceStatus:**
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
interface ServiceStatus {
|
|
280
|
+
isRunning: boolean;
|
|
281
|
+
error?: string; // Error message if service failed
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### `removeAllListeners(): Promise<void>`
|
|
286
|
+
|
|
287
|
+
Remove all event listeners.
|
|
288
|
+
|
|
289
|
+
### Configuration Interfaces
|
|
290
|
+
|
|
291
|
+
#### `ApiServiceConfig`
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
interface ApiServiceConfig {
|
|
295
|
+
url: string; // API endpoint URL (REQUIRED)
|
|
296
|
+
type?: 'GET' | 'POST' | 'PUT' | 'PATCH'; // HTTP method (default: 'POST')
|
|
297
|
+
header?: Record<string, string>; // HTTP headers
|
|
298
|
+
additionalParams?: Record<string, any>; // Extra parameters for request body
|
|
299
|
+
apiInterval?: number; // Send interval in minutes (default: 5)
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Error Handling
|
|
304
|
+
|
|
305
|
+
The plugin provides consistent error codes:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
const ERROR_CODES = {
|
|
309
|
+
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
310
|
+
INVALID_NOTIFICATION: 'INVALID_NOTIFICATION',
|
|
311
|
+
INVALID_PARAMETERS: 'INVALID_PARAMETERS',
|
|
312
|
+
LOCATION_SERVICES_DISABLED: 'LOCATION_SERVICES_DISABLED',
|
|
313
|
+
UNSUPPORTED_PLATFORM: 'UNSUPPORTED_PLATFORM',
|
|
314
|
+
} as const;
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Advanced Usage Examples
|
|
318
|
+
|
|
319
|
+
### Service Monitoring
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { ForeGroundLocation } from 'foreground-location';
|
|
323
|
+
|
|
324
|
+
const monitorLocationService = async () => {
|
|
325
|
+
// Check if service is already running
|
|
326
|
+
const { isRunning } = await ForeGroundLocation.isServiceRunning();
|
|
327
|
+
console.log('Service running:', isRunning);
|
|
328
|
+
|
|
329
|
+
// Monitor API service status
|
|
330
|
+
const apiStatus = await ForeGroundLocation.getApiServiceStatus();
|
|
331
|
+
console.log('API Status:', {
|
|
332
|
+
enabled: apiStatus.isEnabled,
|
|
333
|
+
buffer: apiStatus.bufferSize,
|
|
334
|
+
healthy: apiStatus.isHealthy,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Listen for service status changes
|
|
338
|
+
await ForeGroundLocation.addListener('serviceStatusChanged', (status) => {
|
|
339
|
+
if (status.error) {
|
|
340
|
+
console.error('Service error:', status.error);
|
|
341
|
+
// Handle service errors (restart, notify user, etc.)
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Power-Optimized Tracking
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
const startEfficientTracking = async () => {
|
|
351
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
352
|
+
interval: 120000, // 2 minutes for battery saving
|
|
353
|
+
fastestInterval: 60000, // Minimum 1 minute
|
|
354
|
+
priority: 'BALANCED_POWER', // Balance accuracy and battery
|
|
355
|
+
distanceFilter: 10, // Only update if moved 10+ meters
|
|
356
|
+
enableHighAccuracy: false, // Disable GPS for power saving
|
|
357
|
+
notification: {
|
|
358
|
+
title: 'Efficient Tracking',
|
|
359
|
+
text: 'Battery-optimized location tracking',
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
};
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Dynamic Settings Update
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const switchToHighAccuracy = async () => {
|
|
369
|
+
try {
|
|
370
|
+
// Update settings without stopping the service
|
|
371
|
+
await ForeGroundLocation.updateLocationSettings({
|
|
372
|
+
interval: 5000,
|
|
373
|
+
fastestInterval: 2000,
|
|
374
|
+
priority: 'HIGH_ACCURACY',
|
|
375
|
+
enableHighAccuracy: true,
|
|
376
|
+
notification: {
|
|
377
|
+
title: 'High Accuracy Mode',
|
|
378
|
+
text: 'Precise location tracking active',
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
console.log('Switched to high accuracy mode');
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('Failed to update settings:', error);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### API Integration with Error Handling
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const robustApiTracking = async () => {
|
|
392
|
+
try {
|
|
393
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
394
|
+
interval: 30000,
|
|
395
|
+
notification: {
|
|
396
|
+
title: 'Location Sync',
|
|
397
|
+
text: 'Syncing location data to cloud',
|
|
398
|
+
},
|
|
399
|
+
api: {
|
|
400
|
+
url: 'https://api.yourservice.com/locations',
|
|
401
|
+
type: 'POST',
|
|
402
|
+
header: {
|
|
403
|
+
Authorization: 'Bearer ' + (await getAuthToken()),
|
|
404
|
+
'Content-Type': 'application/json',
|
|
405
|
+
'X-Device-ID': await getDeviceId(),
|
|
406
|
+
},
|
|
407
|
+
apiInterval: 3, // Send every 3 minutes
|
|
408
|
+
additionalParams: {
|
|
409
|
+
source: 'mobile-app',
|
|
410
|
+
version: '1.0.0',
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Monitor API health
|
|
416
|
+
setInterval(async () => {
|
|
417
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
418
|
+
|
|
419
|
+
if (!status.isHealthy) {
|
|
420
|
+
console.warn('API service unhealthy, resetting...');
|
|
421
|
+
await ForeGroundLocation.resetApiCircuitBreaker();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (status.bufferSize > 50) {
|
|
425
|
+
console.warn('Buffer getting full:', status.bufferSize);
|
|
426
|
+
// Optionally clear old data or adjust settings
|
|
427
|
+
}
|
|
428
|
+
}, 60000); // Check every minute
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.error('API tracking setup failed:', error);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Implementation Patterns
|
|
436
|
+
|
|
437
|
+
### React/Ionic Hook
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { useEffect, useState } from 'react';
|
|
441
|
+
import { ForeGroundLocation, LocationResult } from 'foreground-location';
|
|
442
|
+
|
|
443
|
+
export const useLocationTracking = () => {
|
|
444
|
+
const [isTracking, setIsTracking] = useState(false);
|
|
445
|
+
const [currentLocation, setCurrentLocation] = useState<LocationResult | null>(null);
|
|
446
|
+
const [error, setError] = useState<string | null>(null);
|
|
447
|
+
|
|
448
|
+
const startTracking = async (options: LocationServiceOptions) => {
|
|
449
|
+
try {
|
|
450
|
+
setError(null);
|
|
451
|
+
|
|
452
|
+
// Check permissions
|
|
453
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
454
|
+
if (permissions.location !== 'granted') {
|
|
455
|
+
const requested = await ForeGroundLocation.requestPermissions();
|
|
456
|
+
if (requested.location !== 'granted') {
|
|
457
|
+
throw new Error('Location permission denied');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Add listeners
|
|
462
|
+
await ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
463
|
+
setCurrentLocation(location);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
await ForeGroundLocation.addListener('serviceStatusChanged', (status) => {
|
|
467
|
+
setIsTracking(status.isRunning);
|
|
468
|
+
if (status.error) {
|
|
469
|
+
setError(status.error);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Start service
|
|
474
|
+
await ForeGroundLocation.startForegroundLocationService(options);
|
|
475
|
+
setIsTracking(true);
|
|
476
|
+
} catch (err) {
|
|
477
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
478
|
+
setIsTracking(false);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const stopTracking = async () => {
|
|
483
|
+
try {
|
|
484
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
485
|
+
await ForeGroundLocation.removeAllListeners();
|
|
486
|
+
setIsTracking(false);
|
|
487
|
+
setCurrentLocation(null);
|
|
488
|
+
} catch (err) {
|
|
489
|
+
console.error('Failed to stop tracking:', err);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Cleanup on unmount
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
return () => {
|
|
496
|
+
stopTracking();
|
|
497
|
+
};
|
|
498
|
+
}, []);
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
isTracking,
|
|
502
|
+
currentLocation,
|
|
503
|
+
error,
|
|
504
|
+
startTracking,
|
|
505
|
+
stopTracking,
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Angular Service
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import { Injectable } from '@angular/core';
|
|
514
|
+
import { BehaviorSubject, Observable } from 'rxjs';
|
|
515
|
+
import { ForeGroundLocation, LocationResult, ServiceStatus } from 'foreground-location';
|
|
516
|
+
|
|
517
|
+
@Injectable({
|
|
518
|
+
providedIn: 'root',
|
|
519
|
+
})
|
|
520
|
+
export class LocationTrackingService {
|
|
521
|
+
private locationSubject = new BehaviorSubject<LocationResult | null>(null);
|
|
522
|
+
private statusSubject = new BehaviorSubject<ServiceStatus>({ isRunning: false });
|
|
523
|
+
|
|
524
|
+
public location$: Observable<LocationResult | null> = this.locationSubject.asObservable();
|
|
525
|
+
public status$: Observable<ServiceStatus> = this.statusSubject.asObservable();
|
|
526
|
+
|
|
527
|
+
async startTracking(options: LocationServiceOptions): Promise<void> {
|
|
528
|
+
try {
|
|
529
|
+
// Request permissions
|
|
530
|
+
const permissions = await ForeGroundLocation.requestPermissions();
|
|
531
|
+
if (permissions.location !== 'granted') {
|
|
532
|
+
throw new Error('Location permission required');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Setup listeners
|
|
536
|
+
await ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
537
|
+
this.locationSubject.next(location);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
await ForeGroundLocation.addListener('serviceStatusChanged', (status) => {
|
|
541
|
+
this.statusSubject.next(status);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Start service
|
|
545
|
+
await ForeGroundLocation.startForegroundLocationService(options);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error('Failed to start location tracking:', error);
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async stopTracking(): Promise<void> {
|
|
553
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
554
|
+
await ForeGroundLocation.removeAllListeners();
|
|
555
|
+
this.statusSubject.next({ isRunning: false });
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async getCurrentLocation(): Promise<LocationResult> {
|
|
559
|
+
return await ForeGroundLocation.getCurrentLocation();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async getApiStatus(): Promise<ApiServiceStatus> {
|
|
563
|
+
return await ForeGroundLocation.getApiServiceStatus();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Best Practices
|
|
569
|
+
|
|
570
|
+
### Permission Management
|
|
571
|
+
|
|
572
|
+
- Always check permissions before starting location services
|
|
573
|
+
- Handle permission denial gracefully with user-friendly messaging
|
|
574
|
+
- Request permissions in context when user understands why they're needed
|
|
575
|
+
|
|
576
|
+
### Battery Optimization
|
|
577
|
+
|
|
578
|
+
- Use `BALANCED_POWER` priority for general use cases
|
|
579
|
+
- Increase intervals (60+ seconds) for background tracking
|
|
580
|
+
- Set `distanceFilter` to avoid unnecessary updates
|
|
581
|
+
- Disable `enableHighAccuracy` when precise GPS isn't needed
|
|
582
|
+
|
|
583
|
+
### API Integration
|
|
584
|
+
|
|
585
|
+
- Use reasonable `apiInterval` values (5-15 minutes)
|
|
586
|
+
- Implement proper authentication token refresh
|
|
587
|
+
- Monitor `bufferSize` to prevent memory issues
|
|
588
|
+
- Handle circuit breaker states appropriately
|
|
589
|
+
|
|
590
|
+
### Error Handling
|
|
591
|
+
|
|
592
|
+
- Always wrap plugin calls in try-catch blocks
|
|
593
|
+
- Listen for `serviceStatusChanged` events
|
|
594
|
+
- Provide user feedback for service failures
|
|
595
|
+
- Implement retry logic for critical operations
|
|
596
|
+
|
|
597
|
+
## Troubleshooting
|
|
598
|
+
|
|
599
|
+
### Common Issues
|
|
600
|
+
|
|
601
|
+
| Issue | Cause | Solution |
|
|
602
|
+
| -------------------- | --------------------------- | ---------------------------------------------------- |
|
|
603
|
+
| Service not starting | Missing notification config | Provide `notification.title` and `notification.text` |
|
|
604
|
+
| Permission denied | User denied location access | Call `requestPermissions()` and handle denial |
|
|
605
|
+
| High battery usage | Too frequent updates | Increase `interval` and use `BALANCED_POWER` |
|
|
606
|
+
| API data not sending | Network/auth issues | Check API status and reset circuit breaker |
|
|
607
|
+
| Location inaccurate | Wrong priority setting | Use `HIGH_ACCURACY` and `enableHighAccuracy: true` |
|
|
608
|
+
|
|
609
|
+
### Debug Information
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
// Get comprehensive debug info
|
|
613
|
+
const debugInfo = async () => {
|
|
614
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
615
|
+
const serviceStatus = await ForeGroundLocation.isServiceRunning();
|
|
616
|
+
const apiStatus = await ForeGroundLocation.getApiServiceStatus();
|
|
617
|
+
|
|
618
|
+
console.log('Debug Info:', {
|
|
619
|
+
permissions,
|
|
620
|
+
serviceRunning: serviceStatus.isRunning,
|
|
621
|
+
apiEnabled: apiStatus.isEnabled,
|
|
622
|
+
bufferSize: apiStatus.bufferSize,
|
|
623
|
+
apiHealthy: apiStatus.isHealthy,
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
## Platform Differences
|
|
629
|
+
|
|
630
|
+
### Android
|
|
631
|
+
|
|
632
|
+
- Requires foreground service with persistent notification
|
|
633
|
+
- Background location permission needed for Android 10+
|
|
634
|
+
- Notification permission required for Android 13+
|
|
635
|
+
- Uses Google Play Services Fused Location Provider
|
|
636
|
+
|
|
637
|
+
### iOS
|
|
638
|
+
|
|
639
|
+
- Uses Core Location framework
|
|
640
|
+
- Requires usage description in Info.plist
|
|
641
|
+
- Background location requires additional entitlements
|
|
642
|
+
- Location accuracy may be limited based on authorization level
|
|
643
|
+
|
|
644
|
+
### Web
|
|
645
|
+
|
|
646
|
+
- Stub implementation that throws `unavailable` errors
|
|
647
|
+
- Use Capacitor's Geolocation plugin for web support
|
|
648
|
+
|
|
649
|
+
## Data Privacy
|
|
650
|
+
|
|
651
|
+
### Location Data Handling
|
|
652
|
+
|
|
653
|
+
- Location data is only stored temporarily in memory buffers
|
|
654
|
+
- API integration sends data to your specified endpoints only
|
|
655
|
+
- No data is sent to plugin developers or third parties
|
|
656
|
+
- Clear buffers regularly to minimize data retention
|
|
657
|
+
|
|
658
|
+
### Compliance Considerations
|
|
659
|
+
|
|
660
|
+
- Ensure your API endpoints comply with GDPR, CCPA, etc.
|
|
661
|
+
- Implement user consent mechanisms in your app
|
|
662
|
+
- Provide clear privacy notices about location tracking
|
|
663
|
+
- Allow users to opt-out and delete their data
|
|
664
|
+
|
|
665
|
+
## Performance Monitoring
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
// Monitor performance metrics
|
|
669
|
+
const monitorPerformance = async () => {
|
|
670
|
+
setInterval(async () => {
|
|
671
|
+
const apiStatus = await ForeGroundLocation.getApiServiceStatus();
|
|
672
|
+
const serviceStatus = await ForeGroundLocation.isServiceRunning();
|
|
673
|
+
|
|
674
|
+
// Log metrics for monitoring
|
|
675
|
+
console.log('Performance Metrics:', {
|
|
676
|
+
timestamp: new Date().toISOString(),
|
|
677
|
+
serviceRunning: serviceStatus.isRunning,
|
|
678
|
+
apiBufferSize: apiStatus.bufferSize,
|
|
679
|
+
memoryUsage: (performance as any).memory?.usedJSHeapSize,
|
|
680
|
+
});
|
|
681
|
+
}, 300000); // Every 5 minutes
|
|
682
|
+
};
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
## Migration Guide
|
|
686
|
+
|
|
687
|
+
### From version 0.0.x to 1.0.x
|
|
688
|
+
|
|
689
|
+
- Update method names to use `startForegroundLocationService()` instead of `startLocationTracking()`
|
|
690
|
+
- Add required `notification` configuration
|
|
691
|
+
- Update API configuration structure
|
|
692
|
+
- Handle new permission states
|
|
693
|
+
|
|
694
|
+
## Type Definitions
|
|
695
|
+
|
|
696
|
+
### Interfaces
|
|
697
|
+
|
|
698
|
+
#### LocationPermissionStatus
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
interface LocationPermissionStatus {
|
|
702
|
+
/**
|
|
703
|
+
* Fine and coarse location permission status
|
|
704
|
+
*/
|
|
705
|
+
location: PermissionState;
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Background location permission status (Android 10+)
|
|
709
|
+
*/
|
|
710
|
+
backgroundLocation: PermissionState;
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Notification permission status (Android 13+)
|
|
714
|
+
*/
|
|
715
|
+
notifications: PermissionState;
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
#### LocationServiceOptions
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
interface LocationServiceOptions {
|
|
723
|
+
/**
|
|
724
|
+
* Update interval in milliseconds
|
|
725
|
+
* @default 60000
|
|
726
|
+
* @minimum 1000
|
|
727
|
+
*/
|
|
728
|
+
interval?: number;
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Fastest update interval in milliseconds
|
|
732
|
+
* @default 30000
|
|
733
|
+
* @minimum 1000
|
|
734
|
+
*/
|
|
735
|
+
fastestInterval?: number;
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Location accuracy priority
|
|
739
|
+
* @default 'HIGH_ACCURACY'
|
|
740
|
+
*/
|
|
741
|
+
priority?: 'HIGH_ACCURACY' | 'BALANCED_POWER' | 'LOW_POWER' | 'NO_POWER';
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Notification configuration for foreground service (REQUIRED)
|
|
745
|
+
*/
|
|
746
|
+
notification: {
|
|
747
|
+
/**
|
|
748
|
+
* Notification title (REQUIRED)
|
|
749
|
+
*/
|
|
750
|
+
title: string;
|
|
751
|
+
/**
|
|
752
|
+
* Notification text/content (REQUIRED)
|
|
753
|
+
*/
|
|
754
|
+
text: string;
|
|
755
|
+
/**
|
|
756
|
+
* Optional notification icon resource name
|
|
757
|
+
*/
|
|
758
|
+
icon?: string;
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Enable high accuracy mode
|
|
763
|
+
* @default true
|
|
764
|
+
*/
|
|
765
|
+
enableHighAccuracy?: boolean;
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Minimum distance in meters to trigger update
|
|
769
|
+
*/
|
|
770
|
+
distanceFilter?: number;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* API service configuration (optional)
|
|
774
|
+
* If provided, location data will be sent to the specified API endpoint in batches
|
|
775
|
+
*/
|
|
776
|
+
api?: ApiServiceConfig;
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
#### ApiServiceConfig
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
interface ApiServiceConfig {
|
|
784
|
+
/**
|
|
785
|
+
* API endpoint URL (REQUIRED if api config is provided)
|
|
786
|
+
*/
|
|
787
|
+
url: string;
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* HTTP method to use
|
|
791
|
+
* @default 'POST'
|
|
792
|
+
*/
|
|
793
|
+
type?: 'GET' | 'POST' | 'PUT' | 'PATCH';
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* HTTP headers to include in API requests
|
|
797
|
+
*/
|
|
798
|
+
header?: Record<string, string>;
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Additional parameters to include in API request body
|
|
802
|
+
*/
|
|
803
|
+
additionalParams?: Record<string, any>;
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Interval in minutes for sending batched location data to API
|
|
807
|
+
* @default 5
|
|
808
|
+
* @minimum 1
|
|
809
|
+
*/
|
|
810
|
+
apiInterval?: number;
|
|
811
|
+
}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
#### ApiServiceStatus
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
interface ApiServiceStatus {
|
|
818
|
+
/**
|
|
819
|
+
* Whether API service is enabled and configured
|
|
820
|
+
*/
|
|
821
|
+
isEnabled: boolean;
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Number of location points in buffer waiting to be sent
|
|
825
|
+
*/
|
|
826
|
+
bufferSize: number;
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Whether API service is healthy (not in circuit breaker state)
|
|
830
|
+
*/
|
|
831
|
+
isHealthy: boolean;
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
#### LocationResult
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
interface LocationResult {
|
|
839
|
+
/**
|
|
840
|
+
* Latitude in decimal degrees
|
|
841
|
+
*/
|
|
842
|
+
latitude: number;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Longitude in decimal degrees
|
|
846
|
+
*/
|
|
847
|
+
longitude: number;
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Accuracy of the location in meters
|
|
851
|
+
*/
|
|
852
|
+
accuracy: number;
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Altitude in meters (if available)
|
|
856
|
+
*/
|
|
857
|
+
altitude?: number;
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Bearing in degrees (if available)
|
|
861
|
+
*/
|
|
862
|
+
bearing?: number;
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Speed in meters per second (if available)
|
|
866
|
+
*/
|
|
867
|
+
speed?: number;
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Timestamp in ISO 8601 format
|
|
871
|
+
*/
|
|
872
|
+
timestamp: string;
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
#### ServiceStatus
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
interface ServiceStatus {
|
|
880
|
+
/**
|
|
881
|
+
* Whether the service is currently running
|
|
882
|
+
*/
|
|
883
|
+
isRunning: boolean;
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Error message if service failed
|
|
887
|
+
*/
|
|
888
|
+
error?: string;
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Type Aliases
|
|
893
|
+
|
|
894
|
+
#### PermissionState
|
|
895
|
+
|
|
896
|
+
```typescript
|
|
897
|
+
type PermissionState = 'prompt' | 'prompt-with-rationale' | 'granted' | 'denied';
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
#### ErrorCode
|
|
901
|
+
|
|
902
|
+
```typescript
|
|
903
|
+
type ErrorCode =
|
|
904
|
+
| 'PERMISSION_DENIED'
|
|
905
|
+
| 'INVALID_NOTIFICATION'
|
|
906
|
+
| 'INVALID_PARAMETERS'
|
|
907
|
+
| 'LOCATION_SERVICES_DISABLED'
|
|
908
|
+
| 'UNSUPPORTED_PLATFORM';
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
#### PluginListenerHandle
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
interface PluginListenerHandle {
|
|
915
|
+
remove: () => Promise<void>;
|
|
916
|
+
}
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
## Support
|
|
920
|
+
|
|
921
|
+
- **GitHub Issues**: [Report bugs and feature requests](https://github.com/xconcepts17/foreground-location/issues)
|
|
922
|
+
- **Documentation**: [Complete setup guide](./docs/setup-and-examples.md)
|
|
923
|
+
- **Examples**: [Implementation examples](./docs/setup-and-examples.md#complete-implementation-example)
|
|
924
|
+
|
|
925
|
+
## License
|
|
926
|
+
|
|
927
|
+
MIT License - see [LICENSE](./LICENSE) file for details.
|
|
928
|
+
|
|
929
|
+
## Contributing
|
|
930
|
+
|
|
931
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution guidelines and development setup.
|