@bglocation/capacitor
v1.1.1
Published
Capacitor plugin for continuous background location tracking on iOS and Android
Maintainers
Readme
@bglocation/capacitor
Capacitor 8 plugin for continuous background location tracking on iOS and Android with optional native HTTP POST.
Status: POC (Proof of Concept)
Features
- Continuous GPS tracking in background (iOS + Android)
- Configurable
distanceFilter,desiredAccuracy, update intervals - Adaptive distance filter —
distanceFilter: 'auto'dynamically adjusts based on speed (EMA smoothing) to maintain ~10s update interval - Heartbeat timer — periodic event even when stationary
- Native HTTP POST — send location to backend from native layer
- Offline buffer — SQLite-backed queue for failed HTTP requests with automatic retry
- Configurable notification — custom title and text for Android foreground service notification (i18n ready)
- Debug mode — verbose logs,
onDebugevents, optional system sounds, dynamic Android notification with GPS counters - OEM battery killer detection — warns when battery optimization may kill tracking (Android), with dontkillmyapp.com links
- Approximate location warning — detects and warns when user granted only approximate location (iOS 14+)
- Significant Location Change fallback — SLC watchdog auto-restarts GPS after iOS kills the app (iOS)
- Mock location detection — detects mock/test location providers (Android)
- Permission rationale — emits event before requesting background location on Android 11+ for custom rationale UI
- Geofencing — monitor enter/exit/dwell for up to 20 circular regions (iOS + Android), with persistence across app restarts
- Events:
onLocation,onHeartbeat,onHttp,onProviderChange,onDebug,onBatteryWarning,onAccuracyWarning,onMockLocation,onPermissionRationale,onTrialExpired,onGeofence - Web fallback for development/testing in browser
- 774 unit tests across 3 platforms
Documentation
Detailed documentation is in the .wiki/ directory:
| Document | Description | |----------|-------------| | Architecture | System architecture, data flow, platform differences | | API Reference | Full API: methods, events, interfaces, error codes | | iOS | CLLocationManager, permissions, heartbeat, background modes | | Android | Foreground Service, FusedLocation, permissions, binding | | Web | Browser fallback implementation | | HTTP | Native HTTP sender: config, format, timeouts, errors | | Testing | Test strategy, running tests, CI integration | | Geofencing | Geofence API, dwell, persistence, platform differences | | Deployment | Production checklist: license key, platform requirements, troubleshooting | | Known Limitations | POC limitations and production roadmap |
Platforms
| Platform | Min Version | Implementation |
|----------|-------------|----------------|
| iOS | 15.0 | CLLocationManager with background modes |
| Android | API 26 | FusedLocationProviderClient + Foreground Service |
| Web | — | navigator.geolocation (dev/testing fallback) |
Installation
npm install @bglocation/capacitor
npx cap synciOS
Add all of the following keys to Info.plist:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location to track the tour route</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show your position on the map</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>Important: Both
NSLocationAlwaysAndWhenInUseUsageDescriptionandNSLocationWhenInUseUsageDescriptionare required. Missing either will cause a runtime crash with a privacy violation. The plugin callsrequestAlwaysAuthorization()automatically when tracking starts if the user hasn't been prompted yet.
allowsBackgroundLocationUpdates = trueis set internally by the plugin duringconfigure(), before the app enters background — this is required by iOS.
Android
Permissions are declared in the plugin's AndroidManifest.xml and merged automatically:
ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATIONACCESS_BACKGROUND_LOCATION(Android 10+)FOREGROUND_SERVICE/FOREGROUND_SERVICE_LOCATION
Your app must request permissions before calling start(). The plugin only checks permissions — it does not prompt the user. On Android 10+ (API 29), the user must explicitly grant "Allow all the time" for background location to work. If permissions are not granted, start() will reject with an error.
iOS vs Android difference: iOS auto-prompts for
Alwaysauthorization viarequestAlwaysAuthorization(). Android requires the app to handle permission requests explicitly.
Quick Start
import { BackgroundLocation } from '@bglocation/capacitor';
// 1. Request permissions
const perms = await BackgroundLocation.checkPermissions();
if (perms.location !== 'granted') {
await BackgroundLocation.requestPermissions({ permissions: ['location'] });
}
if (perms.backgroundLocation !== 'granted') {
await BackgroundLocation.requestPermissions({ permissions: ['backgroundLocation'] });
}
// 2. Configure
await BackgroundLocation.configure({
distanceFilter: 15, // meters
desiredAccuracy: 'high', // 'high' | 'balanced' | 'low'
locationUpdateInterval: 5000, // ms (Android only)
fastestLocationUpdateInterval: 2000, // ms (Android only)
heartbeatInterval: 15, // seconds
http: { // optional — native HTTP POST
url: 'https://api.example.com/location',
headers: { Authorization: 'Bearer <token>' },
buffer: { maxSize: 1000 }, // optional — offline buffer for failed requests
},
notification: { // optional — Android foreground service notification
title: 'Tour Active',
text: 'Tracking your position',
},
debug: true, // optional — verbose logging + onDebug events
debugSounds: true, // optional — system sounds on events (requires debug: true)
});
// 3. Listen for events
BackgroundLocation.addListener('onLocation', (location) => {
console.log('Location:', location.latitude, location.longitude);
});
BackgroundLocation.addListener('onHeartbeat', (event) => {
console.log('Heartbeat:', event.location);
});
BackgroundLocation.addListener('onHttp', (event) => {
console.log('HTTP:', event.success, event.statusCode, 'buffered:', event.bufferedCount);
});
BackgroundLocation.addListener('onDebug', (event) => {
console.log('Debug:', event.message);
});
// 4. Start tracking
await BackgroundLocation.start();
// ... later ...
// 5. Stop tracking
await BackgroundLocation.stop();
await BackgroundLocation.removeAllListeners();API Overview
Methods
| Method | Returns | Description |
|--------|---------|-------------|
| checkPermissions() | LocationPermissionStatus | Check current permission state |
| requestPermissions(options?) | LocationPermissionStatus | Request location permissions |
| configure(options) | ConfigureResult | Set tracking parameters (call before start()). Supports partial reconfiguration — omitted fields are merged with previous config. |
| start() | LocationState | Start background tracking |
| stop() | LocationState | Stop tracking |
| getState() | LocationState | Get current plugin state |
| getCurrentPosition(options?) | Location | One-shot position fix |
| checkBatteryOptimization() | BatteryWarningEvent | Check OEM battery optimization state (Android) |
| requestBatteryOptimization() | BatteryWarningEvent | Open battery optimization settings (Android) |
| addGeofence(options) | void | Add a geofence (max 20) |
| addGeofences(options) | void | Add multiple geofences atomically |
| removeGeofence(options) | void | Remove a geofence by identifier |
| removeAllGeofences() | void | Remove all geofences |
| getGeofences() | { geofences: Geofence[] } | Get registered geofences |
| removeAllListeners() | void | Remove all listeners and stop tracking |
Events
| Event | Payload | Trigger |
|-------|---------|---------|
| onLocation | Location | Each GPS update (per distanceFilter) |
| onHeartbeat | HeartbeatEvent | Every heartbeatInterval seconds |
| onHttp | HttpEvent | After each native HTTP POST response (includes bufferedCount) |
| onProviderChange | ProviderChangeEvent | GPS/permission status change |
| onDebug | DebugEvent | Debug log message (when debug: true) |
| onBatteryWarning | BatteryWarningEvent | Battery optimization may kill tracking (Android only) |
| onAccuracyWarning | AccuracyWarningEvent | Approximate location granted (iOS only) |
| onMockLocation | MockLocationEvent | Mock location detected — once per session (Android only) |
| onPermissionRationale | PermissionRationaleEvent | Before requesting background location (Android 11+ only) |
| onGeofence | GeofenceEvent | Geofence enter/exit/dwell transition |
Full API documentation: .wiki/API.md
Licensing
The plugin uses an offline RSA-2048-SHA256 license system with a perpetual + update gating model:
- Full mode: No restrictions. Requires a valid license key with active updates.
- Trial mode:
debug=trueforced, 30 min tracking limit, 1 h cooldown.
Update Gating
Each plugin build embeds a build timestamp. If the license's update period (exp) has ended before the build date, the plugin runs in trial mode:
License exp = 2027-03-16 (1 year from purchase)
Plugin v1.2 (built 2026-05) → exp > build → FULL ✅ forever
Plugin v2.0 (built 2027-06) → exp < build → TRIAL ⚠️ renewal neededLicense Renewal
- Sign in to the License Portal
- Expired licenses show a red Expired badge
- Click Renew to generate a new key with a fresh 1-year update period
- Update
capacitor.config.tswith the new key
See .wiki/Licensing.md for full details.
Testing
npm test # Web (Vitest) — 164 tests
npm run test:android # Android (JUnit + MockK) — 343 tests
npm run test:ios # iOS (XCTest via SPM) — 267 testsTotal: 774 tests across 3 platforms (+ 116 in test app = 890 total).
See .wiki/Testing.md for details.
Development
npm run build # TypeScript → dist/
npm run test:watch # Vitest watch mode
npm run verify # Build + test both native platforms
npm run verify:ios # Pod install + xcodebuild
npm run verify:android # Gradle clean build testDebug Sounds
When debug: true and debugSounds: true, the plugin plays system sounds on key events:
| Event | iOS | Android |
|-------|-----|---------|
| Start tracking | Begin Recording (1054) | TONE_PROP_BEEP |
| Stop tracking | End Recording (1055) | TONE_PROP_BEEP2 |
| Location fix | Tink (1052) | TONE_PROP_ACK |
| Heartbeat | Tock (1057) | TONE_SUP_CONFIRM |
| HTTP error | SMS Alert (1073) | TONE_SUP_ERROR |
| HTTP success | — (silent) | — (silent) |
| Geofence add | Key Press Click (1116) | TONE_PROP_ACK |
| Geofence event | Key Press Delete (1117) | TONE_SUP_CONFIRM |
Web platform does not play sounds — debug events are available via
onDebuglistener andconsole.debug.
Known Limitations
- No motion/activity detection
See .wiki/Known-Limitations.md for full list and production roadmap.
License
ELv2 — all rights reserved.
