@bglocation/capacitor 1.1.0
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/CapacitorBackgroundLocation.podspec +19 -0
- package/LICENSE.md +97 -0
- package/Package.swift +44 -0
- package/README.md +264 -0
- package/android/build.gradle +74 -0
- package/android/src/main/AndroidManifest.xml +37 -0
- package/android/src/main/kotlin/dev/bglocation/BackgroundLocationPlugin.kt +684 -0
- package/android/src/main/kotlin/dev/bglocation/core/Models.kt +76 -0
- package/android/src/main/kotlin/dev/bglocation/core/battery/BGLBatteryHelper.kt +127 -0
- package/android/src/main/kotlin/dev/bglocation/core/boot/BGLBootCompletedReceiver.kt +32 -0
- package/android/src/main/kotlin/dev/bglocation/core/config/BGLConfigParser.kt +114 -0
- package/android/src/main/kotlin/dev/bglocation/core/config/BGLVersion.kt +6 -0
- package/android/src/main/kotlin/dev/bglocation/core/debug/BGLDebugLogger.kt +174 -0
- package/android/src/main/kotlin/dev/bglocation/core/geofence/BGLGeofenceBroadcastReceiver.kt +93 -0
- package/android/src/main/kotlin/dev/bglocation/core/geofence/BGLGeofenceManager.kt +310 -0
- package/android/src/main/kotlin/dev/bglocation/core/http/BGLHttpSender.kt +187 -0
- package/android/src/main/kotlin/dev/bglocation/core/http/BGLLocationBuffer.kt +152 -0
- package/android/src/main/kotlin/dev/bglocation/core/license/BGLBuildConfig.kt +16 -0
- package/android/src/main/kotlin/dev/bglocation/core/license/BGLLicenseEnforcer.kt +137 -0
- package/android/src/main/kotlin/dev/bglocation/core/license/BGLLicenseValidator.kt +134 -0
- package/android/src/main/kotlin/dev/bglocation/core/license/BGLTrialTimer.kt +176 -0
- package/android/src/main/kotlin/dev/bglocation/core/location/BGLAdaptiveFilter.kt +94 -0
- package/android/src/main/kotlin/dev/bglocation/core/location/BGLHeartbeatTimer.kt +38 -0
- package/android/src/main/kotlin/dev/bglocation/core/location/BGLLocationForegroundService.kt +289 -0
- package/android/src/main/kotlin/dev/bglocation/core/location/BGLLocationHelpers.kt +72 -0
- package/android/src/main/kotlin/dev/bglocation/core/location/BGLPermissionManager.kt +99 -0
- package/android/src/main/kotlin/dev/bglocation/core/notification/BGLNotificationHelper.kt +77 -0
- package/dist/esm/definitions.d.ts +390 -0
- package/dist/esm/definitions.js +3 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +26 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +47 -0
- package/dist/esm/web.js +231 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/esm/web.test.d.ts +1 -0
- package/dist/esm/web.test.js +940 -0
- package/dist/esm/web.test.js.map +1 -0
- package/dist/plugin.cjs.js +267 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +270 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/BGLocationCore/Config/BGLConfigParser.swift +88 -0
- package/ios/Sources/BGLocationCore/Config/BGLVersion.swift +6 -0
- package/ios/Sources/BGLocationCore/Debug/BGLDebugLogger.swift +201 -0
- package/ios/Sources/BGLocationCore/Geofence/BGLGeofenceManager.swift +538 -0
- package/ios/Sources/BGLocationCore/Http/BGLHttpSender.swift +227 -0
- package/ios/Sources/BGLocationCore/Http/BGLLocationBuffer.swift +198 -0
- package/ios/Sources/BGLocationCore/License/BGLBuildConfig.swift +11 -0
- package/ios/Sources/BGLocationCore/License/BGLLicenseEnforcer.swift +134 -0
- package/ios/Sources/BGLocationCore/License/BGLLicenseValidator.swift +163 -0
- package/ios/Sources/BGLocationCore/License/BGLTrialTimer.swift +168 -0
- package/ios/Sources/BGLocationCore/Location/BGLAdaptiveFilter.swift +91 -0
- package/ios/Sources/BGLocationCore/Location/BGLHeartbeatTimer.swift +50 -0
- package/ios/Sources/BGLocationCore/Location/BGLLocationData.swift +48 -0
- package/ios/Sources/BGLocationCore/Location/BGLLocationHelpers.swift +42 -0
- package/ios/Sources/BGLocationCore/Location/BGLLocationManager.swift +268 -0
- package/ios/Sources/BGLocationCore/Location/BGLPermissionManager.swift +33 -0
- package/ios/Sources/BackgroundLocationPlugin/BackgroundLocationPlugin.swift +657 -0
- package/package.json +75 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import CoreLocation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Wrapper around CLLocationManager providing continuous background location tracking.
|
|
5
|
+
///
|
|
6
|
+
/// Key iOS requirements for background location:
|
|
7
|
+
/// - `allowsBackgroundLocationUpdates = true` MUST be set BEFORE app enters background
|
|
8
|
+
/// - `pausesLocationUpdatesAutomatically = false` is CRITICAL — prevents iOS from pausing GPS
|
|
9
|
+
/// - `activityType = .otherNavigation` gives navigation-app priority
|
|
10
|
+
/// - `showsBackgroundLocationIndicator = true` shows the blue pill on status bar
|
|
11
|
+
public class BGLLocationManager: NSObject, CLLocationManagerDelegate, BGLLocationProvider {
|
|
12
|
+
|
|
13
|
+
/// Accuracy threshold above which a location is considered SLC-delivered (not standard GPS).
|
|
14
|
+
/// SLC typically delivers accuracy of ~500m; standard GPS is usually <100m after warm-up.
|
|
15
|
+
private static let slcAccuracyThreshold: CLLocationDistance = 200
|
|
16
|
+
|
|
17
|
+
/// Speed threshold (m/s) above which the device is considered moving.
|
|
18
|
+
/// Used to derive the `isMoving` flag sent to JS and HTTP layers.
|
|
19
|
+
public static let motionSpeedThreshold: Double = 0.5
|
|
20
|
+
|
|
21
|
+
/// Callback fired on each new location update.
|
|
22
|
+
public var onLocation: ((CLLocation, Bool) -> Void)?
|
|
23
|
+
|
|
24
|
+
/// Callback fired when authorization status changes.
|
|
25
|
+
public var onProviderChange: ((CLAuthorizationStatus) -> Void)?
|
|
26
|
+
|
|
27
|
+
/// Callback fired when accuracy authorization changes (iOS 14+).
|
|
28
|
+
public var onAccuracyChange: ((CLAccuracyAuthorization) -> Void)?
|
|
29
|
+
|
|
30
|
+
/// Callback fired when Significant Location Change wakes the app and restarts tracking.
|
|
31
|
+
public var onSignificantLocationChange: (() -> Void)?
|
|
32
|
+
|
|
33
|
+
/// Whether tracking is currently active.
|
|
34
|
+
public private(set) var isTracking = false
|
|
35
|
+
|
|
36
|
+
/// Guards against repeated SLC restart calls while GPS is warming up.
|
|
37
|
+
private var slcRestartFired = false
|
|
38
|
+
|
|
39
|
+
/// Last received location (used by heartbeat for force-emit).
|
|
40
|
+
public private(set) var lastKnownLocation: CLLocation?
|
|
41
|
+
|
|
42
|
+
/// Whether user has granted at least "When In Use" authorization.
|
|
43
|
+
public var isAuthorized: Bool {
|
|
44
|
+
let status = manager.authorizationStatus
|
|
45
|
+
return status == .authorizedAlways || status == .authorizedWhenInUse
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Current authorization status (exposed for permission checks).
|
|
49
|
+
public var authorizationStatus: CLAuthorizationStatus {
|
|
50
|
+
manager.authorizationStatus
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Current accuracy authorization (iOS 14+: fullAccuracy or reducedAccuracy).
|
|
54
|
+
public var accuracyAuthorization: CLAccuracyAuthorization {
|
|
55
|
+
manager.accuracyAuthorization
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Whether the user has granted full accuracy (precise GPS).
|
|
59
|
+
public var isFullAccuracy: Bool {
|
|
60
|
+
accuracyAuthorization == .fullAccuracy
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// One-shot callback fired when authorization status is resolved after a request.
|
|
64
|
+
public var onAuthorizationResolved: ((CLAuthorizationStatus) -> Void)?
|
|
65
|
+
|
|
66
|
+
/// Request "Always" authorization (triggers the system prompt if status is .notDetermined).
|
|
67
|
+
public func requestWhenInUseAuthorization() {
|
|
68
|
+
manager.requestWhenInUseAuthorization()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public func requestAlwaysAuthorization() {
|
|
72
|
+
manager.requestAlwaysAuthorization()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Request temporary full accuracy when the user has granted only approximate location.
|
|
76
|
+
/// The `purposeKey` must match an entry in Info.plist `NSLocationTemporaryUsageDescriptionDictionary`.
|
|
77
|
+
/// Completion returns the resulting accuracy authorization.
|
|
78
|
+
public func requestTemporaryFullAccuracy(
|
|
79
|
+
purposeKey: String, completion: @escaping (CLAccuracyAuthorization) -> Void
|
|
80
|
+
) {
|
|
81
|
+
manager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: purposeKey) {
|
|
82
|
+
[weak self] error in
|
|
83
|
+
if let error = error {
|
|
84
|
+
print(
|
|
85
|
+
"BGLocation: requestTemporaryFullAccuracy error — \(error.localizedDescription)"
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
let result = self?.accuracyAuthorization ?? .reducedAccuracy
|
|
89
|
+
completion(result)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private let manager = CLLocationManager()
|
|
94
|
+
private var previousIsMoving = false
|
|
95
|
+
private var pendingPositionCompletion: ((CLLocation?) -> Void)?
|
|
96
|
+
|
|
97
|
+
/// Current desired accuracy (exposed so callers can reconfigure distanceFilter without changing accuracy).
|
|
98
|
+
public private(set) var currentDesiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest
|
|
99
|
+
|
|
100
|
+
public override init() {
|
|
101
|
+
super.init()
|
|
102
|
+
manager.delegate = self
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// MARK: - Configuration
|
|
106
|
+
|
|
107
|
+
public func configure(distanceFilter: Double, desiredAccuracy: CLLocationAccuracy) {
|
|
108
|
+
currentDesiredAccuracy = desiredAccuracy
|
|
109
|
+
manager.desiredAccuracy = desiredAccuracy
|
|
110
|
+
manager.distanceFilter = distanceFilter
|
|
111
|
+
|
|
112
|
+
// CRITICAL: Must be set before app enters background.
|
|
113
|
+
// Only set when the host app declares "location" in UIBackgroundModes —
|
|
114
|
+
// setting this without the entitlement crashes (NSInternalInconsistencyException).
|
|
115
|
+
if let modes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String],
|
|
116
|
+
modes.contains("location")
|
|
117
|
+
{
|
|
118
|
+
manager.allowsBackgroundLocationUpdates = true
|
|
119
|
+
manager.showsBackgroundLocationIndicator = true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// CRITICAL: false prevents iOS from auto-pausing location when stationary
|
|
123
|
+
manager.pausesLocationUpdatesAutomatically = false
|
|
124
|
+
|
|
125
|
+
// Navigation apps get higher priority for background execution
|
|
126
|
+
manager.activityType = .otherNavigation
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Update only the distance filter without resetting other CLLocationManager properties.
|
|
130
|
+
/// Used by the adaptive distance filter to reconfigure on-the-fly.
|
|
131
|
+
public func updateDistanceFilter(_ distance: Double) {
|
|
132
|
+
manager.distanceFilter = distance
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// MARK: - Tracking
|
|
136
|
+
|
|
137
|
+
public func startTracking() {
|
|
138
|
+
if manager.authorizationStatus == .notDetermined {
|
|
139
|
+
manager.requestWhenInUseAuthorization()
|
|
140
|
+
}
|
|
141
|
+
slcRestartFired = false
|
|
142
|
+
manager.startUpdatingLocation()
|
|
143
|
+
|
|
144
|
+
// C.1: Start Significant Location Change monitoring as a watchdog.
|
|
145
|
+
// SLC survives app termination by iOS. When the app is relaunched via SLC,
|
|
146
|
+
// didUpdateLocations fires → we restart standard location updates automatically.
|
|
147
|
+
manager.startMonitoringSignificantLocationChanges()
|
|
148
|
+
|
|
149
|
+
isTracking = true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public func stopTracking() {
|
|
153
|
+
manager.stopUpdatingLocation()
|
|
154
|
+
manager.stopMonitoringSignificantLocationChanges()
|
|
155
|
+
isTracking = false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Request the current position with a timeout.
|
|
159
|
+
///
|
|
160
|
+
/// Returns the last known location if it's fresh enough (within `timeout` seconds),
|
|
161
|
+
/// otherwise triggers a one-shot `requestLocation()` and waits up to `timeout`
|
|
162
|
+
/// for a fresh fix.
|
|
163
|
+
public func requestCurrentPosition(
|
|
164
|
+
timeout: TimeInterval, completion: @escaping (CLLocation?) -> Void
|
|
165
|
+
) {
|
|
166
|
+
// Return last known location if fresh enough
|
|
167
|
+
if let location = lastKnownLocation,
|
|
168
|
+
Date().timeIntervalSince(location.timestamp) < timeout
|
|
169
|
+
{
|
|
170
|
+
completion(location)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Otherwise try manager's cached location
|
|
175
|
+
if let location = manager.location,
|
|
176
|
+
Date().timeIntervalSince(location.timestamp) < timeout
|
|
177
|
+
{
|
|
178
|
+
completion(location)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Request a fresh fix via one-shot requestLocation()
|
|
183
|
+
pendingPositionCompletion = completion
|
|
184
|
+
manager.requestLocation()
|
|
185
|
+
|
|
186
|
+
// Timeout: if no location arrives within `timeout` seconds, return nil
|
|
187
|
+
let deadline = DispatchTime.now() + timeout
|
|
188
|
+
DispatchQueue.main.asyncAfter(deadline: deadline) { [weak self] in
|
|
189
|
+
guard let self = self, let pending = self.pendingPositionCompletion else { return }
|
|
190
|
+
self.pendingPositionCompletion = nil
|
|
191
|
+
pending(self.lastKnownLocation)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// MARK: - CLLocationManagerDelegate
|
|
196
|
+
|
|
197
|
+
public func locationManager(
|
|
198
|
+
_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]
|
|
199
|
+
) {
|
|
200
|
+
guard let location = locations.last else { return }
|
|
201
|
+
lastKnownLocation = location
|
|
202
|
+
|
|
203
|
+
// C.1: If tracking was active but standard updates were stopped by iOS
|
|
204
|
+
// (e.g., app was suspended/terminated and relaunched via SLC), restart them.
|
|
205
|
+
if isTracking && !slcRestartFired {
|
|
206
|
+
let isSLCAccuracy = location.horizontalAccuracy > Self.slcAccuracyThreshold
|
|
207
|
+
if isSLCAccuracy {
|
|
208
|
+
slcRestartFired = true
|
|
209
|
+
manager.startUpdatingLocation()
|
|
210
|
+
onSignificantLocationChange?()
|
|
211
|
+
print("BGLocation: SLC watchdog fired — restarting standard location updates")
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Resolve pending getCurrentPosition() request
|
|
216
|
+
if let pending = pendingPositionCompletion {
|
|
217
|
+
pendingPositionCompletion = nil
|
|
218
|
+
pending(location)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let isMoving = location.speed > Self.motionSpeedThreshold
|
|
222
|
+
let motionChanged = isMoving != previousIsMoving
|
|
223
|
+
previousIsMoving = isMoving
|
|
224
|
+
|
|
225
|
+
#if DEBUG
|
|
226
|
+
if onLocation == nil {
|
|
227
|
+
print(
|
|
228
|
+
"BGLocation: \u{26A0}\u{FE0F} location update received but no onLocation callback registered"
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
#endif
|
|
232
|
+
onLocation?(location, isMoving)
|
|
233
|
+
|
|
234
|
+
if motionChanged {
|
|
235
|
+
print("BGLocation: motion change — isMoving=\(isMoving)")
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
240
|
+
print("BGLocation: location error — \(error.localizedDescription)")
|
|
241
|
+
|
|
242
|
+
// Resolve pending getCurrentPosition() on error
|
|
243
|
+
if let pending = pendingPositionCompletion {
|
|
244
|
+
pendingPositionCompletion = nil
|
|
245
|
+
pending(lastKnownLocation)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
250
|
+
let status = manager.authorizationStatus
|
|
251
|
+
let accuracy = manager.accuracyAuthorization
|
|
252
|
+
print(
|
|
253
|
+
"BGLocation: authorization changed — status=\(status.rawValue), accuracy=\(accuracy == .fullAccuracy ? "full" : "reduced")"
|
|
254
|
+
)
|
|
255
|
+
#if DEBUG
|
|
256
|
+
if onProviderChange == nil {
|
|
257
|
+
print(
|
|
258
|
+
"BGLocation: \u{26A0}\u{FE0F} authorization changed but no onProviderChange callback registered"
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
#endif
|
|
262
|
+
onProviderChange?(status)
|
|
263
|
+
onAccuracyChange?(accuracy)
|
|
264
|
+
// Resolve pending requestPermissions() call
|
|
265
|
+
onAuthorizationResolved?(status)
|
|
266
|
+
onAuthorizationResolved = nil
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import CoreLocation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Maps CLAuthorizationStatus into JS-friendly permission status dictionaries.
|
|
5
|
+
/// Extracted from BackgroundLocationPlugin to reduce bridge complexity.
|
|
6
|
+
public enum BGLPermissionManager {
|
|
7
|
+
|
|
8
|
+
/// Build permission status dictionary from CLAuthorizationStatus.
|
|
9
|
+
public static func permissionStatusDict(_ status: CLAuthorizationStatus) -> [String: String] {
|
|
10
|
+
let locationState: String
|
|
11
|
+
let bgState: String
|
|
12
|
+
|
|
13
|
+
switch status {
|
|
14
|
+
case .authorizedAlways:
|
|
15
|
+
locationState = "granted"
|
|
16
|
+
bgState = "granted"
|
|
17
|
+
case .authorizedWhenInUse:
|
|
18
|
+
locationState = "granted"
|
|
19
|
+
bgState = "prompt"
|
|
20
|
+
case .denied, .restricted:
|
|
21
|
+
locationState = "denied"
|
|
22
|
+
bgState = "denied"
|
|
23
|
+
case .notDetermined:
|
|
24
|
+
locationState = "prompt"
|
|
25
|
+
bgState = "prompt"
|
|
26
|
+
@unknown default:
|
|
27
|
+
locationState = "prompt"
|
|
28
|
+
bgState = "prompt"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return ["location": locationState, "backgroundLocation": bgState]
|
|
32
|
+
}
|
|
33
|
+
}
|