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
|
@@ -0,0 +1,2851 @@
|
|
|
1
|
+
# Setup and Examples Guide
|
|
2
|
+
|
|
3
|
+
This comprehensive guide provides detailed setup instructions and practical examples for the Capacitor Foreground Location plugin. Learn how to implement location tracking with foreground services and optional API integration.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation and Setup](#installation-and-setup)
|
|
8
|
+
- [Permission Management](#permission-management)
|
|
9
|
+
- [Basic Implementation](#basic-implementation)
|
|
10
|
+
- [Advanced Features](#advanced-features)
|
|
11
|
+
- [API Integration Examples](#api-integration-examples)
|
|
12
|
+
- [Framework-Specific Examples](#framework-specific-examples)
|
|
13
|
+
- [Production Considerations](#production-considerations)
|
|
14
|
+
- [Troubleshooting](#troubleshooting)
|
|
15
|
+
|
|
16
|
+
## Installation and Setup
|
|
17
|
+
|
|
18
|
+
### 1. Install the Plugin
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install foreground-location
|
|
22
|
+
npx cap sync
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Platform Configuration
|
|
26
|
+
|
|
27
|
+
#### Android Setup
|
|
28
|
+
|
|
29
|
+
**Add permissions to `android/app/src/main/AndroidManifest.xml`:**
|
|
30
|
+
|
|
31
|
+
```xml
|
|
32
|
+
<!-- Location permissions -->
|
|
33
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
34
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
35
|
+
|
|
36
|
+
<!-- Foreground service permissions -->
|
|
37
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
38
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
39
|
+
|
|
40
|
+
<!-- Notification permission (Android 13+) -->
|
|
41
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
42
|
+
|
|
43
|
+
<!-- Internet for API integration -->
|
|
44
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Optional: Add custom notification icon to `android/app/src/main/res/drawable/`:**
|
|
48
|
+
|
|
49
|
+
```xml
|
|
50
|
+
<!-- ic_location_tracking.xml -->
|
|
51
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
52
|
+
android:width="24dp"
|
|
53
|
+
android:height="24dp"
|
|
54
|
+
android:viewportWidth="24"
|
|
55
|
+
android:viewportHeight="24"
|
|
56
|
+
android:tint="?attr/colorOnPrimary">
|
|
57
|
+
<path
|
|
58
|
+
android:fillColor="@android:color/white"
|
|
59
|
+
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
|
|
60
|
+
</vector>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### iOS Setup
|
|
64
|
+
|
|
65
|
+
**Add usage descriptions to `ios/App/App/Info.plist`:**
|
|
66
|
+
|
|
67
|
+
```xml
|
|
68
|
+
<dict>
|
|
69
|
+
<!-- Other keys... -->
|
|
70
|
+
|
|
71
|
+
<!-- Location permissions -->
|
|
72
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
73
|
+
<string>This app needs location access to track your position while actively using the app</string>
|
|
74
|
+
|
|
75
|
+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
|
76
|
+
<string>This app needs continuous location access to provide location-based services</string>
|
|
77
|
+
|
|
78
|
+
<!-- Background modes for location tracking -->
|
|
79
|
+
<key>UIBackgroundModes</key>
|
|
80
|
+
<array>
|
|
81
|
+
<string>location</string>
|
|
82
|
+
</array>
|
|
83
|
+
|
|
84
|
+
<!-- Optional: Support for background app refresh -->
|
|
85
|
+
<key>UIBackgroundRefreshStatusAvailable</key>
|
|
86
|
+
<true/>
|
|
87
|
+
</dict>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Build and Sync
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Build the project
|
|
94
|
+
npm run build
|
|
95
|
+
|
|
96
|
+
# Sync with native platforms
|
|
97
|
+
npx cap sync
|
|
98
|
+
|
|
99
|
+
# Open in native IDEs for testing
|
|
100
|
+
npx cap open ios
|
|
101
|
+
npx cap open android
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Permission Management
|
|
105
|
+
|
|
106
|
+
### Basic Permission Handling
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { ForeGroundLocation, LocationPermissionStatus } from 'foreground-location';
|
|
110
|
+
|
|
111
|
+
class PermissionManager {
|
|
112
|
+
/**
|
|
113
|
+
* Check current permission status
|
|
114
|
+
*/
|
|
115
|
+
async checkPermissions(): Promise<LocationPermissionStatus> {
|
|
116
|
+
try {
|
|
117
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
118
|
+
console.log('Current permissions:', {
|
|
119
|
+
location: permissions.location,
|
|
120
|
+
backgroundLocation: permissions.backgroundLocation,
|
|
121
|
+
notifications: permissions.notifications,
|
|
122
|
+
});
|
|
123
|
+
return permissions;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('Failed to check permissions:', error);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Request all required permissions
|
|
132
|
+
*/
|
|
133
|
+
async requestPermissions(): Promise<boolean> {
|
|
134
|
+
try {
|
|
135
|
+
const permissions = await ForeGroundLocation.requestPermissions();
|
|
136
|
+
|
|
137
|
+
// Check if basic location permission is granted
|
|
138
|
+
if (permissions.location !== 'granted') {
|
|
139
|
+
throw new Error('Location permission is required for this feature');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Log permission status
|
|
143
|
+
console.log('Permission results:', {
|
|
144
|
+
location: permissions.location,
|
|
145
|
+
backgroundLocation: permissions.backgroundLocation,
|
|
146
|
+
notifications: permissions.notifications,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return true;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Permission request failed:', error);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Handle permission states with user-friendly messages
|
|
158
|
+
*/
|
|
159
|
+
async handlePermissions(): Promise<boolean> {
|
|
160
|
+
const permissions = await this.checkPermissions();
|
|
161
|
+
|
|
162
|
+
switch (permissions.location) {
|
|
163
|
+
case 'granted':
|
|
164
|
+
console.log('✅ Location permission granted');
|
|
165
|
+
return true;
|
|
166
|
+
|
|
167
|
+
case 'denied':
|
|
168
|
+
console.log('❌ Location permission denied permanently');
|
|
169
|
+
// Show instructions to enable in settings
|
|
170
|
+
this.showPermissionInstructions();
|
|
171
|
+
return false;
|
|
172
|
+
|
|
173
|
+
case 'prompt':
|
|
174
|
+
console.log('🔄 Requesting location permission...');
|
|
175
|
+
return await this.requestPermissions();
|
|
176
|
+
|
|
177
|
+
default:
|
|
178
|
+
console.log('❓ Unknown permission state');
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private showPermissionInstructions(): void {
|
|
184
|
+
// Implement UI to guide user to app settings
|
|
185
|
+
alert('Please enable location permissions in your device settings to use this feature.');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Basic Implementation
|
|
191
|
+
|
|
192
|
+
### Simple Location Tracking
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { ForeGroundLocation, LocationResult, ServiceStatus } from 'foreground-location';
|
|
196
|
+
|
|
197
|
+
class BasicLocationService {
|
|
198
|
+
private permissionManager = new PermissionManager();
|
|
199
|
+
private locationListener: any = null;
|
|
200
|
+
private statusListener: any = null;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Start basic location tracking
|
|
204
|
+
*/
|
|
205
|
+
async startTracking(): Promise<void> {
|
|
206
|
+
try {
|
|
207
|
+
// 1. Handle permissions
|
|
208
|
+
const hasPermissions = await this.permissionManager.handlePermissions();
|
|
209
|
+
if (!hasPermissions) {
|
|
210
|
+
throw new Error('Location permissions required');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 2. Set up event listeners
|
|
214
|
+
await this.setupListeners();
|
|
215
|
+
|
|
216
|
+
// 3. Start the foreground service
|
|
217
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
218
|
+
interval: 30000, // Update every 30 seconds
|
|
219
|
+
fastestInterval: 15000, // But not faster than 15 seconds
|
|
220
|
+
priority: 'HIGH_ACCURACY',
|
|
221
|
+
notification: {
|
|
222
|
+
title: 'Location Tracking Active',
|
|
223
|
+
text: 'Tracking your location in the background',
|
|
224
|
+
icon: 'ic_location_tracking', // Optional custom icon
|
|
225
|
+
},
|
|
226
|
+
enableHighAccuracy: true,
|
|
227
|
+
distanceFilter: 5, // Only update if moved 5+ meters
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
console.log('✅ Location tracking started successfully');
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error('❌ Failed to start location tracking:', error);
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Set up event listeners for location and status updates
|
|
239
|
+
*/
|
|
240
|
+
private async setupListeners(): Promise<void> {
|
|
241
|
+
// Listen for location updates
|
|
242
|
+
this.locationListener = await ForeGroundLocation.addListener(
|
|
243
|
+
'locationUpdate',
|
|
244
|
+
this.handleLocationUpdate.bind(this),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Listen for service status changes
|
|
248
|
+
this.statusListener = await ForeGroundLocation.addListener(
|
|
249
|
+
'serviceStatusChanged',
|
|
250
|
+
this.handleStatusChange.bind(this),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Handle incoming location updates
|
|
256
|
+
*/
|
|
257
|
+
private handleLocationUpdate(location: LocationResult): void {
|
|
258
|
+
console.log('📍 New location update:', {
|
|
259
|
+
coordinates: `${location.latitude}, ${location.longitude}`,
|
|
260
|
+
accuracy: `${location.accuracy}m`,
|
|
261
|
+
timestamp: location.timestamp,
|
|
262
|
+
speed: location.speed ? `${location.speed} m/s` : 'N/A',
|
|
263
|
+
altitude: location.altitude ? `${location.altitude}m` : 'N/A',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Update your app's UI or store the location
|
|
267
|
+
this.updateLocationDisplay(location);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Handle service status changes
|
|
272
|
+
*/
|
|
273
|
+
private handleStatusChange(status: ServiceStatus): void {
|
|
274
|
+
console.log('🔄 Service status changed:', status);
|
|
275
|
+
|
|
276
|
+
if (status.error) {
|
|
277
|
+
console.error('❌ Service error:', status.error);
|
|
278
|
+
this.handleServiceError(status.error);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!status.isRunning) {
|
|
282
|
+
console.log('⏹️ Location service stopped');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Handle service errors
|
|
288
|
+
*/
|
|
289
|
+
private handleServiceError(error: string): void {
|
|
290
|
+
// Implement error handling based on your app's needs
|
|
291
|
+
switch (error) {
|
|
292
|
+
case 'LOCATION_SERVICES_DISABLED':
|
|
293
|
+
alert('Please enable location services in your device settings');
|
|
294
|
+
break;
|
|
295
|
+
case 'PERMISSION_DENIED':
|
|
296
|
+
alert('Location permission was revoked. Please re-enable it.');
|
|
297
|
+
break;
|
|
298
|
+
default:
|
|
299
|
+
console.error('Unknown service error:', error);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Update your app's location display
|
|
305
|
+
*/
|
|
306
|
+
private updateLocationDisplay(location: LocationResult): void {
|
|
307
|
+
// Implement based on your UI framework
|
|
308
|
+
// For example, update a map, show coordinates, etc.
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Stop location tracking
|
|
313
|
+
*/
|
|
314
|
+
async stopTracking(): Promise<void> {
|
|
315
|
+
try {
|
|
316
|
+
// Stop the location service
|
|
317
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
318
|
+
|
|
319
|
+
// Remove event listeners
|
|
320
|
+
if (this.locationListener) {
|
|
321
|
+
this.locationListener.remove();
|
|
322
|
+
this.locationListener = null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (this.statusListener) {
|
|
326
|
+
this.statusListener.remove();
|
|
327
|
+
this.statusListener = null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log('✅ Location tracking stopped successfully');
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('❌ Failed to stop location tracking:', error);
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get current service status
|
|
339
|
+
*/
|
|
340
|
+
async getServiceStatus(): Promise<{ isRunning: boolean }> {
|
|
341
|
+
return await ForeGroundLocation.isServiceRunning();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get a single location update without starting the service
|
|
346
|
+
*/
|
|
347
|
+
async getCurrentLocation(): Promise<LocationResult> {
|
|
348
|
+
const hasPermissions = await this.permissionManager.handlePermissions();
|
|
349
|
+
if (!hasPermissions) {
|
|
350
|
+
throw new Error('Location permissions required');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return await ForeGroundLocation.getCurrentLocation();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Advanced Features
|
|
359
|
+
|
|
360
|
+
### Power Management and Dynamic Settings
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
class AdvancedLocationService extends BasicLocationService {
|
|
364
|
+
private currentMode: 'efficient' | 'accurate' | 'custom' = 'efficient';
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Start with battery-efficient settings
|
|
368
|
+
*/
|
|
369
|
+
async startEfficientMode(): Promise<void> {
|
|
370
|
+
await this.startTracking();
|
|
371
|
+
|
|
372
|
+
await ForeGroundLocation.updateLocationSettings({
|
|
373
|
+
interval: 120000, // 2 minutes
|
|
374
|
+
fastestInterval: 60000, // 1 minute minimum
|
|
375
|
+
priority: 'BALANCED_POWER',
|
|
376
|
+
enableHighAccuracy: false,
|
|
377
|
+
distanceFilter: 20, // 20 meters
|
|
378
|
+
notification: {
|
|
379
|
+
title: 'Efficient Tracking',
|
|
380
|
+
text: 'Battery-optimized location tracking',
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
this.currentMode = 'efficient';
|
|
385
|
+
console.log('🔋 Switched to efficient mode');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Switch to high accuracy mode
|
|
390
|
+
*/
|
|
391
|
+
async switchToAccurateMode(): Promise<void> {
|
|
392
|
+
try {
|
|
393
|
+
await ForeGroundLocation.updateLocationSettings({
|
|
394
|
+
interval: 10000, // 10 seconds
|
|
395
|
+
fastestInterval: 5000, // 5 seconds
|
|
396
|
+
priority: 'HIGH_ACCURACY',
|
|
397
|
+
enableHighAccuracy: true,
|
|
398
|
+
distanceFilter: 1, // 1 meter
|
|
399
|
+
notification: {
|
|
400
|
+
title: 'High Accuracy Mode',
|
|
401
|
+
text: 'Precise location tracking active',
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
this.currentMode = 'accurate';
|
|
406
|
+
console.log('🎯 Switched to accurate mode');
|
|
407
|
+
} catch (error) {
|
|
408
|
+
console.error('Failed to switch to accurate mode:', error);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Automatically adjust settings based on speed
|
|
414
|
+
*/
|
|
415
|
+
private handleLocationUpdate(location: LocationResult): void {
|
|
416
|
+
super.handleLocationUpdate(location);
|
|
417
|
+
|
|
418
|
+
// Auto-adjust based on movement speed
|
|
419
|
+
if (location.speed !== undefined) {
|
|
420
|
+
if (location.speed > 15 && this.currentMode !== 'accurate') {
|
|
421
|
+
// High speed detected, switch to accurate mode
|
|
422
|
+
this.switchToAccurateMode();
|
|
423
|
+
} else if (location.speed < 2 && this.currentMode !== 'efficient') {
|
|
424
|
+
// Stationary or slow movement, switch to efficient mode
|
|
425
|
+
this.startEfficientMode();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get comprehensive service information
|
|
432
|
+
*/
|
|
433
|
+
async getServiceInfo(): Promise<any> {
|
|
434
|
+
const [serviceStatus, apiStatus] = await Promise.all([
|
|
435
|
+
ForeGroundLocation.isServiceRunning(),
|
|
436
|
+
ForeGroundLocation.getApiServiceStatus(),
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
service: serviceStatus,
|
|
441
|
+
api: apiStatus,
|
|
442
|
+
mode: this.currentMode,
|
|
443
|
+
timestamp: new Date().toISOString(),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## API Integration Examples
|
|
450
|
+
|
|
451
|
+
### Basic API Integration
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
import { ForeGroundLocation, ApiServiceConfig } from 'foreground-location';
|
|
455
|
+
|
|
456
|
+
class ApiLocationService extends BasicLocationService {
|
|
457
|
+
private apiConfig: ApiServiceConfig;
|
|
458
|
+
|
|
459
|
+
constructor(apiEndpoint: string, authToken: string) {
|
|
460
|
+
super();
|
|
461
|
+
this.apiConfig = {
|
|
462
|
+
url: apiEndpoint,
|
|
463
|
+
type: 'POST',
|
|
464
|
+
header: {
|
|
465
|
+
Authorization: `Bearer ${authToken}`,
|
|
466
|
+
'Content-Type': 'application/json',
|
|
467
|
+
'X-Client': 'mobile-app',
|
|
468
|
+
},
|
|
469
|
+
apiInterval: 5, // Send data every 5 minutes
|
|
470
|
+
additionalParams: {
|
|
471
|
+
deviceId: this.getDeviceId(),
|
|
472
|
+
appVersion: '1.0.0',
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Start tracking with API integration
|
|
479
|
+
*/
|
|
480
|
+
async startApiTracking(): Promise<void> {
|
|
481
|
+
try {
|
|
482
|
+
const hasPermissions = await this.permissionManager.handlePermissions();
|
|
483
|
+
if (!hasPermissions) {
|
|
484
|
+
throw new Error('Location permissions required');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
await this.setupListeners();
|
|
488
|
+
|
|
489
|
+
// Start with API configuration
|
|
490
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
491
|
+
interval: 30000,
|
|
492
|
+
fastestInterval: 15000,
|
|
493
|
+
priority: 'HIGH_ACCURACY',
|
|
494
|
+
notification: {
|
|
495
|
+
title: 'Location Sync Active',
|
|
496
|
+
text: 'Syncing location data to server',
|
|
497
|
+
},
|
|
498
|
+
api: this.apiConfig,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Monitor API service
|
|
502
|
+
this.startApiMonitoring();
|
|
503
|
+
|
|
504
|
+
console.log('✅ API location tracking started');
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error('❌ Failed to start API tracking:', error);
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Monitor API service health
|
|
513
|
+
*/
|
|
514
|
+
private startApiMonitoring(): void {
|
|
515
|
+
setInterval(async () => {
|
|
516
|
+
try {
|
|
517
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
518
|
+
|
|
519
|
+
console.log('📊 API Status:', {
|
|
520
|
+
enabled: status.isEnabled,
|
|
521
|
+
buffer: status.bufferSize,
|
|
522
|
+
healthy: status.isHealthy,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// Handle unhealthy API service
|
|
526
|
+
if (!status.isHealthy) {
|
|
527
|
+
console.warn('⚠️ API service unhealthy, attempting reset...');
|
|
528
|
+
await ForeGroundLocation.resetApiCircuitBreaker();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Handle buffer overflow
|
|
532
|
+
if (status.bufferSize > 100) {
|
|
533
|
+
console.warn('⚠️ API buffer overflow, clearing old data...');
|
|
534
|
+
await ForeGroundLocation.clearApiBuffers();
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
console.error('Failed to monitor API status:', error);
|
|
538
|
+
}
|
|
539
|
+
}, 60000); // Check every minute
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Update API configuration
|
|
544
|
+
*/
|
|
545
|
+
async updateApiConfig(newConfig: Partial<ApiServiceConfig>): Promise<void> {
|
|
546
|
+
this.apiConfig = { ...this.apiConfig, ...newConfig };
|
|
547
|
+
|
|
548
|
+
// Restart service with new config
|
|
549
|
+
await ForeGroundLocation.updateLocationSettings({
|
|
550
|
+
api: this.apiConfig,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private getDeviceId(): string {
|
|
555
|
+
// Implement device ID generation
|
|
556
|
+
return 'device-' + Math.random().toString(36).substr(2, 9);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Advanced API Integration with Auth Refresh
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
class SecureApiLocationService extends ApiLocationService {
|
|
565
|
+
private authToken: string = '';
|
|
566
|
+
private refreshToken: string = '';
|
|
567
|
+
private tokenExpiryTime: number = 0;
|
|
568
|
+
|
|
569
|
+
constructor(apiEndpoint: string, initialAuthToken: string, refreshToken: string) {
|
|
570
|
+
super(apiEndpoint, initialAuthToken);
|
|
571
|
+
this.authToken = initialAuthToken;
|
|
572
|
+
this.refreshToken = refreshToken;
|
|
573
|
+
this.scheduleTokenRefresh();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Schedule automatic token refresh
|
|
578
|
+
*/
|
|
579
|
+
private scheduleTokenRefresh(): void {
|
|
580
|
+
setInterval(async () => {
|
|
581
|
+
if (Date.now() >= this.tokenExpiryTime - 300000) {
|
|
582
|
+
// 5 minutes before expiry
|
|
583
|
+
await this.refreshAuthToken();
|
|
584
|
+
}
|
|
585
|
+
}, 60000); // Check every minute
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Refresh authentication token
|
|
590
|
+
*/
|
|
591
|
+
private async refreshAuthToken(): Promise<void> {
|
|
592
|
+
try {
|
|
593
|
+
const response = await fetch('https://api.yourservice.com/auth/refresh', {
|
|
594
|
+
method: 'POST',
|
|
595
|
+
headers: {
|
|
596
|
+
'Content-Type': 'application/json',
|
|
597
|
+
Authorization: `Bearer ${this.refreshToken}`,
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
if (response.ok) {
|
|
602
|
+
const data = await response.json();
|
|
603
|
+
this.authToken = data.accessToken;
|
|
604
|
+
this.tokenExpiryTime = Date.now() + data.expiresIn * 1000;
|
|
605
|
+
|
|
606
|
+
// Update API configuration with new token
|
|
607
|
+
await this.updateApiConfig({
|
|
608
|
+
header: {
|
|
609
|
+
...this.apiConfig.header,
|
|
610
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
console.log('🔐 Auth token refreshed successfully');
|
|
615
|
+
} else {
|
|
616
|
+
throw new Error('Token refresh failed');
|
|
617
|
+
}
|
|
618
|
+
} catch (error) {
|
|
619
|
+
console.error('❌ Failed to refresh auth token:', error);
|
|
620
|
+
// Handle auth failure (logout user, show login screen, etc.)
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Handle API service errors with retry logic
|
|
626
|
+
*/
|
|
627
|
+
protected handleServiceError(error: string): void {
|
|
628
|
+
super.handleServiceError(error);
|
|
629
|
+
|
|
630
|
+
if (error.includes('401') || error.includes('403')) {
|
|
631
|
+
// Authentication error, attempt token refresh
|
|
632
|
+
this.refreshAuthToken();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
## Framework-Specific Examples
|
|
639
|
+
|
|
640
|
+
### React/Ionic Implementation
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
644
|
+
import {
|
|
645
|
+
IonButton,
|
|
646
|
+
IonContent,
|
|
647
|
+
IonHeader,
|
|
648
|
+
IonPage,
|
|
649
|
+
IonTitle,
|
|
650
|
+
IonToolbar,
|
|
651
|
+
IonCard,
|
|
652
|
+
IonCardContent,
|
|
653
|
+
IonCardHeader,
|
|
654
|
+
IonCardTitle,
|
|
655
|
+
IonItem,
|
|
656
|
+
IonLabel,
|
|
657
|
+
IonIcon,
|
|
658
|
+
IonBadge
|
|
659
|
+
} from '@ionic/react';
|
|
660
|
+
import { locationOutline, playOutline, stopOutline } from 'ionicons/icons';
|
|
661
|
+
import { ForeGroundLocation, LocationResult, ApiServiceStatus } from 'foreground-location';
|
|
662
|
+
|
|
663
|
+
const LocationTracker: React.FC = () => {
|
|
664
|
+
const [isTracking, setIsTracking] = useState(false);
|
|
665
|
+
const [currentLocation, setCurrentLocation] = useState<LocationResult | null>(null);
|
|
666
|
+
const [apiStatus, setApiStatus] = useState<ApiServiceStatus | null>(null);
|
|
667
|
+
const [error, setError] = useState<string | null>(null);
|
|
668
|
+
|
|
669
|
+
// Custom hook for location tracking
|
|
670
|
+
const useLocationTracking = () => {
|
|
671
|
+
const startTracking = useCallback(async () => {
|
|
672
|
+
try {
|
|
673
|
+
setError(null);
|
|
674
|
+
|
|
675
|
+
// Check permissions
|
|
676
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
677
|
+
if (permissions.location !== 'granted') {
|
|
678
|
+
const requested = await ForeGroundLocation.requestPermissions();
|
|
679
|
+
if (requested.location !== 'granted') {
|
|
680
|
+
throw new Error('Location permission denied');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Add listeners
|
|
685
|
+
await ForeGroundLocation.addListener('locationUpdate', (location: LocationResult) => {
|
|
686
|
+
setCurrentLocation(location);
|
|
687
|
+
console.log('📍 Location update:', location);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
await ForeGroundLocation.addListener('serviceStatusChanged', (status) => {
|
|
691
|
+
setIsTracking(status.isRunning);
|
|
692
|
+
if (status.error) {
|
|
693
|
+
setError(status.error);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Start service
|
|
698
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
699
|
+
interval: 10000,
|
|
700
|
+
fastestInterval: 5000,
|
|
701
|
+
priority: 'HIGH_ACCURACY',
|
|
702
|
+
notification: {
|
|
703
|
+
title: 'Location Tracking',
|
|
704
|
+
text: 'Tracking your location'
|
|
705
|
+
},
|
|
706
|
+
api: {
|
|
707
|
+
url: 'https://api.example.com/locations',
|
|
708
|
+
type: 'POST',
|
|
709
|
+
header: {
|
|
710
|
+
'Authorization': 'Bearer your-token',
|
|
711
|
+
'Content-Type': 'application/json'
|
|
712
|
+
},
|
|
713
|
+
apiInterval: 2
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
setIsTracking(true);
|
|
718
|
+
} catch (err) {
|
|
719
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
720
|
+
setIsTracking(false);
|
|
721
|
+
}
|
|
722
|
+
}, []);
|
|
723
|
+
|
|
724
|
+
const stopTracking = useCallback(async () => {
|
|
725
|
+
try {
|
|
726
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
727
|
+
await ForeGroundLocation.removeAllListeners();
|
|
728
|
+
setIsTracking(false);
|
|
729
|
+
setCurrentLocation(null);
|
|
730
|
+
setApiStatus(null);
|
|
731
|
+
} catch (err) {
|
|
732
|
+
console.error('Failed to stop tracking:', err);
|
|
733
|
+
}
|
|
734
|
+
}, []);
|
|
735
|
+
|
|
736
|
+
return { startTracking, stopTracking };
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
const { startTracking, stopTracking } = useLocationTracking();
|
|
740
|
+
|
|
741
|
+
// Monitor API status
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
if (!isTracking) return;
|
|
744
|
+
|
|
745
|
+
const checkApiStatus = async () => {
|
|
746
|
+
try {
|
|
747
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
748
|
+
setApiStatus(status);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error('Failed to check API status:', error);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const interval = setInterval(checkApiStatus, 30000);
|
|
755
|
+
checkApiStatus(); // Initial check
|
|
756
|
+
|
|
757
|
+
return () => clearInterval(interval);
|
|
758
|
+
}, [isTracking]);
|
|
759
|
+
|
|
760
|
+
// Cleanup on unmount
|
|
761
|
+
useEffect(() => {
|
|
762
|
+
return () => {
|
|
763
|
+
if (isTracking) {
|
|
764
|
+
stopTracking();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}, [isTracking, stopTracking]);
|
|
768
|
+
|
|
769
|
+
return (
|
|
770
|
+
<IonPage>
|
|
771
|
+
<IonHeader>
|
|
772
|
+
<IonToolbar>
|
|
773
|
+
<IonTitle>Location Tracker</IonTitle>
|
|
774
|
+
</IonToolbar>
|
|
775
|
+
</IonHeader>
|
|
776
|
+
|
|
777
|
+
<IonContent className="ion-padding">
|
|
778
|
+
{/* Control Buttons */}
|
|
779
|
+
<IonCard>
|
|
780
|
+
<IonCardHeader>
|
|
781
|
+
<IonCardTitle>
|
|
782
|
+
<IonIcon icon={locationOutline} /> Location Service
|
|
783
|
+
</IonCardTitle>
|
|
784
|
+
</IonCardHeader>
|
|
785
|
+
<IonCardContent>
|
|
786
|
+
<IonButton
|
|
787
|
+
expand="block"
|
|
788
|
+
color={isTracking ? "danger" : "primary"}
|
|
789
|
+
onClick={isTracking ? stopTracking : startTracking}
|
|
790
|
+
>
|
|
791
|
+
<IonIcon icon={isTracking ? stopOutline : playOutline} slot="start" />
|
|
792
|
+
{isTracking ? 'Stop Tracking' : 'Start Tracking'}
|
|
793
|
+
</IonButton>
|
|
794
|
+
|
|
795
|
+
{error && (
|
|
796
|
+
<div style={{ color: 'var(--ion-color-danger)', marginTop: '10px' }}>
|
|
797
|
+
Error: {error}
|
|
798
|
+
</div>
|
|
799
|
+
)}
|
|
800
|
+
</IonCardContent>
|
|
801
|
+
</IonCard>
|
|
802
|
+
|
|
803
|
+
{/* Current Location */}
|
|
804
|
+
{currentLocation && (
|
|
805
|
+
<IonCard>
|
|
806
|
+
<IonCardHeader>
|
|
807
|
+
<IonCardTitle>Current Location</IonCardTitle>
|
|
808
|
+
</IonCardHeader>
|
|
809
|
+
<IonCardContent>
|
|
810
|
+
<IonItem>
|
|
811
|
+
<IonLabel>
|
|
812
|
+
<h3>Coordinates</h3>
|
|
813
|
+
<p>{currentLocation.latitude.toFixed(6)}, {currentLocation.longitude.toFixed(6)}</p>
|
|
814
|
+
</IonLabel>
|
|
815
|
+
</IonItem>
|
|
816
|
+
<IonItem>
|
|
817
|
+
<IonLabel>
|
|
818
|
+
<h3>Accuracy</h3>
|
|
819
|
+
<p>{currentLocation.accuracy.toFixed(1)} meters</p>
|
|
820
|
+
</IonLabel>
|
|
821
|
+
</IonItem>
|
|
822
|
+
{currentLocation.speed !== undefined && (
|
|
823
|
+
<IonItem>
|
|
824
|
+
<IonLabel>
|
|
825
|
+
<h3>Speed</h3>
|
|
826
|
+
<p>{(currentLocation.speed * 3.6).toFixed(1)} km/h</p>
|
|
827
|
+
</IonLabel>
|
|
828
|
+
</IonItem>
|
|
829
|
+
)}
|
|
830
|
+
<IonItem>
|
|
831
|
+
<IonLabel>
|
|
832
|
+
<h3>Last Update</h3>
|
|
833
|
+
<p>{new Date(currentLocation.timestamp).toLocaleString()}</p>
|
|
834
|
+
</IonLabel>
|
|
835
|
+
</IonItem>
|
|
836
|
+
</IonCardContent>
|
|
837
|
+
</IonCard>
|
|
838
|
+
)}
|
|
839
|
+
|
|
840
|
+
{/* API Status */}
|
|
841
|
+
{apiStatus && (
|
|
842
|
+
<IonCard>
|
|
843
|
+
<IonCardHeader>
|
|
844
|
+
<IonCardTitle>API Service Status</IonCardTitle>
|
|
845
|
+
</IonCardHeader>
|
|
846
|
+
<IonCardContent>
|
|
847
|
+
<IonItem>
|
|
848
|
+
<IonLabel>Service Status</IonLabel>
|
|
849
|
+
<IonBadge color={apiStatus.isEnabled ? "success" : "medium"}>
|
|
850
|
+
{apiStatus.isEnabled ? "Enabled" : "Disabled"}
|
|
851
|
+
</IonBadge>
|
|
852
|
+
</IonItem>
|
|
853
|
+
<IonItem>
|
|
854
|
+
<IonLabel>Health Status</IonLabel>
|
|
855
|
+
<IonBadge color={apiStatus.isHealthy ? "success" : "warning"}>
|
|
856
|
+
{apiStatus.isHealthy ? "Healthy" : "Unhealthy"}
|
|
857
|
+
</IonBadge>
|
|
858
|
+
</IonItem>
|
|
859
|
+
<IonItem>
|
|
860
|
+
<IonLabel>Buffer Size</IonLabel>
|
|
861
|
+
<IonBadge color={apiStatus.bufferSize > 50 ? "warning" : "primary"}>
|
|
862
|
+
{apiStatus.bufferSize}
|
|
863
|
+
</IonBadge>
|
|
864
|
+
</IonItem>
|
|
865
|
+
</IonCardContent>
|
|
866
|
+
</IonCard>
|
|
867
|
+
)}
|
|
868
|
+
</IonContent>
|
|
869
|
+
</IonPage>
|
|
870
|
+
);
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
export default LocationTracker;
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### Angular Service Implementation
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
import { Injectable, OnDestroy } from '@angular/core';
|
|
880
|
+
import { BehaviorSubject, Observable, interval, Subscription } from 'rxjs';
|
|
881
|
+
import {
|
|
882
|
+
ForeGroundLocation,
|
|
883
|
+
LocationResult,
|
|
884
|
+
ServiceStatus,
|
|
885
|
+
ApiServiceStatus,
|
|
886
|
+
LocationServiceOptions,
|
|
887
|
+
} from 'foreground-location';
|
|
888
|
+
|
|
889
|
+
@Injectable({
|
|
890
|
+
providedIn: 'root',
|
|
891
|
+
})
|
|
892
|
+
export class LocationTrackingService implements OnDestroy {
|
|
893
|
+
private locationSubject = new BehaviorSubject<LocationResult | null>(null);
|
|
894
|
+
private statusSubject = new BehaviorSubject<ServiceStatus>({ isRunning: false });
|
|
895
|
+
private apiStatusSubject = new BehaviorSubject<ApiServiceStatus | null>(null);
|
|
896
|
+
|
|
897
|
+
private locationListener: any = null;
|
|
898
|
+
private statusListener: any = null;
|
|
899
|
+
private apiMonitorSubscription: Subscription | null = null;
|
|
900
|
+
|
|
901
|
+
// Public observables
|
|
902
|
+
public location$: Observable<LocationResult | null> = this.locationSubject.asObservable();
|
|
903
|
+
public status$: Observable<ServiceStatus> = this.statusSubject.asObservable();
|
|
904
|
+
public apiStatus$: Observable<ApiServiceStatus | null> = this.apiStatusSubject.asObservable();
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Start location tracking with configuration
|
|
908
|
+
*/
|
|
909
|
+
async startTracking(options: LocationServiceOptions): Promise<void> {
|
|
910
|
+
try {
|
|
911
|
+
// Request permissions
|
|
912
|
+
const permissions = await ForeGroundLocation.requestPermissions();
|
|
913
|
+
if (permissions.location !== 'granted') {
|
|
914
|
+
throw new Error('Location permission required');
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Setup listeners
|
|
918
|
+
await this.setupListeners();
|
|
919
|
+
|
|
920
|
+
// Start the service
|
|
921
|
+
await ForeGroundLocation.startForegroundLocationService(options);
|
|
922
|
+
|
|
923
|
+
// Start API monitoring if API is configured
|
|
924
|
+
if (options.api) {
|
|
925
|
+
this.startApiMonitoring();
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
console.log('✅ Location tracking started');
|
|
929
|
+
} catch (error) {
|
|
930
|
+
console.error('❌ Failed to start location tracking:', error);
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Stop location tracking
|
|
937
|
+
*/
|
|
938
|
+
async stopTracking(): Promise<void> {
|
|
939
|
+
try {
|
|
940
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
941
|
+
await this.cleanup();
|
|
942
|
+
console.log('✅ Location tracking stopped');
|
|
943
|
+
} catch (error) {
|
|
944
|
+
console.error('❌ Failed to stop location tracking:', error);
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Get current location once
|
|
951
|
+
*/
|
|
952
|
+
async getCurrentLocation(): Promise<LocationResult> {
|
|
953
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
954
|
+
if (permissions.location !== 'granted') {
|
|
955
|
+
throw new Error('Location permission required');
|
|
956
|
+
}
|
|
957
|
+
return await ForeGroundLocation.getCurrentLocation();
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Update location service settings
|
|
962
|
+
*/
|
|
963
|
+
async updateSettings(options: LocationServiceOptions): Promise<void> {
|
|
964
|
+
await ForeGroundLocation.updateLocationSettings(options);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Clear API buffers
|
|
969
|
+
*/
|
|
970
|
+
async clearApiBuffers(): Promise<void> {
|
|
971
|
+
await ForeGroundLocation.clearApiBuffers();
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Reset API circuit breaker
|
|
976
|
+
*/
|
|
977
|
+
async resetApiCircuitBreaker(): Promise<void> {
|
|
978
|
+
await ForeGroundLocation.resetApiCircuitBreaker();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Setup event listeners
|
|
983
|
+
*/
|
|
984
|
+
private async setupListeners(): Promise<void> {
|
|
985
|
+
this.locationListener = await ForeGroundLocation.addListener('locationUpdate', (location: LocationResult) => {
|
|
986
|
+
this.locationSubject.next(location);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
this.statusListener = await ForeGroundLocation.addListener('serviceStatusChanged', (status: ServiceStatus) => {
|
|
990
|
+
this.statusSubject.next(status);
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Start monitoring API service status
|
|
996
|
+
*/
|
|
997
|
+
private startApiMonitoring(): void {
|
|
998
|
+
this.apiMonitorSubscription = interval(30000).subscribe(async () => {
|
|
999
|
+
try {
|
|
1000
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
1001
|
+
this.apiStatusSubject.next(status);
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
console.error('Failed to check API status:', error);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Cleanup listeners and subscriptions
|
|
1010
|
+
*/
|
|
1011
|
+
private async cleanup(): Promise<void> {
|
|
1012
|
+
if (this.locationListener) {
|
|
1013
|
+
this.locationListener.remove();
|
|
1014
|
+
this.locationListener = null;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (this.statusListener) {
|
|
1018
|
+
this.statusListener.remove();
|
|
1019
|
+
this.statusListener = null;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (this.apiMonitorSubscription) {
|
|
1023
|
+
this.apiMonitorSubscription.unsubscribe();
|
|
1024
|
+
this.apiMonitorSubscription = null;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
await ForeGroundLocation.removeAllListeners();
|
|
1028
|
+
|
|
1029
|
+
// Reset subjects
|
|
1030
|
+
this.statusSubject.next({ isRunning: false });
|
|
1031
|
+
this.apiStatusSubject.next(null);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* OnDestroy lifecycle hook
|
|
1036
|
+
*/
|
|
1037
|
+
ngOnDestroy(): void {
|
|
1038
|
+
this.cleanup();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
### Vue.js Composition API
|
|
1044
|
+
|
|
1045
|
+
```typescript
|
|
1046
|
+
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
|
1047
|
+
import {
|
|
1048
|
+
ForeGroundLocation,
|
|
1049
|
+
LocationResult,
|
|
1050
|
+
ServiceStatus,
|
|
1051
|
+
ApiServiceStatus,
|
|
1052
|
+
LocationServiceOptions,
|
|
1053
|
+
} from 'foreground-location';
|
|
1054
|
+
|
|
1055
|
+
export function useLocationTracking() {
|
|
1056
|
+
const isTracking = ref(false);
|
|
1057
|
+
const currentLocation = ref<LocationResult | null>(null);
|
|
1058
|
+
const serviceStatus = ref<ServiceStatus>({ isRunning: false });
|
|
1059
|
+
const apiStatus = ref<ApiServiceStatus | null>(null);
|
|
1060
|
+
const error = ref<string | null>(null);
|
|
1061
|
+
|
|
1062
|
+
let locationListener: any = null;
|
|
1063
|
+
let statusListener: any = null;
|
|
1064
|
+
let apiMonitorInterval: number | null = null;
|
|
1065
|
+
|
|
1066
|
+
// Computed properties
|
|
1067
|
+
const locationDisplay = computed(() => {
|
|
1068
|
+
if (!currentLocation.value) return null;
|
|
1069
|
+
|
|
1070
|
+
return {
|
|
1071
|
+
coordinates: `${currentLocation.value.latitude.toFixed(6)}, ${currentLocation.value.longitude.toFixed(6)}`,
|
|
1072
|
+
accuracy: `${currentLocation.value.accuracy.toFixed(1)}m`,
|
|
1073
|
+
speed: currentLocation.value.speed ? `${(currentLocation.value.speed * 3.6).toFixed(1)} km/h` : 'N/A',
|
|
1074
|
+
timestamp: new Date(currentLocation.value.timestamp).toLocaleString(),
|
|
1075
|
+
};
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
const apiStatusDisplay = computed(() => {
|
|
1079
|
+
if (!apiStatus.value) return null;
|
|
1080
|
+
|
|
1081
|
+
return {
|
|
1082
|
+
status: apiStatus.value.isEnabled ? 'Enabled' : 'Disabled',
|
|
1083
|
+
health: apiStatus.value.isHealthy ? 'Healthy' : 'Unhealthy',
|
|
1084
|
+
bufferWarning: apiStatus.value.bufferSize > 50,
|
|
1085
|
+
};
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Start location tracking
|
|
1090
|
+
*/
|
|
1091
|
+
const startTracking = async (options: LocationServiceOptions) => {
|
|
1092
|
+
try {
|
|
1093
|
+
error.value = null;
|
|
1094
|
+
|
|
1095
|
+
// Check permissions
|
|
1096
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
1097
|
+
if (permissions.location !== 'granted') {
|
|
1098
|
+
const requested = await ForeGroundLocation.requestPermissions();
|
|
1099
|
+
if (requested.location !== 'granted') {
|
|
1100
|
+
throw new Error('Location permission denied');
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Setup listeners
|
|
1105
|
+
locationListener = await ForeGroundLocation.addListener('locationUpdate', (location: LocationResult) => {
|
|
1106
|
+
currentLocation.value = location;
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
statusListener = await ForeGroundLocation.addListener('serviceStatusChanged', (status: ServiceStatus) => {
|
|
1110
|
+
serviceStatus.value = status;
|
|
1111
|
+
isTracking.value = status.isRunning;
|
|
1112
|
+
if (status.error) {
|
|
1113
|
+
error.value = status.error;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// Start service
|
|
1118
|
+
await ForeGroundLocation.startForegroundLocationService(options);
|
|
1119
|
+
|
|
1120
|
+
// Monitor API if configured
|
|
1121
|
+
if (options.api) {
|
|
1122
|
+
startApiMonitoring();
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
isTracking.value = true;
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
error.value = err instanceof Error ? err.message : 'Unknown error';
|
|
1128
|
+
isTracking.value = false;
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Stop location tracking
|
|
1134
|
+
*/
|
|
1135
|
+
const stopTracking = async () => {
|
|
1136
|
+
try {
|
|
1137
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
1138
|
+
await cleanup();
|
|
1139
|
+
isTracking.value = false;
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
console.error('Failed to stop tracking:', err);
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Start API monitoring
|
|
1147
|
+
*/
|
|
1148
|
+
const startApiMonitoring = () => {
|
|
1149
|
+
apiMonitorInterval = window.setInterval(async () => {
|
|
1150
|
+
try {
|
|
1151
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
1152
|
+
apiStatus.value = status;
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
console.error('Failed to check API status:', error);
|
|
1155
|
+
}
|
|
1156
|
+
}, 30000);
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Cleanup resources
|
|
1161
|
+
*/
|
|
1162
|
+
const cleanup = async () => {
|
|
1163
|
+
if (locationListener) {
|
|
1164
|
+
locationListener.remove();
|
|
1165
|
+
locationListener = null;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
if (statusListener) {
|
|
1169
|
+
statusListener.remove();
|
|
1170
|
+
statusListener = null;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (apiMonitorInterval) {
|
|
1174
|
+
clearInterval(apiMonitorInterval);
|
|
1175
|
+
apiMonitorInterval = null;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
await ForeGroundLocation.removeAllListeners();
|
|
1179
|
+
|
|
1180
|
+
currentLocation.value = null;
|
|
1181
|
+
serviceStatus.value = { isRunning: false };
|
|
1182
|
+
apiStatus.value = null;
|
|
1183
|
+
error.value = null;
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// Lifecycle hooks
|
|
1187
|
+
onMounted(() => {
|
|
1188
|
+
// Component mounted
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
onUnmounted(() => {
|
|
1192
|
+
cleanup();
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
return {
|
|
1196
|
+
// State
|
|
1197
|
+
isTracking,
|
|
1198
|
+
currentLocation,
|
|
1199
|
+
serviceStatus,
|
|
1200
|
+
apiStatus,
|
|
1201
|
+
error,
|
|
1202
|
+
|
|
1203
|
+
// Computed
|
|
1204
|
+
locationDisplay,
|
|
1205
|
+
apiStatusDisplay,
|
|
1206
|
+
|
|
1207
|
+
// Methods
|
|
1208
|
+
startTracking,
|
|
1209
|
+
stopTracking,
|
|
1210
|
+
cleanup,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
## Production Considerations
|
|
1216
|
+
|
|
1217
|
+
### Performance Optimization
|
|
1218
|
+
|
|
1219
|
+
```typescript
|
|
1220
|
+
class ProductionLocationService extends BasicLocationService {
|
|
1221
|
+
private performanceMetrics = {
|
|
1222
|
+
locationUpdates: 0,
|
|
1223
|
+
apiCalls: 0,
|
|
1224
|
+
errors: 0,
|
|
1225
|
+
startTime: Date.now(),
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Production-ready configuration
|
|
1230
|
+
*/
|
|
1231
|
+
async startProductionTracking(): Promise<void> {
|
|
1232
|
+
const config: LocationServiceOptions = {
|
|
1233
|
+
// Balanced settings for production
|
|
1234
|
+
interval: 60000, // 1 minute intervals
|
|
1235
|
+
fastestInterval: 30000, // Minimum 30 seconds
|
|
1236
|
+
priority: 'BALANCED_POWER', // Balance accuracy and battery
|
|
1237
|
+
distanceFilter: 10, // 10 meters minimum movement
|
|
1238
|
+
enableHighAccuracy: false, // Disable GPS for battery
|
|
1239
|
+
|
|
1240
|
+
notification: {
|
|
1241
|
+
title: 'Location Services',
|
|
1242
|
+
text: 'App is tracking your location',
|
|
1243
|
+
icon: 'location_on',
|
|
1244
|
+
},
|
|
1245
|
+
|
|
1246
|
+
api: {
|
|
1247
|
+
url: process.env.API_ENDPOINT!,
|
|
1248
|
+
type: 'POST',
|
|
1249
|
+
header: {
|
|
1250
|
+
Authorization: `Bearer ${await this.getValidToken()}`,
|
|
1251
|
+
'Content-Type': 'application/json',
|
|
1252
|
+
'X-App-Version': process.env.APP_VERSION || '1.0.0',
|
|
1253
|
+
'X-Platform': this.getPlatform(),
|
|
1254
|
+
},
|
|
1255
|
+
apiInterval: 10, // Send every 10 minutes
|
|
1256
|
+
additionalParams: {
|
|
1257
|
+
userId: await this.getUserId(),
|
|
1258
|
+
deviceId: await this.getDeviceId(),
|
|
1259
|
+
sessionId: this.generateSessionId(),
|
|
1260
|
+
},
|
|
1261
|
+
},
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
await this.startTracking();
|
|
1265
|
+
await ForeGroundLocation.updateLocationSettings(config);
|
|
1266
|
+
|
|
1267
|
+
this.startPerformanceMonitoring();
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Monitor performance metrics
|
|
1272
|
+
*/
|
|
1273
|
+
private startPerformanceMonitoring(): void {
|
|
1274
|
+
setInterval(() => {
|
|
1275
|
+
const uptime = Date.now() - this.performanceMetrics.startTime;
|
|
1276
|
+
const avgUpdatesPerHour = (this.performanceMetrics.locationUpdates / uptime) * 3600000;
|
|
1277
|
+
|
|
1278
|
+
console.log('📊 Performance Metrics:', {
|
|
1279
|
+
uptime: `${Math.round(uptime / 60000)} minutes`,
|
|
1280
|
+
locationUpdates: this.performanceMetrics.locationUpdates,
|
|
1281
|
+
apiCalls: this.performanceMetrics.apiCalls,
|
|
1282
|
+
errors: this.performanceMetrics.errors,
|
|
1283
|
+
avgUpdatesPerHour: Math.round(avgUpdatesPerHour),
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
// Send metrics to analytics service
|
|
1287
|
+
this.sendAnalytics('location_service_performance', this.performanceMetrics);
|
|
1288
|
+
}, 600000); // Every 10 minutes
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
protected handleLocationUpdate(location: LocationResult): void {
|
|
1292
|
+
super.handleLocationUpdate(location);
|
|
1293
|
+
this.performanceMetrics.locationUpdates++;
|
|
1294
|
+
|
|
1295
|
+
// Validate location data
|
|
1296
|
+
if (this.isValidLocation(location)) {
|
|
1297
|
+
this.processValidLocation(location);
|
|
1298
|
+
} else {
|
|
1299
|
+
console.warn('⚠️ Invalid location data received:', location);
|
|
1300
|
+
this.performanceMetrics.errors++;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
private isValidLocation(location: LocationResult): boolean {
|
|
1305
|
+
return (
|
|
1306
|
+
location.latitude >= -90 &&
|
|
1307
|
+
location.latitude <= 90 &&
|
|
1308
|
+
location.longitude >= -180 &&
|
|
1309
|
+
location.longitude <= 180 &&
|
|
1310
|
+
location.accuracy > 0 &&
|
|
1311
|
+
location.accuracy < 1000
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
private async getValidToken(): Promise<string> {
|
|
1316
|
+
// Implement token validation and refresh logic
|
|
1317
|
+
return 'valid-token';
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
private getPlatform(): string {
|
|
1321
|
+
// Detect platform
|
|
1322
|
+
return 'unknown';
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
private async getUserId(): Promise<string> {
|
|
1326
|
+
// Get user ID from your auth system
|
|
1327
|
+
return 'user-id';
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
private async getDeviceId(): Promise<string> {
|
|
1331
|
+
// Generate or retrieve persistent device ID
|
|
1332
|
+
return 'device-id';
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
private generateSessionId(): string {
|
|
1336
|
+
return 'session-' + Math.random().toString(36).substr(2, 9);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
private sendAnalytics(event: string, data: any): void {
|
|
1340
|
+
// Send to your analytics service
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
private processValidLocation(location: LocationResult): void {
|
|
1344
|
+
// Process valid location data
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
### Error Handling and Recovery
|
|
1350
|
+
|
|
1351
|
+
```typescript
|
|
1352
|
+
class RobustLocationService extends ProductionLocationService {
|
|
1353
|
+
private retryCount = 0;
|
|
1354
|
+
private maxRetries = 3;
|
|
1355
|
+
private backoffDelay = 1000;
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Start with automatic retry on failure
|
|
1359
|
+
*/
|
|
1360
|
+
async startWithRetry(): Promise<void> {
|
|
1361
|
+
while (this.retryCount < this.maxRetries) {
|
|
1362
|
+
try {
|
|
1363
|
+
await this.startProductionTracking();
|
|
1364
|
+
this.retryCount = 0; // Reset on success
|
|
1365
|
+
return;
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
this.retryCount++;
|
|
1368
|
+
console.error(`❌ Start attempt ${this.retryCount} failed:`, error);
|
|
1369
|
+
|
|
1370
|
+
if (this.retryCount >= this.maxRetries) {
|
|
1371
|
+
throw new Error(`Failed to start after ${this.maxRetries} attempts: ${error}`);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// Exponential backoff
|
|
1375
|
+
const delay = this.backoffDelay * Math.pow(2, this.retryCount - 1);
|
|
1376
|
+
console.log(`⏳ Retrying in ${delay}ms...`);
|
|
1377
|
+
await this.sleep(delay);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Handle service errors with recovery
|
|
1384
|
+
*/
|
|
1385
|
+
protected handleServiceError(error: string): void {
|
|
1386
|
+
super.handleServiceError(error);
|
|
1387
|
+
|
|
1388
|
+
// Implement specific recovery strategies
|
|
1389
|
+
switch (error) {
|
|
1390
|
+
case 'LOCATION_SERVICES_DISABLED':
|
|
1391
|
+
this.handleLocationServicesDisabled();
|
|
1392
|
+
break;
|
|
1393
|
+
case 'PERMISSION_DENIED':
|
|
1394
|
+
this.handlePermissionDenied();
|
|
1395
|
+
break;
|
|
1396
|
+
default:
|
|
1397
|
+
this.handleGenericError(error);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
private async handleLocationServicesDisabled(): Promise<void> {
|
|
1402
|
+
console.log('🔧 Attempting to handle disabled location services...');
|
|
1403
|
+
|
|
1404
|
+
// Wait and retry
|
|
1405
|
+
await this.sleep(5000);
|
|
1406
|
+
|
|
1407
|
+
try {
|
|
1408
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
1409
|
+
if (permissions.location === 'granted') {
|
|
1410
|
+
console.log('🔄 Attempting to restart service...');
|
|
1411
|
+
await this.startWithRetry();
|
|
1412
|
+
}
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
console.error('Failed to recover from disabled location services:', error);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
private async handlePermissionDenied(): Promise<void> {
|
|
1419
|
+
console.log('🔧 Handling permission denied...');
|
|
1420
|
+
|
|
1421
|
+
// Show user guidance
|
|
1422
|
+
this.showPermissionGuidance();
|
|
1423
|
+
|
|
1424
|
+
// Set up permission monitoring
|
|
1425
|
+
this.monitorPermissionChanges();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private handleGenericError(error: string): void {
|
|
1429
|
+
console.log('🔧 Handling generic error:', error);
|
|
1430
|
+
|
|
1431
|
+
// Schedule retry
|
|
1432
|
+
setTimeout(() => {
|
|
1433
|
+
this.startWithRetry();
|
|
1434
|
+
}, 30000); // Retry after 30 seconds
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
private showPermissionGuidance(): void {
|
|
1438
|
+
// Show user-friendly permission guidance
|
|
1439
|
+
alert('Please enable location permissions in your device settings to continue tracking.');
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
private monitorPermissionChanges(): void {
|
|
1443
|
+
const checkPermissions = async () => {
|
|
1444
|
+
try {
|
|
1445
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
1446
|
+
if (permissions.location === 'granted') {
|
|
1447
|
+
console.log('✅ Permissions restored, restarting service...');
|
|
1448
|
+
await this.startWithRetry();
|
|
1449
|
+
clearInterval(permissionCheck);
|
|
1450
|
+
}
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
console.error('Permission monitoring error:', error);
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
const permissionCheck = setInterval(checkPermissions, 10000); // Check every 10 seconds
|
|
1457
|
+
|
|
1458
|
+
// Stop monitoring after 5 minutes
|
|
1459
|
+
setTimeout(() => {
|
|
1460
|
+
clearInterval(permissionCheck);
|
|
1461
|
+
}, 300000);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
private sleep(ms: number): Promise<void> {
|
|
1465
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
```
|
|
1469
|
+
|
|
1470
|
+
## Troubleshooting
|
|
1471
|
+
|
|
1472
|
+
### Common Issues and Solutions
|
|
1473
|
+
|
|
1474
|
+
| Issue | Symptoms | Cause | Solution |
|
|
1475
|
+
| -------------------- | ---------------------------------------- | ----------------------------- | ------------------------------------------------------------ |
|
|
1476
|
+
| Service won't start | Error on startForegroundLocationService | Missing notification config | Ensure notification.title and notification.text are provided |
|
|
1477
|
+
| No location updates | Service running but no location events | Permission issues | Check permissions with checkPermissions() |
|
|
1478
|
+
| High battery drain | Device heating up, battery draining fast | Too frequent updates | Increase interval, use BALANCED_POWER priority |
|
|
1479
|
+
| API data not sending | Buffer size increasing, no API calls | Network/authentication issues | Check API endpoint, verify auth token |
|
|
1480
|
+
| Location inaccurate | Large accuracy values | Wrong settings | Use HIGH_ACCURACY priority, enable GPS |
|
|
1481
|
+
| App crashes on start | App closes when starting service | Native permission issues | Ensure all required permissions in manifest |
|
|
1482
|
+
|
|
1483
|
+
### Debug Utilities
|
|
1484
|
+
|
|
1485
|
+
```typescript
|
|
1486
|
+
class LocationDebugger {
|
|
1487
|
+
/**
|
|
1488
|
+
* Comprehensive system check
|
|
1489
|
+
*/
|
|
1490
|
+
static async runSystemCheck(): Promise<void> {
|
|
1491
|
+
console.log('🔍 Running location system diagnostics...');
|
|
1492
|
+
|
|
1493
|
+
try {
|
|
1494
|
+
// Check permissions
|
|
1495
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
1496
|
+
console.log('📋 Permissions:', permissions);
|
|
1497
|
+
|
|
1498
|
+
// Check service status
|
|
1499
|
+
const serviceStatus = await ForeGroundLocation.isServiceRunning();
|
|
1500
|
+
console.log('⚙️ Service Status:', serviceStatus);
|
|
1501
|
+
|
|
1502
|
+
// Try getting current location
|
|
1503
|
+
try {
|
|
1504
|
+
const location = await ForeGroundLocation.getCurrentLocation();
|
|
1505
|
+
console.log('📍 Current Location:', location);
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
console.log('❌ Current Location Error:', error);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// Check API status
|
|
1511
|
+
try {
|
|
1512
|
+
const apiStatus = await ForeGroundLocation.getApiServiceStatus();
|
|
1513
|
+
console.log('🌐 API Status:', apiStatus);
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
console.log('❌ API Status Error:', error);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
console.log('✅ System check completed');
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
console.error('❌ System check failed:', error);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/**
|
|
1525
|
+
* Monitor location service health
|
|
1526
|
+
*/
|
|
1527
|
+
static startHealthMonitoring(): void {
|
|
1528
|
+
setInterval(async () => {
|
|
1529
|
+
try {
|
|
1530
|
+
const timestamp = new Date().toISOString();
|
|
1531
|
+
const serviceStatus = await ForeGroundLocation.isServiceRunning();
|
|
1532
|
+
const apiStatus = await ForeGroundLocation.getApiServiceStatus();
|
|
1533
|
+
|
|
1534
|
+
console.log(`🏥 Health Check [${timestamp}]:`, {
|
|
1535
|
+
service: serviceStatus.isRunning ? '✅' : '❌',
|
|
1536
|
+
api: apiStatus.isEnabled ? '✅' : '❌',
|
|
1537
|
+
healthy: apiStatus.isHealthy ? '✅' : '⚠️',
|
|
1538
|
+
buffer: apiStatus.bufferSize,
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
// Alert on issues
|
|
1542
|
+
if (!serviceStatus.isRunning) {
|
|
1543
|
+
console.warn('⚠️ Location service is not running!');
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (apiStatus.isEnabled && !apiStatus.isHealthy) {
|
|
1547
|
+
console.warn('⚠️ API service is unhealthy!');
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
if (apiStatus.bufferSize > 100) {
|
|
1551
|
+
console.warn('⚠️ API buffer is getting full:', apiStatus.bufferSize);
|
|
1552
|
+
}
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
console.error('❌ Health monitoring error:', error);
|
|
1555
|
+
}
|
|
1556
|
+
}, 60000); // Every minute
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// Usage
|
|
1561
|
+
LocationDebugger.runSystemCheck();
|
|
1562
|
+
LocationDebugger.startHealthMonitoring();
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
### Testing Utilities
|
|
1566
|
+
|
|
1567
|
+
```typescript
|
|
1568
|
+
class LocationTester {
|
|
1569
|
+
/**
|
|
1570
|
+
* Test location service lifecycle
|
|
1571
|
+
*/
|
|
1572
|
+
static async testLifecycle(): Promise<void> {
|
|
1573
|
+
console.log('🧪 Testing location service lifecycle...');
|
|
1574
|
+
|
|
1575
|
+
try {
|
|
1576
|
+
// Test permissions
|
|
1577
|
+
console.log('1. Testing permissions...');
|
|
1578
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
1579
|
+
if (permissions.location !== 'granted') {
|
|
1580
|
+
const requested = await ForeGroundLocation.requestPermissions();
|
|
1581
|
+
console.log('Permission result:', requested);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// Test service start
|
|
1585
|
+
console.log('2. Testing service start...');
|
|
1586
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
1587
|
+
interval: 5000,
|
|
1588
|
+
notification: {
|
|
1589
|
+
title: 'Test Location Service',
|
|
1590
|
+
text: 'Testing location tracking',
|
|
1591
|
+
},
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
// Wait for location updates
|
|
1595
|
+
console.log('3. Waiting for location updates...');
|
|
1596
|
+
await new Promise((resolve) => {
|
|
1597
|
+
let updateCount = 0;
|
|
1598
|
+
ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
1599
|
+
updateCount++;
|
|
1600
|
+
console.log(`Location update ${updateCount}:`, location);
|
|
1601
|
+
|
|
1602
|
+
if (updateCount >= 3) {
|
|
1603
|
+
resolve(undefined);
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
// Test service stop
|
|
1609
|
+
console.log('4. Testing service stop...');
|
|
1610
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
1611
|
+
await ForeGroundLocation.removeAllListeners();
|
|
1612
|
+
|
|
1613
|
+
console.log('✅ Lifecycle test completed successfully');
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
console.error('❌ Lifecycle test failed:', error);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* Test API integration
|
|
1621
|
+
*/
|
|
1622
|
+
static async testApiIntegration(apiUrl: string): Promise<void> {
|
|
1623
|
+
console.log('🧪 Testing API integration...');
|
|
1624
|
+
|
|
1625
|
+
try {
|
|
1626
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
1627
|
+
interval: 10000,
|
|
1628
|
+
notification: {
|
|
1629
|
+
title: 'API Test',
|
|
1630
|
+
text: 'Testing API integration',
|
|
1631
|
+
},
|
|
1632
|
+
api: {
|
|
1633
|
+
url: apiUrl,
|
|
1634
|
+
type: 'POST',
|
|
1635
|
+
header: {
|
|
1636
|
+
'Content-Type': 'application/json',
|
|
1637
|
+
},
|
|
1638
|
+
apiInterval: 1, // Send every minute for testing
|
|
1639
|
+
},
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
// Monitor API status
|
|
1643
|
+
const checkInterval = setInterval(async () => {
|
|
1644
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
1645
|
+
console.log('API Status:', status);
|
|
1646
|
+
}, 10000);
|
|
1647
|
+
|
|
1648
|
+
// Run for 2 minutes
|
|
1649
|
+
setTimeout(async () => {
|
|
1650
|
+
clearInterval(checkInterval);
|
|
1651
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
1652
|
+
console.log('✅ API test completed');
|
|
1653
|
+
}, 120000);
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
console.error('❌ API test failed:', error);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
This comprehensive setup and examples guide provides everything you need to implement the Capacitor Foreground Location plugin correctly in your application. Remember to always test thoroughly on real devices and handle edge cases appropriately for production use.
|
|
1662
|
+
|
|
1663
|
+
useEffect(() => {
|
|
1664
|
+
return () => {
|
|
1665
|
+
// Cleanup on unmount
|
|
1666
|
+
if (locationListener) {
|
|
1667
|
+
locationListener.remove();
|
|
1668
|
+
}
|
|
1669
|
+
if (isTracking) {
|
|
1670
|
+
ForeGroundLocation.stopLocationTracking();
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
}, [locationListener, isTracking]);
|
|
1674
|
+
|
|
1675
|
+
const startTracking = async () => {
|
|
1676
|
+
try {
|
|
1677
|
+
// Setup location listener
|
|
1678
|
+
const listener = await ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
1679
|
+
setCurrentLocation(location);
|
|
1680
|
+
console.log('Location update received:', location);
|
|
1681
|
+
});
|
|
1682
|
+
setLocationListener(listener);
|
|
1683
|
+
|
|
1684
|
+
// Configure API service
|
|
1685
|
+
const apiConfig: ApiServiceConfig = {
|
|
1686
|
+
baseUrl: 'https://your-api.com',
|
|
1687
|
+
endpoint: '/api/locations',
|
|
1688
|
+
method: 'POST',
|
|
1689
|
+
headers: {
|
|
1690
|
+
'Authorization': 'Bearer your-token',
|
|
1691
|
+
'Content-Type': 'application/json'
|
|
1692
|
+
},
|
|
1693
|
+
batchSize: 10,
|
|
1694
|
+
retryAttempts: 3,
|
|
1695
|
+
retryDelay: 1000,
|
|
1696
|
+
timeout: 30000,
|
|
1697
|
+
circuitBreakerThreshold: 5,
|
|
1698
|
+
bufferSize: 100
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
// Start tracking
|
|
1702
|
+
await ForeGroundLocation.startLocationTracking({
|
|
1703
|
+
interval: 10000,
|
|
1704
|
+
fastestInterval: 5000,
|
|
1705
|
+
priority: 'HIGH_ACCURACY',
|
|
1706
|
+
apiService: apiConfig
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
setIsTracking(true);
|
|
1710
|
+
|
|
1711
|
+
// Start monitoring API status
|
|
1712
|
+
monitorApiStatus();
|
|
1713
|
+
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
console.error('Failed to start tracking:', error);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
const stopTracking = async () => {
|
|
1721
|
+
try {
|
|
1722
|
+
await ForeGroundLocation.stopLocationTracking();
|
|
1723
|
+
|
|
1724
|
+
if (locationListener) {
|
|
1725
|
+
locationListener.remove();
|
|
1726
|
+
setLocationListener(null);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
setIsTracking(false);
|
|
1730
|
+
setApiStatus(null);
|
|
1731
|
+
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
console.error('Failed to stop tracking:', error);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1738
|
+
const monitorApiStatus = () => {
|
|
1739
|
+
const checkStatus = async () => {
|
|
1740
|
+
try {
|
|
1741
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
1742
|
+
setApiStatus(status);
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
console.error('Failed to get API status:', error);
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
|
|
1748
|
+
// Check immediately and then every 30 seconds
|
|
1749
|
+
checkStatus();
|
|
1750
|
+
const interval = setInterval(checkStatus, 30000);
|
|
1751
|
+
|
|
1752
|
+
// Cleanup interval when tracking stops
|
|
1753
|
+
setTimeout(() => {
|
|
1754
|
+
if (!isTracking) {
|
|
1755
|
+
clearInterval(interval);
|
|
1756
|
+
}
|
|
1757
|
+
}, 100);
|
|
1758
|
+
|
|
1759
|
+
};
|
|
1760
|
+
|
|
1761
|
+
const clearBuffers = async () => {
|
|
1762
|
+
try {
|
|
1763
|
+
await ForeGroundLocation.clearApiBuffers();
|
|
1764
|
+
console.log('Buffers cleared');
|
|
1765
|
+
} catch (error) {
|
|
1766
|
+
console.error('Failed to clear buffers:', error);
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
const resetCircuitBreaker = async () => {
|
|
1771
|
+
try {
|
|
1772
|
+
await ForeGroundLocation.resetApiCircuitBreaker();
|
|
1773
|
+
console.log('Circuit breaker reset');
|
|
1774
|
+
} catch (error) {
|
|
1775
|
+
console.error('Failed to reset circuit breaker:', error);
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
|
|
1779
|
+
return (
|
|
1780
|
+
<IonPage>
|
|
1781
|
+
<IonHeader>
|
|
1782
|
+
<IonToolbar>
|
|
1783
|
+
<IonTitle>Location Tracker</IonTitle>
|
|
1784
|
+
</IonToolbar>
|
|
1785
|
+
</IonHeader>
|
|
1786
|
+
<IonContent>
|
|
1787
|
+
<IonItem>
|
|
1788
|
+
<IonLabel>
|
|
1789
|
+
<h2>Tracking Status</h2>
|
|
1790
|
+
<p>{isTracking ? 'Active' : 'Inactive'}</p>
|
|
1791
|
+
</IonLabel>
|
|
1792
|
+
</IonItem>
|
|
1793
|
+
|
|
1794
|
+
{currentLocation && (
|
|
1795
|
+
<IonItem>
|
|
1796
|
+
<IonLabel>
|
|
1797
|
+
<h2>Current Location</h2>
|
|
1798
|
+
<p>Lat: {currentLocation.latitude.toFixed(6)}</p>
|
|
1799
|
+
<p>Lng: {currentLocation.longitude.toFixed(6)}</p>
|
|
1800
|
+
<p>Accuracy: {currentLocation.accuracy}m</p>
|
|
1801
|
+
<p>Time: {new Date(currentLocation.timestamp).toLocaleString()}</p>
|
|
1802
|
+
</IonLabel>
|
|
1803
|
+
</IonItem>
|
|
1804
|
+
)}
|
|
1805
|
+
|
|
1806
|
+
{apiStatus && (
|
|
1807
|
+
<IonItem>
|
|
1808
|
+
<IonLabel>
|
|
1809
|
+
<h2>API Service Status</h2>
|
|
1810
|
+
<p>Active: {apiStatus.isActive ? 'Yes' : 'No'}</p>
|
|
1811
|
+
<p>Buffered: {apiStatus.bufferedCount} locations</p>
|
|
1812
|
+
<p>Circuit Breaker: {apiStatus.circuitBreakerOpen ? 'Open' : 'Closed'}</p>
|
|
1813
|
+
{apiStatus.lastSuccessfulCall && (
|
|
1814
|
+
<p>Last Success: {new Date(apiStatus.lastSuccessfulCall).toLocaleString()}</p>
|
|
1815
|
+
)}
|
|
1816
|
+
{apiStatus.lastError && (
|
|
1817
|
+
<p>Last Error: {apiStatus.lastError}</p>
|
|
1818
|
+
)}
|
|
1819
|
+
</IonLabel>
|
|
1820
|
+
</IonItem>
|
|
1821
|
+
)}
|
|
1822
|
+
|
|
1823
|
+
<IonButton
|
|
1824
|
+
expand="block"
|
|
1825
|
+
onClick={isTracking ? stopTracking : startTracking}
|
|
1826
|
+
color={isTracking ? 'danger' : 'primary'}
|
|
1827
|
+
>
|
|
1828
|
+
{isTracking ? 'Stop Tracking' : 'Start Tracking'}
|
|
1829
|
+
</IonButton>
|
|
1830
|
+
|
|
1831
|
+
{apiStatus && (
|
|
1832
|
+
<>
|
|
1833
|
+
<IonButton expand="block" fill="outline" onClick={clearBuffers}>
|
|
1834
|
+
Clear API Buffers
|
|
1835
|
+
</IonButton>
|
|
1836
|
+
|
|
1837
|
+
{apiStatus.circuitBreakerOpen && (
|
|
1838
|
+
<IonButton expand="block" fill="outline" onClick={resetCircuitBreaker}>
|
|
1839
|
+
Reset Circuit Breaker
|
|
1840
|
+
</IonButton>
|
|
1841
|
+
)}
|
|
1842
|
+
</>
|
|
1843
|
+
)}
|
|
1844
|
+
</IonContent>
|
|
1845
|
+
</IonPage>
|
|
1846
|
+
|
|
1847
|
+
);
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1850
|
+
export default LocationTracker;
|
|
1851
|
+
```
|
|
1852
|
+
|
|
1853
|
+
## Error Handling
|
|
1854
|
+
|
|
1855
|
+
### Comprehensive Error Handling Example
|
|
1856
|
+
|
|
1857
|
+
```typescript
|
|
1858
|
+
import { ForeGroundLocation } from '@xconcepts/foreground-location';
|
|
1859
|
+
|
|
1860
|
+
class RobustLocationService {
|
|
1861
|
+
private maxRetries = 3;
|
|
1862
|
+
private retryCount = 0;
|
|
1863
|
+
|
|
1864
|
+
async startTrackingWithErrorHandling() {
|
|
1865
|
+
try {
|
|
1866
|
+
await this.attemptStartTracking();
|
|
1867
|
+
} catch (error) {
|
|
1868
|
+
await this.handleStartError(error);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
private async attemptStartTracking() {
|
|
1873
|
+
try {
|
|
1874
|
+
await ForeGroundLocation.startLocationTracking({
|
|
1875
|
+
interval: 10000,
|
|
1876
|
+
fastestInterval: 5000,
|
|
1877
|
+
priority: 'HIGH_ACCURACY',
|
|
1878
|
+
apiService: {
|
|
1879
|
+
baseUrl: 'https://api.example.com',
|
|
1880
|
+
endpoint: '/locations',
|
|
1881
|
+
method: 'POST',
|
|
1882
|
+
headers: { Authorization: 'Bearer token' },
|
|
1883
|
+
batchSize: 10,
|
|
1884
|
+
retryAttempts: 3,
|
|
1885
|
+
timeout: 30000,
|
|
1886
|
+
},
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1889
|
+
this.retryCount = 0; // Reset on success
|
|
1890
|
+
} catch (error) {
|
|
1891
|
+
throw error;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
private async handleStartError(error: any) {
|
|
1896
|
+
console.error('Location tracking error:', error);
|
|
1897
|
+
|
|
1898
|
+
if (this.retryCount < this.maxRetries) {
|
|
1899
|
+
this.retryCount++;
|
|
1900
|
+
console.log(`Retrying... Attempt ${this.retryCount}/${this.maxRetries}`);
|
|
1901
|
+
|
|
1902
|
+
// Wait before retry
|
|
1903
|
+
await new Promise((resolve) => setTimeout(resolve, 2000 * this.retryCount));
|
|
1904
|
+
|
|
1905
|
+
try {
|
|
1906
|
+
await this.attemptStartTracking();
|
|
1907
|
+
} catch (retryError) {
|
|
1908
|
+
await this.handleStartError(retryError);
|
|
1909
|
+
}
|
|
1910
|
+
} else {
|
|
1911
|
+
// Max retries reached
|
|
1912
|
+
console.error('Max retries reached. Location tracking failed.');
|
|
1913
|
+
this.handleFinalError(error);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
private handleFinalError(error: any) {
|
|
1918
|
+
// Handle different error types
|
|
1919
|
+
if (error.message?.includes('permission')) {
|
|
1920
|
+
console.error('Permission denied. Please grant location permissions.');
|
|
1921
|
+
// Show permission request dialog
|
|
1922
|
+
} else if (error.message?.includes('GPS')) {
|
|
1923
|
+
console.error('GPS is disabled. Please enable location services.');
|
|
1924
|
+
// Show GPS enable prompt
|
|
1925
|
+
} else if (error.message?.includes('network')) {
|
|
1926
|
+
console.error('Network error. API service may not be available.');
|
|
1927
|
+
// Try without API service
|
|
1928
|
+
this.startWithoutApi();
|
|
1929
|
+
} else {
|
|
1930
|
+
console.error('Unknown error occurred:', error);
|
|
1931
|
+
// Show generic error message
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
private async startWithoutApi() {
|
|
1936
|
+
try {
|
|
1937
|
+
console.log('Starting location tracking without API service...');
|
|
1938
|
+
await ForeGroundLocation.startLocationTracking({
|
|
1939
|
+
interval: 10000,
|
|
1940
|
+
fastestInterval: 5000,
|
|
1941
|
+
priority: 'HIGH_ACCURACY',
|
|
1942
|
+
// No apiService configuration
|
|
1943
|
+
});
|
|
1944
|
+
console.log('Location tracking started without API service');
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
console.error('Failed to start even without API service:', error);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
## Best Practices
|
|
1953
|
+
|
|
1954
|
+
### Performance Optimization
|
|
1955
|
+
|
|
1956
|
+
1. **Choose appropriate intervals**:
|
|
1957
|
+
- For real-time tracking: 5-10 seconds
|
|
1958
|
+
- For periodic updates: 30-60 seconds
|
|
1959
|
+
- For battery saving: 2-5 minutes
|
|
1960
|
+
|
|
1961
|
+
2. **Use suitable priority levels**:
|
|
1962
|
+
- `HIGH_ACCURACY`: For navigation apps
|
|
1963
|
+
- `BALANCED_POWER_ACCURACY`: For most apps
|
|
1964
|
+
- `LOW_POWER`: For background tracking
|
|
1965
|
+
- `NO_POWER`: For passive location updates
|
|
1966
|
+
|
|
1967
|
+
3. **Configure API batching**:
|
|
1968
|
+
- Small batches (5-10): For real-time requirements
|
|
1969
|
+
- Large batches (20-50): For efficient network usage
|
|
1970
|
+
|
|
1971
|
+
### Memory Management
|
|
1972
|
+
|
|
1973
|
+
```typescript
|
|
1974
|
+
// Monitor and manage buffer sizes
|
|
1975
|
+
const monitorMemoryUsage = async () => {
|
|
1976
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
1977
|
+
|
|
1978
|
+
if (status.bufferedCount > 80) {
|
|
1979
|
+
// 80% of default buffer size
|
|
1980
|
+
console.warn('Buffer getting full, consider clearing or increasing batch frequency');
|
|
1981
|
+
|
|
1982
|
+
// Option 1: Clear buffers
|
|
1983
|
+
await ForeGroundLocation.clearApiBuffers();
|
|
1984
|
+
|
|
1985
|
+
// Option 2: Adjust configuration (restart with smaller buffer)
|
|
1986
|
+
// await restartWithSmallerBuffer();
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
```
|
|
1990
|
+
|
|
1991
|
+
### Security Considerations
|
|
1992
|
+
|
|
1993
|
+
```typescript
|
|
1994
|
+
// Secure API configuration
|
|
1995
|
+
const getSecureApiConfig = async (): Promise<ApiServiceConfig> => {
|
|
1996
|
+
const token = await getSecureToken(); // From secure storage
|
|
1997
|
+
|
|
1998
|
+
return {
|
|
1999
|
+
baseUrl: await getApiEndpoint(), // From secure config
|
|
2000
|
+
endpoint: '/locations',
|
|
2001
|
+
method: 'POST',
|
|
2002
|
+
headers: {
|
|
2003
|
+
Authorization: `Bearer ${token}`,
|
|
2004
|
+
'Content-Type': 'application/json',
|
|
2005
|
+
'X-Client-Version': getAppVersion(),
|
|
2006
|
+
'X-Request-ID': generateRequestId(),
|
|
2007
|
+
},
|
|
2008
|
+
batchSize: 10,
|
|
2009
|
+
retryAttempts: 3,
|
|
2010
|
+
timeout: 30000,
|
|
2011
|
+
};
|
|
2012
|
+
};
|
|
2013
|
+
|
|
2014
|
+
// Rotate tokens periodically
|
|
2015
|
+
const rotateAuthToken = async () => {
|
|
2016
|
+
try {
|
|
2017
|
+
const newToken = await refreshAuthToken();
|
|
2018
|
+
|
|
2019
|
+
// Stop current tracking
|
|
2020
|
+
await ForeGroundLocation.stopLocationTracking();
|
|
2021
|
+
|
|
2022
|
+
// Restart with new token
|
|
2023
|
+
const newConfig = await getSecureApiConfig();
|
|
2024
|
+
await ForeGroundLocation.startLocationTracking({
|
|
2025
|
+
interval: 10000,
|
|
2026
|
+
fastestInterval: 5000,
|
|
2027
|
+
priority: 'HIGH_ACCURACY',
|
|
2028
|
+
apiService: newConfig,
|
|
2029
|
+
});
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
console.error('Failed to rotate auth token:', error);
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
```
|
|
2035
|
+
|
|
2036
|
+
### Testing and Debugging
|
|
2037
|
+
|
|
2038
|
+
```typescript
|
|
2039
|
+
// Debug mode configuration
|
|
2040
|
+
const DEBUG_MODE = process.env.NODE_ENV === 'development';
|
|
2041
|
+
|
|
2042
|
+
const getDebugApiConfig = (): ApiServiceConfig => ({
|
|
2043
|
+
baseUrl: DEBUG_MODE ? 'https://api-dev.example.com' : 'https://api.example.com',
|
|
2044
|
+
endpoint: '/locations',
|
|
2045
|
+
method: 'POST',
|
|
2046
|
+
headers: {
|
|
2047
|
+
Authorization: `Bearer ${getToken()}`,
|
|
2048
|
+
'Content-Type': 'application/json',
|
|
2049
|
+
...(DEBUG_MODE && { 'X-Debug-Mode': 'true' }),
|
|
2050
|
+
},
|
|
2051
|
+
batchSize: DEBUG_MODE ? 3 : 10, // Smaller batches in debug
|
|
2052
|
+
retryAttempts: DEBUG_MODE ? 1 : 3,
|
|
2053
|
+
timeout: DEBUG_MODE ? 10000 : 30000,
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
// Enhanced logging for debugging
|
|
2057
|
+
const debugLocationUpdate = (location: any) => {
|
|
2058
|
+
if (DEBUG_MODE) {
|
|
2059
|
+
console.log('Location Debug Info:', {
|
|
2060
|
+
coordinates: `${location.latitude}, ${location.longitude}`,
|
|
2061
|
+
accuracy: `${location.accuracy}m`,
|
|
2062
|
+
timestamp: new Date(location.timestamp).toISOString(),
|
|
2063
|
+
speed: location.speed ? `${location.speed} m/s` : 'N/A',
|
|
2064
|
+
bearing: location.bearing ? `${location.bearing}°` : 'N/A',
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
```
|
|
2069
|
+
|
|
2070
|
+
This completes the comprehensive setup and examples guide for the Capacitor Foreground Location plugin with API service integration.
|
|
2071
|
+
|
|
2072
|
+
### 3. iOS Configuration
|
|
2073
|
+
|
|
2074
|
+
#### Update Info.plist
|
|
2075
|
+
|
|
2076
|
+
```xml
|
|
2077
|
+
<!-- ios/App/App/Info.plist -->
|
|
2078
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
2079
|
+
<string>This app needs location access to track your route.</string>
|
|
2080
|
+
|
|
2081
|
+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
|
2082
|
+
<string>This app needs location access to track your route in the background.</string>
|
|
2083
|
+
|
|
2084
|
+
<!-- If using background location -->
|
|
2085
|
+
<key>UIBackgroundModes</key>
|
|
2086
|
+
<array>
|
|
2087
|
+
<string>location</string>
|
|
2088
|
+
</array>
|
|
2089
|
+
```
|
|
2090
|
+
|
|
2091
|
+
## Basic Usage Examples
|
|
2092
|
+
|
|
2093
|
+
### Example 1: Simple Location Tracking
|
|
2094
|
+
|
|
2095
|
+
```typescript
|
|
2096
|
+
import { ForeGroundLocation } from 'foreground-location';
|
|
2097
|
+
import type { LocationResult } from 'foreground-location';
|
|
2098
|
+
|
|
2099
|
+
export class LocationService {
|
|
2100
|
+
private locationListener: any;
|
|
2101
|
+
|
|
2102
|
+
async startTracking() {
|
|
2103
|
+
try {
|
|
2104
|
+
// Check and request permissions
|
|
2105
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
2106
|
+
|
|
2107
|
+
if (permissions.location !== 'granted') {
|
|
2108
|
+
const requestResult = await ForeGroundLocation.requestPermissions();
|
|
2109
|
+
if (requestResult.location !== 'granted') {
|
|
2110
|
+
throw new Error('Location permission denied');
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// Start location service
|
|
2115
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2116
|
+
notification: {
|
|
2117
|
+
title: 'Location Tracking',
|
|
2118
|
+
text: 'Tracking your location in the background',
|
|
2119
|
+
icon: 'ic_location',
|
|
2120
|
+
},
|
|
2121
|
+
interval: 30000, // 30 seconds
|
|
2122
|
+
fastestInterval: 15000, // 15 seconds
|
|
2123
|
+
priority: 'HIGH_ACCURACY',
|
|
2124
|
+
});
|
|
2125
|
+
|
|
2126
|
+
// Listen for location updates
|
|
2127
|
+
this.locationListener = await ForeGroundLocation.addListener('locationUpdate', (location: LocationResult) => {
|
|
2128
|
+
console.log('New location:', {
|
|
2129
|
+
lat: location.latitude,
|
|
2130
|
+
lng: location.longitude,
|
|
2131
|
+
accuracy: location.accuracy,
|
|
2132
|
+
time: location.timestamp,
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
// Process location data
|
|
2136
|
+
this.handleLocationUpdate(location);
|
|
2137
|
+
});
|
|
2138
|
+
|
|
2139
|
+
console.log('Location tracking started successfully');
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
console.error('Failed to start location tracking:', error);
|
|
2142
|
+
throw error;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
async stopTracking() {
|
|
2147
|
+
try {
|
|
2148
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
2149
|
+
|
|
2150
|
+
if (this.locationListener) {
|
|
2151
|
+
this.locationListener.remove();
|
|
2152
|
+
this.locationListener = null;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
console.log('Location tracking stopped');
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
console.error('Failed to stop location tracking:', error);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
private handleLocationUpdate(location: LocationResult) {
|
|
2162
|
+
// Store location data locally
|
|
2163
|
+
// Update UI
|
|
2164
|
+
// Send to analytics, etc.
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
```
|
|
2168
|
+
|
|
2169
|
+
### Example 2: Distance-Based Tracking
|
|
2170
|
+
|
|
2171
|
+
```typescript
|
|
2172
|
+
export class DistanceBasedTracking {
|
|
2173
|
+
async startDistanceTracking() {
|
|
2174
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2175
|
+
notification: {
|
|
2176
|
+
title: 'Distance Tracking',
|
|
2177
|
+
text: 'Recording significant movement',
|
|
2178
|
+
},
|
|
2179
|
+
interval: 60000, // 1 minute baseline
|
|
2180
|
+
fastestInterval: 30000, // 30 seconds minimum
|
|
2181
|
+
priority: 'BALANCED_POWER', // Save battery
|
|
2182
|
+
distanceFilter: 50, // Only update every 50 meters
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
await ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
2186
|
+
console.log(`Moved at least 50m: ${location.latitude}, ${location.longitude}`);
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
```
|
|
2191
|
+
|
|
2192
|
+
### Example 3: Battery-Optimized Tracking
|
|
2193
|
+
|
|
2194
|
+
```typescript
|
|
2195
|
+
export class BatteryOptimizedTracking {
|
|
2196
|
+
async startLowPowerTracking() {
|
|
2197
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2198
|
+
notification: {
|
|
2199
|
+
title: 'Low Power Tracking',
|
|
2200
|
+
text: 'Conserving battery while tracking',
|
|
2201
|
+
},
|
|
2202
|
+
interval: 300000, // 5 minutes
|
|
2203
|
+
fastestInterval: 180000, // 3 minutes
|
|
2204
|
+
priority: 'LOW_POWER', // Network-based location
|
|
2205
|
+
distanceFilter: 100, // Only significant movement
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
```
|
|
2210
|
+
|
|
2211
|
+
## API Service Integration
|
|
2212
|
+
|
|
2213
|
+
### Example 4: Basic API Integration
|
|
2214
|
+
|
|
2215
|
+
```typescript
|
|
2216
|
+
export class APILocationService {
|
|
2217
|
+
async startWithAPI() {
|
|
2218
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2219
|
+
notification: {
|
|
2220
|
+
title: 'Route Recording',
|
|
2221
|
+
text: 'Uploading your journey to the cloud',
|
|
2222
|
+
},
|
|
2223
|
+
interval: 30000,
|
|
2224
|
+
priority: 'HIGH_ACCURACY',
|
|
2225
|
+
|
|
2226
|
+
// API service configuration
|
|
2227
|
+
api: {
|
|
2228
|
+
url: 'https://api.yourcompany.com/locations',
|
|
2229
|
+
type: 'POST',
|
|
2230
|
+
header: {
|
|
2231
|
+
'Content-Type': 'application/json',
|
|
2232
|
+
Authorization: 'Bearer YOUR_API_TOKEN',
|
|
2233
|
+
'X-Device-ID': await this.getDeviceId(),
|
|
2234
|
+
},
|
|
2235
|
+
additionalParams: {
|
|
2236
|
+
userId: await this.getCurrentUserId(),
|
|
2237
|
+
sessionId: this.generateSessionId(),
|
|
2238
|
+
appVersion: '1.0.0',
|
|
2239
|
+
},
|
|
2240
|
+
apiInterval: 5, // Send data every 5 minutes
|
|
2241
|
+
},
|
|
2242
|
+
});
|
|
2243
|
+
|
|
2244
|
+
// Monitor API service
|
|
2245
|
+
setInterval(async () => {
|
|
2246
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
2247
|
+
console.log('API Status:', {
|
|
2248
|
+
enabled: status.isEnabled,
|
|
2249
|
+
bufferSize: status.bufferSize,
|
|
2250
|
+
healthy: status.isHealthy,
|
|
2251
|
+
});
|
|
2252
|
+
|
|
2253
|
+
if (!status.isHealthy) {
|
|
2254
|
+
console.warn('API service is unhealthy - check network connection');
|
|
2255
|
+
}
|
|
2256
|
+
}, 60000); // Check every minute
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
private async getDeviceId(): Promise<string> {
|
|
2260
|
+
// Implementation to get unique device ID
|
|
2261
|
+
return 'device-123';
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
private async getCurrentUserId(): Promise<string> {
|
|
2265
|
+
// Implementation to get current user ID
|
|
2266
|
+
return 'user-456';
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
private generateSessionId(): string {
|
|
2270
|
+
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
```
|
|
2274
|
+
|
|
2275
|
+
### Example 5: Advanced API Configuration with Authentication
|
|
2276
|
+
|
|
2277
|
+
```typescript
|
|
2278
|
+
export class AuthenticatedAPIService {
|
|
2279
|
+
private authToken: string = '';
|
|
2280
|
+
|
|
2281
|
+
async startWithAuthenticatedAPI() {
|
|
2282
|
+
// Get fresh auth token
|
|
2283
|
+
this.authToken = await this.refreshAuthToken();
|
|
2284
|
+
|
|
2285
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2286
|
+
notification: {
|
|
2287
|
+
title: 'Secure Tracking',
|
|
2288
|
+
text: 'Securely uploading location data',
|
|
2289
|
+
},
|
|
2290
|
+
interval: 20000,
|
|
2291
|
+
api: {
|
|
2292
|
+
url: 'https://secure-api.yourcompany.com/v1/locations',
|
|
2293
|
+
type: 'POST',
|
|
2294
|
+
header: {
|
|
2295
|
+
'Content-Type': 'application/json',
|
|
2296
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
2297
|
+
'X-API-Version': '1.0',
|
|
2298
|
+
'X-Client-Platform': 'mobile-app',
|
|
2299
|
+
},
|
|
2300
|
+
additionalParams: {
|
|
2301
|
+
userId: await this.getCurrentUserId(),
|
|
2302
|
+
deviceInfo: await this.getDeviceInfo(),
|
|
2303
|
+
trackingMode: 'high-accuracy',
|
|
2304
|
+
timestamp: new Date().toISOString(),
|
|
2305
|
+
},
|
|
2306
|
+
apiInterval: 3, // Send every 3 minutes for real-time tracking
|
|
2307
|
+
},
|
|
2308
|
+
});
|
|
2309
|
+
|
|
2310
|
+
// Set up token refresh
|
|
2311
|
+
this.setupTokenRefresh();
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
private async refreshAuthToken(): Promise<string> {
|
|
2315
|
+
// Your authentication logic
|
|
2316
|
+
const response = await fetch('/api/auth/refresh', {
|
|
2317
|
+
method: 'POST',
|
|
2318
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2319
|
+
body: JSON.stringify({ refreshToken: this.getStoredRefreshToken() }),
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
const data = await response.json();
|
|
2323
|
+
return data.accessToken;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
private setupTokenRefresh() {
|
|
2327
|
+
// Refresh token every 50 minutes (assuming 1-hour expiry)
|
|
2328
|
+
setInterval(
|
|
2329
|
+
async () => {
|
|
2330
|
+
try {
|
|
2331
|
+
this.authToken = await this.refreshAuthToken();
|
|
2332
|
+
console.log('Auth token refreshed successfully');
|
|
2333
|
+
|
|
2334
|
+
// Update the running service with new token (restart with new config)
|
|
2335
|
+
await this.restartWithNewToken();
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
console.error('Failed to refresh auth token:', error);
|
|
2338
|
+
// Handle auth failure - maybe stop tracking or show user notification
|
|
2339
|
+
}
|
|
2340
|
+
},
|
|
2341
|
+
50 * 60 * 1000,
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
private async restartWithNewToken() {
|
|
2346
|
+
// Restart service with new authentication token
|
|
2347
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
2348
|
+
await this.startWithAuthenticatedAPI();
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
```
|
|
2352
|
+
|
|
2353
|
+
### Example 6: API Error Handling and Recovery
|
|
2354
|
+
|
|
2355
|
+
```typescript
|
|
2356
|
+
export class ResilientAPIService {
|
|
2357
|
+
async startResilientTracking() {
|
|
2358
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2359
|
+
notification: {
|
|
2360
|
+
title: 'Smart Tracking',
|
|
2361
|
+
text: 'Intelligent location sync with failover',
|
|
2362
|
+
},
|
|
2363
|
+
interval: 15000,
|
|
2364
|
+
api: {
|
|
2365
|
+
url: 'https://primary-api.yourcompany.com/locations',
|
|
2366
|
+
type: 'POST',
|
|
2367
|
+
header: {
|
|
2368
|
+
'Content-Type': 'application/json',
|
|
2369
|
+
Authorization: 'Bearer YOUR_TOKEN',
|
|
2370
|
+
},
|
|
2371
|
+
apiInterval: 2, // Frequent sync
|
|
2372
|
+
},
|
|
2373
|
+
});
|
|
2374
|
+
|
|
2375
|
+
// Monitor and handle API issues
|
|
2376
|
+
this.startAPIMonitoring();
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
private startAPIMonitoring() {
|
|
2380
|
+
setInterval(async () => {
|
|
2381
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
2382
|
+
|
|
2383
|
+
if (!status.isHealthy) {
|
|
2384
|
+
console.warn('API service is unhealthy');
|
|
2385
|
+
|
|
2386
|
+
// Check if buffer is getting too large
|
|
2387
|
+
if (status.bufferSize > 100) {
|
|
2388
|
+
console.warn('Large buffer detected, may need intervention');
|
|
2389
|
+
|
|
2390
|
+
// Option 1: Clear buffers to prevent memory issues
|
|
2391
|
+
// await ForeGroundLocation.clearApiBuffers();
|
|
2392
|
+
|
|
2393
|
+
// Option 2: Reset circuit breaker to retry immediately
|
|
2394
|
+
await ForeGroundLocation.resetApiCircuitBreaker();
|
|
2395
|
+
|
|
2396
|
+
// Option 3: Switch to backup API endpoint
|
|
2397
|
+
await this.switchToBackupAPI();
|
|
2398
|
+
}
|
|
2399
|
+
} else if (status.bufferSize === 0) {
|
|
2400
|
+
console.log('API service healthy, all data synced');
|
|
2401
|
+
}
|
|
2402
|
+
}, 30000); // Check every 30 seconds
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
private async switchToBackupAPI() {
|
|
2406
|
+
console.log('Switching to backup API endpoint');
|
|
2407
|
+
|
|
2408
|
+
await ForeGroundLocation.stopForegroundLocationService();
|
|
2409
|
+
|
|
2410
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2411
|
+
notification: {
|
|
2412
|
+
title: 'Backup Sync',
|
|
2413
|
+
text: 'Using backup server for location sync',
|
|
2414
|
+
},
|
|
2415
|
+
interval: 15000,
|
|
2416
|
+
api: {
|
|
2417
|
+
url: 'https://backup-api.yourcompany.com/locations',
|
|
2418
|
+
type: 'POST',
|
|
2419
|
+
header: {
|
|
2420
|
+
'Content-Type': 'application/json',
|
|
2421
|
+
Authorization: 'Bearer YOUR_TOKEN',
|
|
2422
|
+
},
|
|
2423
|
+
apiInterval: 5, // Less frequent on backup
|
|
2424
|
+
},
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
```
|
|
2429
|
+
|
|
2430
|
+
## Advanced Configuration
|
|
2431
|
+
|
|
2432
|
+
### Example 7: Dynamic Configuration Updates
|
|
2433
|
+
|
|
2434
|
+
```typescript
|
|
2435
|
+
export class DynamicLocationService {
|
|
2436
|
+
private currentMode: 'normal' | 'high-accuracy' | 'battery-saver' = 'normal';
|
|
2437
|
+
|
|
2438
|
+
async startDynamicTracking() {
|
|
2439
|
+
await this.updateConfigurationFor('normal');
|
|
2440
|
+
|
|
2441
|
+
// Set up dynamic configuration based on app state
|
|
2442
|
+
document.addEventListener('visibilitychange', () => {
|
|
2443
|
+
if (document.hidden) {
|
|
2444
|
+
this.switchMode('battery-saver');
|
|
2445
|
+
} else {
|
|
2446
|
+
this.switchMode('high-accuracy');
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
async switchMode(mode: 'normal' | 'high-accuracy' | 'battery-saver') {
|
|
2452
|
+
if (this.currentMode === mode) return;
|
|
2453
|
+
|
|
2454
|
+
console.log(`Switching to ${mode} mode`);
|
|
2455
|
+
this.currentMode = mode;
|
|
2456
|
+
|
|
2457
|
+
await this.updateConfigurationFor(mode);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
private async updateConfigurationFor(mode: string) {
|
|
2461
|
+
const configs = {
|
|
2462
|
+
'high-accuracy': {
|
|
2463
|
+
notification: {
|
|
2464
|
+
title: 'High Accuracy Mode',
|
|
2465
|
+
text: 'Precise location tracking active',
|
|
2466
|
+
},
|
|
2467
|
+
interval: 5000,
|
|
2468
|
+
fastestInterval: 2000,
|
|
2469
|
+
priority: 'HIGH_ACCURACY' as const,
|
|
2470
|
+
distanceFilter: 5,
|
|
2471
|
+
api: { apiInterval: 1 },
|
|
2472
|
+
},
|
|
2473
|
+
normal: {
|
|
2474
|
+
notification: {
|
|
2475
|
+
title: 'Standard Tracking',
|
|
2476
|
+
text: 'Normal location tracking',
|
|
2477
|
+
},
|
|
2478
|
+
interval: 30000,
|
|
2479
|
+
fastestInterval: 15000,
|
|
2480
|
+
priority: 'BALANCED_POWER' as const,
|
|
2481
|
+
distanceFilter: 20,
|
|
2482
|
+
api: { apiInterval: 5 },
|
|
2483
|
+
},
|
|
2484
|
+
'battery-saver': {
|
|
2485
|
+
notification: {
|
|
2486
|
+
title: 'Battery Saver Mode',
|
|
2487
|
+
text: 'Low power location tracking',
|
|
2488
|
+
},
|
|
2489
|
+
interval: 300000,
|
|
2490
|
+
fastestInterval: 180000,
|
|
2491
|
+
priority: 'LOW_POWER' as const,
|
|
2492
|
+
distanceFilter: 100,
|
|
2493
|
+
api: { apiInterval: 15 },
|
|
2494
|
+
},
|
|
2495
|
+
};
|
|
2496
|
+
|
|
2497
|
+
const config = configs[mode];
|
|
2498
|
+
const baseApiConfig = {
|
|
2499
|
+
url: 'https://api.yourcompany.com/locations',
|
|
2500
|
+
type: 'POST' as const,
|
|
2501
|
+
header: {
|
|
2502
|
+
'Content-Type': 'application/json',
|
|
2503
|
+
Authorization: 'Bearer YOUR_TOKEN',
|
|
2504
|
+
},
|
|
2505
|
+
additionalParams: {
|
|
2506
|
+
trackingMode: mode,
|
|
2507
|
+
},
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
await ForeGroundLocation.updateLocationSettings({
|
|
2511
|
+
...config,
|
|
2512
|
+
api: { ...baseApiConfig, ...config.api },
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
```
|
|
2517
|
+
|
|
2518
|
+
## Error Handling
|
|
2519
|
+
|
|
2520
|
+
### Example 8: Comprehensive Error Handling
|
|
2521
|
+
|
|
2522
|
+
```typescript
|
|
2523
|
+
export class ErrorHandlingLocationService {
|
|
2524
|
+
async startWithErrorHandling() {
|
|
2525
|
+
try {
|
|
2526
|
+
await this.initializeLocationService();
|
|
2527
|
+
} catch (error) {
|
|
2528
|
+
await this.handleLocationError(error);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
private async initializeLocationService() {
|
|
2533
|
+
// Check permissions first
|
|
2534
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
2535
|
+
|
|
2536
|
+
if (permissions.location === 'denied') {
|
|
2537
|
+
throw new Error('PERMISSION_PERMANENTLY_DENIED');
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
if (permissions.location !== 'granted') {
|
|
2541
|
+
const result = await ForeGroundLocation.requestPermissions();
|
|
2542
|
+
if (result.location !== 'granted') {
|
|
2543
|
+
throw new Error('PERMISSION_DENIED');
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// Test location availability
|
|
2548
|
+
try {
|
|
2549
|
+
await ForeGroundLocation.getCurrentLocation();
|
|
2550
|
+
} catch (error) {
|
|
2551
|
+
throw new Error('LOCATION_NOT_AVAILABLE');
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// Start service with error handling
|
|
2555
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2556
|
+
notification: {
|
|
2557
|
+
title: 'Location Service',
|
|
2558
|
+
text: 'Tracking with error handling',
|
|
2559
|
+
},
|
|
2560
|
+
interval: 30000,
|
|
2561
|
+
priority: 'HIGH_ACCURACY',
|
|
2562
|
+
});
|
|
2563
|
+
|
|
2564
|
+
// Set up error monitoring
|
|
2565
|
+
this.startErrorMonitoring();
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
private async handleLocationError(error: Error) {
|
|
2569
|
+
console.error('Location service error:', error.message);
|
|
2570
|
+
|
|
2571
|
+
switch (error.message) {
|
|
2572
|
+
case 'PERMISSION_DENIED':
|
|
2573
|
+
await this.showPermissionDialog();
|
|
2574
|
+
break;
|
|
2575
|
+
|
|
2576
|
+
case 'PERMISSION_PERMANENTLY_DENIED':
|
|
2577
|
+
await this.showSettingsDialog();
|
|
2578
|
+
break;
|
|
2579
|
+
|
|
2580
|
+
case 'LOCATION_NOT_AVAILABLE':
|
|
2581
|
+
await this.showLocationDisabledDialog();
|
|
2582
|
+
break;
|
|
2583
|
+
|
|
2584
|
+
case 'INVALID_NOTIFICATION':
|
|
2585
|
+
console.error('Invalid notification configuration');
|
|
2586
|
+
break;
|
|
2587
|
+
|
|
2588
|
+
case 'INVALID_PARAMETERS':
|
|
2589
|
+
console.error('Invalid service parameters');
|
|
2590
|
+
break;
|
|
2591
|
+
|
|
2592
|
+
default:
|
|
2593
|
+
await this.showGenericErrorDialog(error.message);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
private startErrorMonitoring() {
|
|
2598
|
+
// Monitor service status
|
|
2599
|
+
const statusListener = ForeGroundLocation.addListener('serviceStatus', (status) => {
|
|
2600
|
+
if (!status.isRunning && status.error) {
|
|
2601
|
+
console.error('Service stopped with error:', status.error);
|
|
2602
|
+
this.handleServiceError(status.error);
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
2605
|
+
|
|
2606
|
+
// Periodic health check
|
|
2607
|
+
setInterval(async () => {
|
|
2608
|
+
try {
|
|
2609
|
+
const isRunning = await ForeGroundLocation.isServiceRunning();
|
|
2610
|
+
if (!isRunning.isRunning) {
|
|
2611
|
+
console.warn('Service is not running, attempting restart');
|
|
2612
|
+
await this.attemptServiceRestart();
|
|
2613
|
+
}
|
|
2614
|
+
} catch (error) {
|
|
2615
|
+
console.error('Health check failed:', error);
|
|
2616
|
+
}
|
|
2617
|
+
}, 60000);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
private async attemptServiceRestart() {
|
|
2621
|
+
try {
|
|
2622
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2623
|
+
notification: {
|
|
2624
|
+
title: 'Location Service Restored',
|
|
2625
|
+
text: 'Resuming location tracking',
|
|
2626
|
+
},
|
|
2627
|
+
interval: 30000,
|
|
2628
|
+
});
|
|
2629
|
+
console.log('Service restarted successfully');
|
|
2630
|
+
} catch (error) {
|
|
2631
|
+
console.error('Failed to restart service:', error);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
private async showPermissionDialog() {
|
|
2636
|
+
// Show user-friendly permission request dialog
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
private async showSettingsDialog() {
|
|
2640
|
+
// Guide user to app settings
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
private async showLocationDisabledDialog() {
|
|
2644
|
+
// Guide user to enable device location
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
private async showGenericErrorDialog(errorMessage: string) {
|
|
2648
|
+
// Show generic error with details
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
private handleServiceError(error: string) {
|
|
2652
|
+
// Handle service-specific errors
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
```
|
|
2656
|
+
|
|
2657
|
+
## Platform-Specific Setup
|
|
2658
|
+
|
|
2659
|
+
### Android-Specific Configuration
|
|
2660
|
+
|
|
2661
|
+
```typescript
|
|
2662
|
+
export class AndroidLocationService {
|
|
2663
|
+
async setupAndroidSpecific() {
|
|
2664
|
+
// Check Android version and adjust configuration
|
|
2665
|
+
const isAndroid10Plus = this.isAndroidVersionAtLeast(10);
|
|
2666
|
+
|
|
2667
|
+
if (isAndroid10Plus) {
|
|
2668
|
+
// Android 10+ requires background location permission
|
|
2669
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
2670
|
+
|
|
2671
|
+
if (permissions.backgroundLocation !== 'granted') {
|
|
2672
|
+
// Show explanation before requesting background permission
|
|
2673
|
+
await this.showBackgroundLocationExplanation();
|
|
2674
|
+
|
|
2675
|
+
const result = await ForeGroundLocation.requestPermissions();
|
|
2676
|
+
if (result.backgroundLocation !== 'granted') {
|
|
2677
|
+
console.warn('Background location not granted, some features may be limited');
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// Configure for Android optimization
|
|
2683
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2684
|
+
notification: {
|
|
2685
|
+
title: 'Location Service',
|
|
2686
|
+
text: 'Optimized for Android',
|
|
2687
|
+
icon: 'ic_location_android', // Android-specific icon
|
|
2688
|
+
},
|
|
2689
|
+
interval: 15000,
|
|
2690
|
+
priority: 'HIGH_ACCURACY',
|
|
2691
|
+
// Android-specific optimizations
|
|
2692
|
+
distanceFilter: 0, // Let Android handle filtering
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
private isAndroidVersionAtLeast(version: number): boolean {
|
|
2697
|
+
// Implementation to check Android version
|
|
2698
|
+
return true; // Placeholder
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
private async showBackgroundLocationExplanation() {
|
|
2702
|
+
// Show explanation dialog
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
```
|
|
2706
|
+
|
|
2707
|
+
### iOS-Specific Configuration
|
|
2708
|
+
|
|
2709
|
+
```typescript
|
|
2710
|
+
export class iOSLocationService {
|
|
2711
|
+
async setupiOSSpecific() {
|
|
2712
|
+
// iOS has different location permission model
|
|
2713
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2714
|
+
notification: {
|
|
2715
|
+
title: 'Location Access',
|
|
2716
|
+
text: 'iOS location service active',
|
|
2717
|
+
},
|
|
2718
|
+
interval: 60000, // iOS may batch updates
|
|
2719
|
+
priority: 'HIGH_ACCURACY',
|
|
2720
|
+
// iOS handles most optimization automatically
|
|
2721
|
+
});
|
|
2722
|
+
|
|
2723
|
+
// iOS-specific monitoring
|
|
2724
|
+
ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
2725
|
+
// iOS may provide delayed/batched updates
|
|
2726
|
+
console.log('iOS location update:', location);
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
```
|
|
2731
|
+
|
|
2732
|
+
## Troubleshooting
|
|
2733
|
+
|
|
2734
|
+
### Common Issues and Solutions
|
|
2735
|
+
|
|
2736
|
+
```typescript
|
|
2737
|
+
export class TroubleshootingService {
|
|
2738
|
+
async diagnoseIssues() {
|
|
2739
|
+
console.log('Running location service diagnostics...');
|
|
2740
|
+
|
|
2741
|
+
// 1. Check permissions
|
|
2742
|
+
const permissions = await ForeGroundLocation.checkPermissions();
|
|
2743
|
+
console.log('Permissions:', permissions);
|
|
2744
|
+
|
|
2745
|
+
// 2. Test basic location access
|
|
2746
|
+
try {
|
|
2747
|
+
const location = await ForeGroundLocation.getCurrentLocation();
|
|
2748
|
+
console.log('Basic location access: OK', location);
|
|
2749
|
+
} catch (error) {
|
|
2750
|
+
console.error('Basic location access: FAILED', error);
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
// 3. Check service status
|
|
2754
|
+
const serviceStatus = await ForeGroundLocation.isServiceRunning();
|
|
2755
|
+
console.log('Service running:', serviceStatus.isRunning);
|
|
2756
|
+
|
|
2757
|
+
// 4. Check API service if configured
|
|
2758
|
+
try {
|
|
2759
|
+
const apiStatus = await ForeGroundLocation.getApiServiceStatus();
|
|
2760
|
+
console.log('API Service:', {
|
|
2761
|
+
enabled: apiStatus.isEnabled,
|
|
2762
|
+
bufferSize: apiStatus.bufferSize,
|
|
2763
|
+
healthy: apiStatus.isHealthy,
|
|
2764
|
+
});
|
|
2765
|
+
} catch (error) {
|
|
2766
|
+
console.log('API Service: Not configured or unavailable');
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
// 5. Performance metrics
|
|
2770
|
+
this.startPerformanceMonitoring();
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
private startPerformanceMonitoring() {
|
|
2774
|
+
let updateCount = 0;
|
|
2775
|
+
let lastUpdate = Date.now();
|
|
2776
|
+
|
|
2777
|
+
ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
2778
|
+
updateCount++;
|
|
2779
|
+
const now = Date.now();
|
|
2780
|
+
const timeSinceLastUpdate = now - lastUpdate;
|
|
2781
|
+
lastUpdate = now;
|
|
2782
|
+
|
|
2783
|
+
console.log(`Update #${updateCount}, ${timeSinceLastUpdate}ms since last update`);
|
|
2784
|
+
|
|
2785
|
+
// Check for issues
|
|
2786
|
+
if (timeSinceLastUpdate > 120000) {
|
|
2787
|
+
// 2 minutes
|
|
2788
|
+
console.warn('Long gap between updates detected');
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
if (location.accuracy > 100) {
|
|
2792
|
+
// 100 meters
|
|
2793
|
+
console.warn('Low accuracy location received:', location.accuracy);
|
|
2794
|
+
}
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
```
|
|
2799
|
+
|
|
2800
|
+
### Debug Configuration
|
|
2801
|
+
|
|
2802
|
+
```typescript
|
|
2803
|
+
export class DebugLocationService {
|
|
2804
|
+
async startDebugMode() {
|
|
2805
|
+
await ForeGroundLocation.startForegroundLocationService({
|
|
2806
|
+
notification: {
|
|
2807
|
+
title: 'DEBUG: Location Service',
|
|
2808
|
+
text: 'Debug mode active - check console',
|
|
2809
|
+
},
|
|
2810
|
+
interval: 10000, // Frequent updates for debugging
|
|
2811
|
+
fastestInterval: 5000,
|
|
2812
|
+
priority: 'HIGH_ACCURACY',
|
|
2813
|
+
distanceFilter: 0, // No filtering for debugging
|
|
2814
|
+
|
|
2815
|
+
api: {
|
|
2816
|
+
url: 'https://httpbin.org/post', // Test endpoint
|
|
2817
|
+
type: 'POST',
|
|
2818
|
+
header: {
|
|
2819
|
+
'Content-Type': 'application/json',
|
|
2820
|
+
'X-Debug': 'true',
|
|
2821
|
+
},
|
|
2822
|
+
additionalParams: {
|
|
2823
|
+
debugMode: true,
|
|
2824
|
+
timestamp: new Date().toISOString(),
|
|
2825
|
+
},
|
|
2826
|
+
apiInterval: 1, // Every minute for testing
|
|
2827
|
+
},
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2830
|
+
// Detailed logging
|
|
2831
|
+
ForeGroundLocation.addListener('locationUpdate', (location) => {
|
|
2832
|
+
console.log('DEBUG Location Update:', {
|
|
2833
|
+
coordinates: `${location.latitude}, ${location.longitude}`,
|
|
2834
|
+
accuracy: `${location.accuracy}m`,
|
|
2835
|
+
timestamp: location.timestamp,
|
|
2836
|
+
hasAltitude: !!location.altitude,
|
|
2837
|
+
hasBearing: !!location.bearing,
|
|
2838
|
+
hasSpeed: !!location.speed,
|
|
2839
|
+
});
|
|
2840
|
+
});
|
|
2841
|
+
|
|
2842
|
+
// Monitor API service in detail
|
|
2843
|
+
setInterval(async () => {
|
|
2844
|
+
const status = await ForeGroundLocation.getApiServiceStatus();
|
|
2845
|
+
console.log('DEBUG API Status:', status);
|
|
2846
|
+
}, 30000);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
```
|
|
2850
|
+
|
|
2851
|
+
This comprehensive guide covers all aspects of setting up and using the Foreground Location Plugin with API service integration. Each example is production-ready and includes proper error handling and best practices.
|