@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.
Files changed (61) hide show
  1. package/CapacitorBackgroundLocation.podspec +19 -0
  2. package/LICENSE.md +97 -0
  3. package/Package.swift +44 -0
  4. package/README.md +264 -0
  5. package/android/build.gradle +74 -0
  6. package/android/src/main/AndroidManifest.xml +37 -0
  7. package/android/src/main/kotlin/dev/bglocation/BackgroundLocationPlugin.kt +684 -0
  8. package/android/src/main/kotlin/dev/bglocation/core/Models.kt +76 -0
  9. package/android/src/main/kotlin/dev/bglocation/core/battery/BGLBatteryHelper.kt +127 -0
  10. package/android/src/main/kotlin/dev/bglocation/core/boot/BGLBootCompletedReceiver.kt +32 -0
  11. package/android/src/main/kotlin/dev/bglocation/core/config/BGLConfigParser.kt +114 -0
  12. package/android/src/main/kotlin/dev/bglocation/core/config/BGLVersion.kt +6 -0
  13. package/android/src/main/kotlin/dev/bglocation/core/debug/BGLDebugLogger.kt +174 -0
  14. package/android/src/main/kotlin/dev/bglocation/core/geofence/BGLGeofenceBroadcastReceiver.kt +93 -0
  15. package/android/src/main/kotlin/dev/bglocation/core/geofence/BGLGeofenceManager.kt +310 -0
  16. package/android/src/main/kotlin/dev/bglocation/core/http/BGLHttpSender.kt +187 -0
  17. package/android/src/main/kotlin/dev/bglocation/core/http/BGLLocationBuffer.kt +152 -0
  18. package/android/src/main/kotlin/dev/bglocation/core/license/BGLBuildConfig.kt +16 -0
  19. package/android/src/main/kotlin/dev/bglocation/core/license/BGLLicenseEnforcer.kt +137 -0
  20. package/android/src/main/kotlin/dev/bglocation/core/license/BGLLicenseValidator.kt +134 -0
  21. package/android/src/main/kotlin/dev/bglocation/core/license/BGLTrialTimer.kt +176 -0
  22. package/android/src/main/kotlin/dev/bglocation/core/location/BGLAdaptiveFilter.kt +94 -0
  23. package/android/src/main/kotlin/dev/bglocation/core/location/BGLHeartbeatTimer.kt +38 -0
  24. package/android/src/main/kotlin/dev/bglocation/core/location/BGLLocationForegroundService.kt +289 -0
  25. package/android/src/main/kotlin/dev/bglocation/core/location/BGLLocationHelpers.kt +72 -0
  26. package/android/src/main/kotlin/dev/bglocation/core/location/BGLPermissionManager.kt +99 -0
  27. package/android/src/main/kotlin/dev/bglocation/core/notification/BGLNotificationHelper.kt +77 -0
  28. package/dist/esm/definitions.d.ts +390 -0
  29. package/dist/esm/definitions.js +3 -0
  30. package/dist/esm/definitions.js.map +1 -0
  31. package/dist/esm/index.d.ts +4 -0
  32. package/dist/esm/index.js +26 -0
  33. package/dist/esm/index.js.map +1 -0
  34. package/dist/esm/web.d.ts +47 -0
  35. package/dist/esm/web.js +231 -0
  36. package/dist/esm/web.js.map +1 -0
  37. package/dist/esm/web.test.d.ts +1 -0
  38. package/dist/esm/web.test.js +940 -0
  39. package/dist/esm/web.test.js.map +1 -0
  40. package/dist/plugin.cjs.js +267 -0
  41. package/dist/plugin.cjs.js.map +1 -0
  42. package/dist/plugin.js +270 -0
  43. package/dist/plugin.js.map +1 -0
  44. package/ios/Sources/BGLocationCore/Config/BGLConfigParser.swift +88 -0
  45. package/ios/Sources/BGLocationCore/Config/BGLVersion.swift +6 -0
  46. package/ios/Sources/BGLocationCore/Debug/BGLDebugLogger.swift +201 -0
  47. package/ios/Sources/BGLocationCore/Geofence/BGLGeofenceManager.swift +538 -0
  48. package/ios/Sources/BGLocationCore/Http/BGLHttpSender.swift +227 -0
  49. package/ios/Sources/BGLocationCore/Http/BGLLocationBuffer.swift +198 -0
  50. package/ios/Sources/BGLocationCore/License/BGLBuildConfig.swift +11 -0
  51. package/ios/Sources/BGLocationCore/License/BGLLicenseEnforcer.swift +134 -0
  52. package/ios/Sources/BGLocationCore/License/BGLLicenseValidator.swift +163 -0
  53. package/ios/Sources/BGLocationCore/License/BGLTrialTimer.swift +168 -0
  54. package/ios/Sources/BGLocationCore/Location/BGLAdaptiveFilter.swift +91 -0
  55. package/ios/Sources/BGLocationCore/Location/BGLHeartbeatTimer.swift +50 -0
  56. package/ios/Sources/BGLocationCore/Location/BGLLocationData.swift +48 -0
  57. package/ios/Sources/BGLocationCore/Location/BGLLocationHelpers.swift +42 -0
  58. package/ios/Sources/BGLocationCore/Location/BGLLocationManager.swift +268 -0
  59. package/ios/Sources/BGLocationCore/Location/BGLPermissionManager.swift +33 -0
  60. package/ios/Sources/BackgroundLocationPlugin/BackgroundLocationPlugin.swift +657 -0
  61. 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
+ }