react-native-nitro-geolocation 1.2.5 → 1.3.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 (223) hide show
  1. package/README.md +55 -278
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/AndroidManifest.xml +39 -0
  4. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidFusedLocationProvider.kt +310 -0
  5. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidFusedRequestFactory.kt +31 -0
  6. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidGeolocationConversions.kt +71 -0
  7. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidGeolocationErrors.kt +85 -0
  8. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidGeolocationOptions.kt +92 -0
  9. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidLocationSettings.kt +3 -0
  10. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidProviderRoute.kt +74 -0
  11. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroBackgroundLocation.kt +132 -0
  12. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocation.kt +219 -476
  13. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/AndroidBackgroundHttpSync.kt +134 -0
  14. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/AndroidBackgroundPermissions.kt +196 -0
  15. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/AndroidBackgroundSerialization.kt +266 -0
  16. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroActivityRecognitionReceiver.kt +13 -0
  17. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundEventHub.kt +65 -0
  18. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundHeadlessTaskService.kt +58 -0
  19. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundLocationController.kt +710 -0
  20. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundLocationService.kt +48 -0
  21. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundNotificationFactory.kt +37 -0
  22. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundStore.kt +655 -0
  23. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBootReceiver.kt +26 -0
  24. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroGeofenceReceiver.kt +14 -0
  25. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroLocationUpdateReceiver.kt +37 -0
  26. package/android/src/test/java/com/margelo/nitro/nitrogeolocation/AndroidFusedLocationProviderTest.kt +64 -0
  27. package/android/src/test/java/com/margelo/nitro/nitrogeolocation/AndroidProviderRouteTest.kt +116 -0
  28. package/ios/IOSBackgroundHttpSync.swift +126 -0
  29. package/ios/IOSBackgroundLocationDelegate.swift +36 -0
  30. package/ios/IOSBackgroundMotion.swift +55 -0
  31. package/ios/IOSBackgroundSerialization.swift +539 -0
  32. package/ios/IOSGeolocationConversions.swift +131 -0
  33. package/ios/IOSGeolocationDelegate.swift +31 -0
  34. package/ios/IOSGeolocationErrors.swift +39 -0
  35. package/ios/IOSGeolocationOptions.swift +135 -0
  36. package/ios/NitroBackgroundLocation.swift +849 -0
  37. package/ios/NitroGeolocation.swift +14 -347
  38. package/nitrogen/generated/android/c++/JActivityRecognitionOptions.hpp +69 -0
  39. package/nitrogen/generated/android/c++/JAndroidBackgroundLocationOptions.hpp +74 -0
  40. package/nitrogen/generated/android/c++/JAndroidBackgroundLocationStatus.hpp +67 -0
  41. package/nitrogen/generated/android/c++/JAndroidBackgroundProvider.hpp +61 -0
  42. package/nitrogen/generated/android/c++/JAndroidForegroundServiceOptions.hpp +90 -0
  43. package/nitrogen/generated/android/c++/JBackgroundEventEnvelope.hpp +129 -0
  44. package/nitrogen/generated/android/c++/JBackgroundEventType.hpp +70 -0
  45. package/nitrogen/generated/android/c++/JBackgroundHttpMethod.hpp +61 -0
  46. package/nitrogen/generated/android/c++/JBackgroundHttpSyncOptions.hpp +131 -0
  47. package/nitrogen/generated/android/c++/JBackgroundHttpSyncResult.hpp +111 -0
  48. package/nitrogen/generated/android/c++/JBackgroundLocation.hpp +110 -0
  49. package/nitrogen/generated/android/c++/JBackgroundLocationOptions.hpp +162 -0
  50. package/nitrogen/generated/android/c++/JBackgroundLocationSource.hpp +73 -0
  51. package/nitrogen/generated/android/c++/JBackgroundLocationState.hpp +70 -0
  52. package/nitrogen/generated/android/c++/JBackgroundLocationStatus.hpp +126 -0
  53. package/nitrogen/generated/android/c++/JBackgroundPermissionResult.hpp +79 -0
  54. package/nitrogen/generated/android/c++/JBackgroundPermissionStatus.hpp +64 -0
  55. package/nitrogen/generated/android/c++/JBackgroundTrackingMode.hpp +61 -0
  56. package/nitrogen/generated/android/c++/JBatterySnapshot.hpp +61 -0
  57. package/nitrogen/generated/android/c++/JDetectedActivity.hpp +66 -0
  58. package/nitrogen/generated/android/c++/JDetectedActivityType.hpp +76 -0
  59. package/nitrogen/generated/android/c++/JFunc_void_BackgroundEventEnvelope.hpp +114 -0
  60. package/nitrogen/generated/android/c++/JFunc_void_BackgroundLocation.hpp +95 -0
  61. package/nitrogen/generated/android/c++/JGeofenceEvent.hpp +94 -0
  62. package/nitrogen/generated/android/c++/JGeofenceRegion.hpp +112 -0
  63. package/nitrogen/generated/android/c++/JGeofenceTransition.hpp +61 -0
  64. package/nitrogen/generated/android/c++/JGeofencingOptions.hpp +82 -0
  65. package/nitrogen/generated/android/c++/JGetStoredBackgroundEventsOptions.hpp +90 -0
  66. package/nitrogen/generated/android/c++/JGetStoredBackgroundLocationsOptions.hpp +69 -0
  67. package/nitrogen/generated/android/c++/JHybridNitroBackgroundLocationSpec.cpp +662 -0
  68. package/nitrogen/generated/android/c++/JHybridNitroBackgroundLocationSpec.hpp +89 -0
  69. package/nitrogen/generated/android/c++/JIOSBackgroundActivityType.hpp +67 -0
  70. package/nitrogen/generated/android/c++/JIOSBackgroundLocationOptions.hpp +79 -0
  71. package/nitrogen/generated/android/c++/JIOSBackgroundLocationStatus.hpp +61 -0
  72. package/nitrogen/generated/android/c++/JLocationProviderStatus.hpp +5 -1
  73. package/nitrogen/generated/android/c++/JStoredBackgroundEventEnvelope.hpp +115 -0
  74. package/nitrogen/generated/android/c++/JStoredBackgroundLocation.hpp +122 -0
  75. package/nitrogen/generated/android/c++/JVariant_NullType_Boolean_String_Double.cpp +34 -0
  76. package/nitrogen/generated/android/c++/JVariant_NullType_Boolean_String_Double.hpp +100 -0
  77. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/ActivityRecognitionOptions.kt +66 -0
  78. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidBackgroundLocationOptions.kt +66 -0
  79. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidBackgroundLocationStatus.kt +61 -0
  80. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidBackgroundProvider.kt +24 -0
  81. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidForegroundServiceOptions.kt +91 -0
  82. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundEventEnvelope.kt +96 -0
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundEventType.kt +27 -0
  84. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundHttpMethod.kt +24 -0
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundHttpSyncOptions.kt +101 -0
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundHttpSyncResult.kt +71 -0
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocation.kt +96 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationOptions.kt +136 -0
  89. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationSource.kt +28 -0
  90. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationState.kt +27 -0
  91. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationStatus.kt +116 -0
  92. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundPermissionResult.kt +71 -0
  93. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundPermissionStatus.kt +25 -0
  94. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundTrackingMode.kt +24 -0
  95. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BatterySnapshot.kt +56 -0
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/DetectedActivity.kt +61 -0
  97. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/DetectedActivityType.kt +29 -0
  98. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_BackgroundEventEnvelope.kt +80 -0
  99. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_BackgroundLocation.kt +80 -0
  100. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofenceEvent.kt +66 -0
  101. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofenceRegion.kt +96 -0
  102. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofenceTransition.kt +24 -0
  103. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofencingOptions.kt +56 -0
  104. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GetStoredBackgroundEventsOptions.kt +66 -0
  105. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GetStoredBackgroundLocationsOptions.kt +66 -0
  106. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroBackgroundLocationSpec.kt +174 -0
  107. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSBackgroundActivityType.kt +26 -0
  108. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSBackgroundLocationOptions.kt +76 -0
  109. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSBackgroundLocationStatus.kt +56 -0
  110. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderStatus.kt +7 -2
  111. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/StoredBackgroundEventEnvelope.kt +76 -0
  112. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/StoredBackgroundLocation.kt +111 -0
  113. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Variant_NullType_Boolean_String_Double.kt +88 -0
  114. package/nitrogen/generated/android/nitrogeolocation+autolinking.cmake +3 -0
  115. package/nitrogen/generated/android/nitrogeolocationOnLoad.cpp +20 -0
  116. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.cpp +104 -15
  117. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.hpp +1107 -50
  118. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Umbrella.hpp +102 -0
  119. package/nitrogen/generated/ios/NitroGeolocationAutolinking.mm +8 -0
  120. package/nitrogen/generated/ios/NitroGeolocationAutolinking.swift +15 -3
  121. package/nitrogen/generated/ios/c++/HybridNitroBackgroundLocationSpecSwift.cpp +11 -0
  122. package/nitrogen/generated/ios/c++/HybridNitroBackgroundLocationSpecSwift.hpp +416 -0
  123. package/nitrogen/generated/ios/swift/ActivityRecognitionOptions.swift +96 -0
  124. package/nitrogen/generated/ios/swift/AndroidBackgroundLocationOptions.swift +76 -0
  125. package/nitrogen/generated/ios/swift/AndroidBackgroundLocationStatus.swift +58 -0
  126. package/nitrogen/generated/ios/swift/AndroidBackgroundProvider.swift +44 -0
  127. package/nitrogen/generated/ios/swift/AndroidForegroundServiceOptions.swift +160 -0
  128. package/nitrogen/generated/ios/swift/BackgroundEventEnvelope.swift +110 -0
  129. package/nitrogen/generated/ios/swift/BackgroundEventType.swift +56 -0
  130. package/nitrogen/generated/ios/swift/BackgroundHttpMethod.swift +44 -0
  131. package/nitrogen/generated/ios/swift/BackgroundHttpSyncOptions.swift +259 -0
  132. package/nitrogen/generated/ios/swift/BackgroundHttpSyncResult.swift +87 -0
  133. package/nitrogen/generated/ios/swift/BackgroundLocation.swift +118 -0
  134. package/nitrogen/generated/ios/swift/BackgroundLocationOptions.swift +292 -0
  135. package/nitrogen/generated/ios/swift/BackgroundLocationSource.swift +60 -0
  136. package/nitrogen/generated/ios/swift/BackgroundLocationState.swift +56 -0
  137. package/nitrogen/generated/ios/swift/BackgroundLocationStatus.swift +124 -0
  138. package/nitrogen/generated/ios/swift/BackgroundPermissionResult.swift +81 -0
  139. package/nitrogen/generated/ios/swift/BackgroundPermissionStatus.swift +48 -0
  140. package/nitrogen/generated/ios/swift/BackgroundTrackingMode.swift +44 -0
  141. package/nitrogen/generated/ios/swift/BatterySnapshot.swift +60 -0
  142. package/nitrogen/generated/ios/swift/DetectedActivity.swift +39 -0
  143. package/nitrogen/generated/ios/swift/DetectedActivityType.swift +64 -0
  144. package/nitrogen/generated/ios/swift/Func_void_BackgroundEventEnvelope.swift +46 -0
  145. package/nitrogen/generated/ios/swift/Func_void_BackgroundHttpSyncResult.swift +46 -0
  146. package/nitrogen/generated/ios/swift/Func_void_BackgroundLocation.swift +46 -0
  147. package/nitrogen/generated/ios/swift/Func_void_BackgroundLocationStatus.swift +46 -0
  148. package/nitrogen/generated/ios/swift/Func_void_BackgroundPermissionResult.swift +46 -0
  149. package/nitrogen/generated/ios/swift/Func_void_std__optional_BackgroundLocationOptions_.swift +46 -0
  150. package/nitrogen/generated/ios/swift/Func_void_std__vector_GeofenceRegion_.swift +46 -0
  151. package/nitrogen/generated/ios/swift/Func_void_std__vector_StoredBackgroundEventEnvelope_.swift +46 -0
  152. package/nitrogen/generated/ios/swift/Func_void_std__vector_StoredBackgroundLocation_.swift +46 -0
  153. package/nitrogen/generated/ios/swift/GeofenceEvent.swift +50 -0
  154. package/nitrogen/generated/ios/swift/GeofenceRegion.swift +195 -0
  155. package/nitrogen/generated/ios/swift/GeofenceTransition.swift +44 -0
  156. package/nitrogen/generated/ios/swift/GeofencingOptions.swift +66 -0
  157. package/nitrogen/generated/ios/swift/GetStoredBackgroundEventsOptions.swift +102 -0
  158. package/nitrogen/generated/ios/swift/GetStoredBackgroundLocationsOptions.swift +96 -0
  159. package/nitrogen/generated/ios/swift/HybridNitroBackgroundLocationSpec.swift +81 -0
  160. package/nitrogen/generated/ios/swift/HybridNitroBackgroundLocationSpec_cxx.swift +654 -0
  161. package/nitrogen/generated/ios/swift/IOSBackgroundActivityType.swift +52 -0
  162. package/nitrogen/generated/ios/swift/IOSBackgroundLocationOptions.swift +125 -0
  163. package/nitrogen/generated/ios/swift/IOSBackgroundLocationStatus.swift +34 -0
  164. package/nitrogen/generated/ios/swift/LocationProviderStatus.swift +19 -1
  165. package/nitrogen/generated/ios/swift/StoredBackgroundEventEnvelope.swift +54 -0
  166. package/nitrogen/generated/ios/swift/StoredBackgroundLocation.swift +120 -0
  167. package/nitrogen/generated/ios/swift/Variant_NullType_Bool_String_Double.swift +34 -0
  168. package/nitrogen/generated/shared/c++/ActivityRecognitionOptions.hpp +95 -0
  169. package/nitrogen/generated/shared/c++/AndroidBackgroundLocationOptions.hpp +100 -0
  170. package/nitrogen/generated/shared/c++/AndroidBackgroundLocationStatus.hpp +93 -0
  171. package/nitrogen/generated/shared/c++/AndroidBackgroundProvider.hpp +80 -0
  172. package/nitrogen/generated/shared/c++/AndroidForegroundServiceOptions.hpp +116 -0
  173. package/nitrogen/generated/shared/c++/BackgroundEventEnvelope.hpp +140 -0
  174. package/nitrogen/generated/shared/c++/BackgroundEventType.hpp +92 -0
  175. package/nitrogen/generated/shared/c++/BackgroundHttpMethod.hpp +80 -0
  176. package/nitrogen/generated/shared/c++/BackgroundHttpSyncOptions.hpp +129 -0
  177. package/nitrogen/generated/shared/c++/BackgroundHttpSyncResult.hpp +101 -0
  178. package/nitrogen/generated/shared/c++/BackgroundLocation.hpp +134 -0
  179. package/nitrogen/generated/shared/c++/BackgroundLocationOptions.hpp +174 -0
  180. package/nitrogen/generated/shared/c++/BackgroundLocationSource.hpp +96 -0
  181. package/nitrogen/generated/shared/c++/BackgroundLocationState.hpp +92 -0
  182. package/nitrogen/generated/shared/c++/BackgroundLocationStatus.hpp +158 -0
  183. package/nitrogen/generated/shared/c++/BackgroundPermissionResult.hpp +107 -0
  184. package/nitrogen/generated/shared/c++/BackgroundPermissionStatus.hpp +84 -0
  185. package/nitrogen/generated/shared/c++/BackgroundTrackingMode.hpp +80 -0
  186. package/nitrogen/generated/shared/c++/BatterySnapshot.hpp +87 -0
  187. package/nitrogen/generated/shared/c++/DetectedActivity.hpp +92 -0
  188. package/nitrogen/generated/shared/c++/DetectedActivityType.hpp +100 -0
  189. package/nitrogen/generated/shared/c++/GeofenceEvent.hpp +103 -0
  190. package/nitrogen/generated/shared/c++/GeofenceRegion.hpp +123 -0
  191. package/nitrogen/generated/shared/c++/GeofenceTransition.hpp +80 -0
  192. package/nitrogen/generated/shared/c++/GeofencingOptions.hpp +90 -0
  193. package/nitrogen/generated/shared/c++/GetStoredBackgroundEventsOptions.hpp +98 -0
  194. package/nitrogen/generated/shared/c++/GetStoredBackgroundLocationsOptions.hpp +95 -0
  195. package/nitrogen/generated/shared/c++/HybridNitroBackgroundLocationSpec.cpp +47 -0
  196. package/nitrogen/generated/shared/c++/HybridNitroBackgroundLocationSpec.hpp +133 -0
  197. package/nitrogen/generated/shared/c++/IOSBackgroundActivityType.hpp +88 -0
  198. package/nitrogen/generated/shared/c++/IOSBackgroundLocationOptions.hpp +105 -0
  199. package/nitrogen/generated/shared/c++/IOSBackgroundLocationStatus.hpp +87 -0
  200. package/nitrogen/generated/shared/c++/LocationProviderStatus.hpp +5 -1
  201. package/nitrogen/generated/shared/c++/StoredBackgroundEventEnvelope.hpp +108 -0
  202. package/nitrogen/generated/shared/c++/StoredBackgroundLocation.hpp +146 -0
  203. package/package.json +17 -6
  204. package/src/NitroGeolocation.nitro.ts +4 -4
  205. package/src/api/getCurrentPosition.ts +1 -1
  206. package/src/api/requestLocationSettings.ts +3 -2
  207. package/src/background/NitroBackgroundLocation.nitro.ts +83 -0
  208. package/src/background/index.ts +200 -0
  209. package/src/background/index.web.ts +120 -0
  210. package/src/background/task.ts +13 -0
  211. package/src/background/types.ts +328 -0
  212. package/src/compat/index.web.ts +141 -0
  213. package/src/index.tsx +3 -0
  214. package/src/index.web.tsx +41 -0
  215. package/src/publicTypes.ts +6 -5
  216. package/src/types.ts +3 -0
  217. package/src/web/browser.ts +171 -0
  218. package/src/web/index.ts +167 -0
  219. package/src/web/useWatchPosition.ts +76 -0
  220. package/src/web/watch.ts +81 -0
  221. package/src/devtools/index.test.ts +0 -54
  222. package/src/utils/errors.test.ts +0 -65
  223. package/src/utils/provider.test.ts +0 -303
@@ -0,0 +1,849 @@
1
+ import CoreLocation
2
+ import CoreMotion
3
+ import Foundation
4
+ import NitroModules
5
+ import UIKit
6
+
7
+ class NitroBackgroundLocation: HybridNitroBackgroundLocationSpec {
8
+ private let defaults = UserDefaults.standard
9
+ private let locationsKey = "nitro.background.locations"
10
+ private let eventsKey = "nitro.background.events"
11
+ private let geofencesKey = "nitro.background.geofences"
12
+ private let optionsKey = "nitro.background.options"
13
+ private var options: BackgroundLocationOptions?
14
+ private var state: BackgroundLocationState = .idle
15
+ private var isRunning = false
16
+ private var eventListeners: [String: (BackgroundEventEnvelope) -> Void] = [:]
17
+ private var locationListeners: [String: (BackgroundLocation) -> Void] = [:]
18
+ private var errorListeners: [String: (LocationError) -> Void] = [:]
19
+ private var storedLocations: [StoredBackgroundLocation] = []
20
+ private var storedEvents: [StoredBackgroundEventEnvelope] = []
21
+ private var geofences: [GeofenceRegion] = []
22
+ private var manager: CLLocationManager?
23
+ private var delegate: NitroBackgroundLocationDelegate?
24
+ private let motionManager = CMMotionActivityManager()
25
+ private let motionQueue = OperationQueue()
26
+ private let syncQueue = DispatchQueue(label: "nitro.background.sync")
27
+ private let httpSync = IOSBackgroundHttpSync()
28
+ private var permissionSemaphore: DispatchSemaphore?
29
+ private var lastSyncAt: TimeInterval = 0
30
+
31
+ override init() {
32
+ super.init()
33
+ loadPersistedStore()
34
+ }
35
+
36
+ func checkBackgroundPermission() throws -> Promise<BackgroundPermissionResult> {
37
+ return Promise.async {
38
+ return self.permissionResult()
39
+ }
40
+ }
41
+
42
+ func requestBackgroundPermission() throws -> Promise<BackgroundPermissionResult> {
43
+ return Promise.async {
44
+ self.ensureManager()
45
+ let status = CLLocationManager.authorizationStatus()
46
+ let shouldWait = status == .notDetermined || status == .authorizedWhenInUse
47
+ let semaphore = shouldWait ? DispatchSemaphore(value: 0) : nil
48
+ self.permissionSemaphore = semaphore
49
+ DispatchQueue.main.async {
50
+ self.manager?.requestAlwaysAuthorization()
51
+ }
52
+ if Thread.isMainThread == false {
53
+ _ = semaphore?.wait(timeout: .now() + 60)
54
+ }
55
+ self.permissionSemaphore = nil
56
+ return self.permissionResult()
57
+ }
58
+ }
59
+
60
+ func openAppLocationSettings() throws -> Promise<Void> {
61
+ return Promise.async {
62
+ if let url = URL(string: UIApplication.openSettingsURLString) {
63
+ DispatchQueue.main.async {
64
+ UIApplication.shared.open(url)
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ func configureBackgroundLocation(options: BackgroundLocationOptions) throws -> Promise<Void> {
71
+ return Promise.async {
72
+ self.options = options
73
+ self.persistOptions(options)
74
+ }
75
+ }
76
+
77
+ func getBackgroundConfiguration() throws -> Promise<BackgroundLocationOptions?> {
78
+ return Promise.async {
79
+ return self.options
80
+ }
81
+ }
82
+
83
+ func startBackgroundLocation(options: BackgroundLocationOptions?) throws -> Promise<Void> {
84
+ return Promise.async {
85
+ if let options {
86
+ self.options = options
87
+ self.persistOptions(options)
88
+ }
89
+ guard let current = self.options else {
90
+ throw RuntimeError.error(withMessage: "Background location is not configured")
91
+ }
92
+ self.ensureManager()
93
+ let permission = self.permissionResult()
94
+ guard permission.background == .granted else {
95
+ self.state = .error
96
+ throw RuntimeError.error(withMessage: "Background location permission is required")
97
+ }
98
+ self.state = .starting
99
+ try self.apply(current)
100
+ if current.trackingMode == .activityaware ||
101
+ current.activityRecognition?.enabled == true {
102
+ self.startMotionUpdatesIfAvailable()
103
+ }
104
+ self.isRunning = true
105
+ self.state = .running
106
+ }
107
+ }
108
+
109
+ func stopBackgroundLocation() throws -> Promise<Void> {
110
+ return Promise.async {
111
+ self.state = .stopping
112
+ self.runOnMainSync {
113
+ self.manager?.disallowDeferredLocationUpdates()
114
+ self.manager?.stopUpdatingLocation()
115
+ self.manager?.stopMonitoringSignificantLocationChanges()
116
+ }
117
+ self.motionManager.stopActivityUpdates()
118
+ self.isRunning = false
119
+ self.state = .stopped
120
+ }
121
+ }
122
+
123
+ func resetBackgroundLocation() throws -> Promise<Void> {
124
+ return Promise.async {
125
+ self.runOnMainSync {
126
+ self.manager?.disallowDeferredLocationUpdates()
127
+ self.manager?.stopUpdatingLocation()
128
+ self.manager?.stopMonitoringSignificantLocationChanges()
129
+ self.manager?.monitoredRegions.forEach { self.manager?.stopMonitoring(for: $0) }
130
+ }
131
+ self.motionManager.stopActivityUpdates()
132
+ self.options = nil
133
+ self.defaults.removeObject(forKey: self.optionsKey)
134
+ self.isRunning = false
135
+ self.state = .idle
136
+ self.storedLocations.removeAll()
137
+ self.storedEvents.removeAll()
138
+ self.geofences.removeAll()
139
+ self.persistStore()
140
+ }
141
+ }
142
+
143
+ func getBackgroundLocationStatus() throws -> Promise<BackgroundLocationStatus> {
144
+ return Promise.async {
145
+ let permission = self.permissionResult()
146
+ return BackgroundLocationStatus(
147
+ state: self.state,
148
+ isRunning: self.isRunning,
149
+ isConfigured: self.options != nil,
150
+ foregroundPermission: permission.foreground,
151
+ backgroundPermission: permission.background,
152
+ accuracyAuthorization: permission.accuracyAuthorization,
153
+ locationServicesEnabled: CLLocationManager.locationServicesEnabled(),
154
+ providerStatus: nil,
155
+ storedLocationCount: Double(self.storedLocations.count),
156
+ storedEventCount: Double(self.storedEvents.count),
157
+ geofenceCount: Double(self.geofences.count),
158
+ android: nil,
159
+ ios: IOSBackgroundLocationStatus(
160
+ allowsBackgroundLocationUpdates: self.manager?.allowsBackgroundLocationUpdates ?? false,
161
+ significantChangesEnabled: self.options?.trackingMode == .significantchanges ||
162
+ self.options?.ios?.useSignificantChanges == true
163
+ ),
164
+ lastError: nil
165
+ )
166
+ }
167
+ }
168
+
169
+ func addBackgroundEventListener(listener: @escaping (BackgroundEventEnvelope) -> Void) throws -> String {
170
+ let token = UUID().uuidString
171
+ eventListeners[token] = listener
172
+ return token
173
+ }
174
+
175
+ func removeBackgroundEventListener(token: String) throws {
176
+ eventListeners.removeValue(forKey: token)
177
+ }
178
+
179
+ func addBackgroundLocationListener(listener: @escaping (BackgroundLocation) -> Void) throws -> String {
180
+ let token = UUID().uuidString
181
+ locationListeners[token] = listener
182
+ return token
183
+ }
184
+
185
+ func removeBackgroundLocationListener(token: String) throws {
186
+ locationListeners.removeValue(forKey: token)
187
+ }
188
+
189
+ func addBackgroundErrorListener(listener: @escaping (LocationError) -> Void) throws -> String {
190
+ let token = UUID().uuidString
191
+ errorListeners[token] = listener
192
+ return token
193
+ }
194
+
195
+ func removeBackgroundErrorListener(token: String) throws {
196
+ errorListeners.removeValue(forKey: token)
197
+ }
198
+
199
+ func getStoredBackgroundLocations(
200
+ options: GetStoredBackgroundLocationsOptions?
201
+ ) throws -> Promise<[StoredBackgroundLocation]> {
202
+ return Promise.async {
203
+ var rows = self.storedLocations
204
+ if options?.includeDelivered != true {
205
+ rows = rows.filter { !$0.deliveredToJS }
206
+ }
207
+ if options?.includeSynced != true {
208
+ rows = rows.filter { !$0.synced }
209
+ }
210
+ if let since = options?.since {
211
+ rows = rows.filter { $0.createdAt >= since }
212
+ }
213
+ let limit = self.safePrefixCount(
214
+ options?.limit,
215
+ defaultValue: 100,
216
+ upperBound: rows.count
217
+ )
218
+ return Array(rows.prefix(limit))
219
+ }
220
+ }
221
+
222
+ func clearStoredBackgroundLocations(ids: [String]?) throws -> Promise<Void> {
223
+ return Promise.async {
224
+ guard let ids else {
225
+ self.storedLocations.removeAll()
226
+ self.persistStore()
227
+ return
228
+ }
229
+ self.storedLocations.removeAll { ids.contains($0.id) }
230
+ self.persistStore()
231
+ }
232
+ }
233
+
234
+ func markStoredBackgroundLocationsDelivered(ids: [String]) throws -> Promise<Void> {
235
+ return Promise.async {
236
+ self.storedLocations = self.storedLocations.map { location in
237
+ ids.contains(location.id)
238
+ ? StoredBackgroundLocation(
239
+ id: location.id,
240
+ deliveredToJS: true,
241
+ synced: location.synced,
242
+ createdAt: location.createdAt,
243
+ source: location.source,
244
+ isFromBackground: location.isFromBackground,
245
+ provider: location.provider,
246
+ mocked: location.mocked,
247
+ recordedAt: location.recordedAt,
248
+ activity: location.activity,
249
+ battery: location.battery,
250
+ coords: location.coords,
251
+ timestamp: location.timestamp
252
+ )
253
+ : location
254
+ }
255
+ self.persistStore()
256
+ }
257
+ }
258
+
259
+ func getStoredBackgroundEvents(
260
+ options: GetStoredBackgroundEventsOptions?
261
+ ) throws -> Promise<[StoredBackgroundEventEnvelope]> {
262
+ return Promise.async {
263
+ var rows = self.storedEvents
264
+ if options?.includeDelivered != true {
265
+ rows = rows.filter { !$0.deliveredToJS }
266
+ }
267
+ if let since = options?.since {
268
+ rows = rows.filter { $0.createdAt >= since }
269
+ }
270
+ if let types = options?.types, !types.isEmpty {
271
+ rows = rows.filter { types.contains($0.type) }
272
+ }
273
+ let limit = self.safePrefixCount(
274
+ options?.limit,
275
+ defaultValue: 100,
276
+ upperBound: rows.count
277
+ )
278
+ return Array(rows.prefix(limit))
279
+ }
280
+ }
281
+
282
+ func clearStoredBackgroundEvents(ids: [String]?) throws -> Promise<Void> {
283
+ return Promise.async {
284
+ guard let ids else {
285
+ self.storedEvents.removeAll()
286
+ self.persistStore()
287
+ return
288
+ }
289
+ self.storedEvents.removeAll { ids.contains($0.id) }
290
+ self.persistStore()
291
+ }
292
+ }
293
+
294
+ func markStoredBackgroundEventsDelivered(ids: [String]) throws -> Promise<Void> {
295
+ return Promise.async {
296
+ self.storedEvents = self.storedEvents.map { event in
297
+ ids.contains(event.id)
298
+ ? StoredBackgroundEventEnvelope(
299
+ event: event.event,
300
+ createdAt: event.createdAt,
301
+ id: event.id,
302
+ type: event.type,
303
+ timestamp: event.timestamp,
304
+ deliveredToJS: true
305
+ )
306
+ : event
307
+ }
308
+ self.persistStore()
309
+ }
310
+ }
311
+
312
+ func addGeofences(regions: [GeofenceRegion], options: GeofencingOptions?) throws -> Promise<Void> {
313
+ return Promise.async {
314
+ self.ensureManager()
315
+ guard self.permissionResult().background == .granted else {
316
+ throw RuntimeError.error(withMessage: "Background location permission is required to register geofences")
317
+ }
318
+ let sanitized = regions.map(sanitizedGeofence)
319
+ self.runOnMainSync {
320
+ for region in sanitized {
321
+ let circular = CLCircularRegion(
322
+ center: CLLocationCoordinate2D(
323
+ latitude: region.latitude,
324
+ longitude: region.longitude
325
+ ),
326
+ radius: region.radius,
327
+ identifier: region.identifier
328
+ )
329
+ circular.notifyOnEntry = region.notifyOnEntry ?? true
330
+ circular.notifyOnExit = region.notifyOnExit ?? true
331
+ self.manager?.startMonitoring(for: circular)
332
+ }
333
+ }
334
+ self.geofences.removeAll { existing in
335
+ sanitized.contains { $0.identifier == existing.identifier }
336
+ }
337
+ self.geofences.append(contentsOf: sanitized)
338
+ self.persistStore()
339
+ }
340
+ }
341
+
342
+ func removeGeofences(identifiers: [String]?) throws -> Promise<Void> {
343
+ return Promise.async {
344
+ guard let identifiers else {
345
+ self.runOnMainSync {
346
+ self.manager?.monitoredRegions.forEach { self.manager?.stopMonitoring(for: $0) }
347
+ }
348
+ self.geofences.removeAll()
349
+ self.persistStore()
350
+ return
351
+ }
352
+ self.runOnMainSync {
353
+ self.manager?.monitoredRegions
354
+ .filter { identifiers.contains($0.identifier) }
355
+ .forEach { self.manager?.stopMonitoring(for: $0) }
356
+ }
357
+ self.geofences.removeAll { identifiers.contains($0.identifier) }
358
+ self.persistStore()
359
+ }
360
+ }
361
+
362
+ func getRegisteredGeofences() throws -> Promise<[GeofenceRegion]> {
363
+ return Promise.async {
364
+ return self.geofences.map(bridgeSafeGeofence)
365
+ }
366
+ }
367
+
368
+ func startActivityRecognition(options: ActivityRecognitionOptions?) throws -> Promise<Void> {
369
+ return Promise.async {
370
+ guard CMMotionActivityManager.isActivityAvailable() else {
371
+ throw RuntimeError.error(withMessage: "Core Motion activity recognition is not available")
372
+ }
373
+ self.startMotionUpdatesIfAvailable()
374
+ }
375
+ }
376
+
377
+ func stopActivityRecognition() throws -> Promise<Void> {
378
+ return Promise.async {
379
+ self.motionManager.stopActivityUpdates()
380
+ }
381
+ }
382
+
383
+ func syncStoredLocations() throws -> Promise<BackgroundHttpSyncResult> {
384
+ return Promise.async {
385
+ return self.performSyncStoredLocations()
386
+ }
387
+ }
388
+
389
+ func handleLocations(_ locations: [CLLocation]) {
390
+ for location in locations {
391
+ let id = UUID().uuidString
392
+ let coords = GeolocationCoordinates(
393
+ latitude: location.coordinate.latitude,
394
+ longitude: location.coordinate.longitude,
395
+ altitude: .second(location.altitude),
396
+ accuracy: location.horizontalAccuracy,
397
+ altitudeAccuracy: .second(location.verticalAccuracy),
398
+ heading: location.course >= 0 ? .second(location.course) : nil,
399
+ speed: location.speed >= 0 ? .second(location.speed) : nil
400
+ )
401
+ let backgroundLocation = BackgroundLocation(
402
+ id: id,
403
+ source: .background,
404
+ isFromBackground: true,
405
+ provider: .unknown,
406
+ mocked: location.sourceInformation?.isSimulatedBySoftware,
407
+ recordedAt: Date().timeIntervalSince1970 * 1000,
408
+ activity: nil,
409
+ battery: nil,
410
+ coords: coords,
411
+ timestamp: location.timestamp.timeIntervalSince1970 * 1000
412
+ )
413
+ let stored = StoredBackgroundLocation(
414
+ id: id,
415
+ deliveredToJS: false,
416
+ synced: false,
417
+ createdAt: Date().timeIntervalSince1970 * 1000,
418
+ source: backgroundLocation.source,
419
+ isFromBackground: true,
420
+ provider: backgroundLocation.provider,
421
+ mocked: backgroundLocation.mocked,
422
+ recordedAt: backgroundLocation.recordedAt,
423
+ activity: nil,
424
+ battery: nil,
425
+ coords: coords,
426
+ timestamp: backgroundLocation.timestamp
427
+ )
428
+ let event = BackgroundEventEnvelope(
429
+ location: backgroundLocation,
430
+ geofence: nil,
431
+ activity: nil,
432
+ providerStatus: nil,
433
+ result: nil,
434
+ error: nil,
435
+ id: UUID().uuidString,
436
+ type: .location,
437
+ timestamp: Date().timeIntervalSince1970 * 1000,
438
+ deliveredToJS: false
439
+ )
440
+ appendStoredLocation(stored)
441
+ appendStoredEvent(
442
+ StoredBackgroundEventEnvelope(
443
+ event: event,
444
+ createdAt: Date().timeIntervalSince1970 * 1000,
445
+ id: event.id,
446
+ type: event.type,
447
+ timestamp: event.timestamp,
448
+ deliveredToJS: false
449
+ )
450
+ )
451
+ persistStore()
452
+ eventListeners.values.forEach { $0(event) }
453
+ locationListeners.values.forEach { $0(backgroundLocation) }
454
+ scheduleSyncIfNeeded()
455
+ }
456
+ }
457
+
458
+ func handleRegion(_ region: CLRegion, transition: GeofenceTransition) {
459
+ guard let geofence = geofences.first(where: { $0.identifier == region.identifier }) else {
460
+ return
461
+ }
462
+ let event = BackgroundEventEnvelope(
463
+ location: nil,
464
+ geofence: GeofenceEvent(
465
+ region: geofence,
466
+ transition: transition,
467
+ location: nil,
468
+ timestamp: Date().timeIntervalSince1970 * 1000
469
+ ),
470
+ activity: nil,
471
+ providerStatus: nil,
472
+ result: nil,
473
+ error: nil,
474
+ id: UUID().uuidString,
475
+ type: .geofence,
476
+ timestamp: Date().timeIntervalSince1970 * 1000,
477
+ deliveredToJS: false
478
+ )
479
+ appendStoredEvent(
480
+ StoredBackgroundEventEnvelope(
481
+ event: event,
482
+ createdAt: Date().timeIntervalSince1970 * 1000,
483
+ id: event.id,
484
+ type: event.type,
485
+ timestamp: event.timestamp,
486
+ deliveredToJS: false
487
+ )
488
+ )
489
+ persistStore()
490
+ eventListeners.values.forEach { $0(event) }
491
+ }
492
+
493
+ private func startMotionUpdatesIfAvailable() {
494
+ guard CMMotionActivityManager.isActivityAvailable() else { return }
495
+ motionManager.startActivityUpdates(to: motionQueue) { [weak self] activity in
496
+ guard let self, let activity else { return }
497
+ self.handleMotionActivity(activity)
498
+ }
499
+ }
500
+
501
+ private func handleMotionActivity(_ activity: CMMotionActivity) {
502
+ let detected = DetectedActivity(
503
+ type: motionActivityType(activity),
504
+ confidence: motionConfidence(activity.confidence),
505
+ timestamp: activity.startDate.timeIntervalSince1970 * 1000
506
+ )
507
+ let event = BackgroundEventEnvelope(
508
+ location: nil,
509
+ geofence: nil,
510
+ activity: detected,
511
+ providerStatus: nil,
512
+ result: nil,
513
+ error: nil,
514
+ id: UUID().uuidString,
515
+ type: .activity,
516
+ timestamp: Date().timeIntervalSince1970 * 1000,
517
+ deliveredToJS: false
518
+ )
519
+ appendStoredEvent(
520
+ StoredBackgroundEventEnvelope(
521
+ event: event,
522
+ createdAt: Date().timeIntervalSince1970 * 1000,
523
+ id: event.id,
524
+ type: event.type,
525
+ timestamp: event.timestamp,
526
+ deliveredToJS: false
527
+ )
528
+ )
529
+ persistStore()
530
+ eventListeners.values.forEach { $0(event) }
531
+ applyActivityAwareTracking(detected)
532
+ }
533
+
534
+ func handleAuthorizationChange() {
535
+ permissionSemaphore?.signal()
536
+ }
537
+
538
+ func handleError(_ error: Error) {
539
+ let locationError = LocationError(code: -1, message: error.localizedDescription)
540
+ errorListeners.values.forEach { $0(locationError) }
541
+ }
542
+
543
+ private func ensureManager() {
544
+ if manager != nil { return }
545
+ if Thread.isMainThread == false {
546
+ DispatchQueue.main.sync {
547
+ self.ensureManager()
548
+ }
549
+ return
550
+ }
551
+ let manager = CLLocationManager()
552
+ let delegate = NitroBackgroundLocationDelegate(owner: self)
553
+ manager.delegate = delegate
554
+ self.manager = manager
555
+ self.delegate = delegate
556
+ }
557
+
558
+ private func apply(_ options: BackgroundLocationOptions) throws {
559
+ guard hasBackgroundLocationMode() else {
560
+ state = .error
561
+ throw RuntimeError.error(
562
+ withMessage: "UIBackgroundModes must include location for iOS background location"
563
+ )
564
+ }
565
+ runOnMainSync {
566
+ guard let manager = self.manager else { return }
567
+ manager.allowsBackgroundLocationUpdates = true
568
+ manager.pausesLocationUpdatesAutomatically =
569
+ options.ios?.pausesLocationUpdatesAutomatically ?? false
570
+ if #available(iOS 11.0, *) {
571
+ manager.showsBackgroundLocationIndicator =
572
+ options.ios?.showsBackgroundLocationIndicator ?? false
573
+ }
574
+ manager.desiredAccuracy = kCLLocationAccuracyBest
575
+ manager.distanceFilter = options.distanceFilter ?? kCLDistanceFilterNone
576
+ manager.activityType = mapActivityType(options.ios?.activityType)
577
+ if options.trackingMode == .significantchanges ||
578
+ options.ios?.useSignificantChanges == true {
579
+ manager.startMonitoringSignificantLocationChanges()
580
+ } else {
581
+ manager.startUpdatingLocation()
582
+ }
583
+ }
584
+ }
585
+
586
+ func applyDeferredUpdatesIfNeeded(_ manager: CLLocationManager) {
587
+ guard
588
+ let options,
589
+ let distance = options.ios?.deferredUpdatesDistance,
590
+ let interval = options.ios?.deferredUpdatesInterval
591
+ else {
592
+ return
593
+ }
594
+ manager.allowDeferredLocationUpdates(
595
+ untilTraveled: distance,
596
+ timeout: interval / 1000
597
+ )
598
+ }
599
+
600
+ private func applyActivityAwareTracking(_ activity: DetectedActivity) {
601
+ guard let options else { return }
602
+ let activityOptions = options.activityRecognition
603
+ guard options.trackingMode == .activityaware ||
604
+ activityOptions?.stopOnStill == true else {
605
+ return
606
+ }
607
+ let minimumConfidence = activityOptions?.minimumConfidence ?? 0
608
+ guard activity.confidence >= minimumConfidence else { return }
609
+ let stopOnStill = activityOptions?.stopOnStill ?? (options.trackingMode == .activityaware)
610
+ if activity.type == .still && stopOnStill {
611
+ runOnMainSync {
612
+ self.manager?.disallowDeferredLocationUpdates()
613
+ self.manager?.stopUpdatingLocation()
614
+ self.manager?.stopMonitoringSignificantLocationChanges()
615
+ }
616
+ return
617
+ }
618
+ if activity.type != .still && activity.type != .unknown && isRunning {
619
+ runOnMainSync {
620
+ if options.trackingMode == .significantchanges ||
621
+ options.ios?.useSignificantChanges == true {
622
+ self.manager?.startMonitoringSignificantLocationChanges()
623
+ } else {
624
+ self.manager?.startUpdatingLocation()
625
+ }
626
+ }
627
+ }
628
+ }
629
+
630
+ private func runOnMainSync(_ work: @escaping () -> Void) {
631
+ if Thread.isMainThread {
632
+ work()
633
+ } else {
634
+ DispatchQueue.main.sync(execute: work)
635
+ }
636
+ }
637
+
638
+ private func hasBackgroundLocationMode() -> Bool {
639
+ guard let modes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String] else {
640
+ return false
641
+ }
642
+ return modes.contains("location")
643
+ }
644
+
645
+ private func loadPersistedStore() {
646
+ storedLocations = (defaults.array(forKey: locationsKey) as? [[String: Any]] ?? [])
647
+ .compactMap(makeStoredLocation)
648
+ geofences = (defaults.array(forKey: geofencesKey) as? [[String: Any]] ?? [])
649
+ .compactMap(makeGeofenceRegion)
650
+ storedEvents = (defaults.array(forKey: eventsKey) as? [[String: Any]] ?? [])
651
+ .compactMap { makeStoredEvent($0, storedLocations: storedLocations) }
652
+ options = defaults.dictionary(forKey: optionsKey).flatMap(makeBackgroundOptions)
653
+ }
654
+
655
+ private func persistStore() {
656
+ defaults.set(storedLocations.map(storedLocationDictionary), forKey: locationsKey)
657
+ defaults.set(storedEvents.map(storedEventDictionary), forKey: eventsKey)
658
+ defaults.set(geofences.map(geofenceDictionary), forKey: geofencesKey)
659
+ }
660
+
661
+ private func shouldPersist() -> Bool {
662
+ return options?.persist != false
663
+ }
664
+
665
+ private func safePrefixCount(
666
+ _ value: Double?,
667
+ defaultValue: Int,
668
+ upperBound: Int
669
+ ) -> Int {
670
+ let requested = value ?? Double(defaultValue)
671
+ guard requested.isFinite, requested > 0 else { return 0 }
672
+ return Int(min(requested.rounded(.down), Double(upperBound)))
673
+ }
674
+
675
+ private func positiveFiniteInt(_ value: Double?, defaultValue: Int) -> Int {
676
+ guard let value, value.isFinite, value > 0 else {
677
+ return defaultValue
678
+ }
679
+ if value >= Double(Int.max) {
680
+ return Int.max
681
+ }
682
+ return max(Int(value.rounded(.down)), 1)
683
+ }
684
+
685
+ private func appendStoredLocation(_ location: StoredBackgroundLocation) {
686
+ guard shouldPersist() else { return }
687
+ storedLocations.append(location)
688
+ if let maxValue = options?.maxStoredLocations, maxValue > 0 {
689
+ let max = Int(maxValue)
690
+ if storedLocations.count > max {
691
+ storedLocations = Array(storedLocations.suffix(max))
692
+ }
693
+ }
694
+ }
695
+
696
+ private func appendStoredEvent(_ event: StoredBackgroundEventEnvelope) {
697
+ guard shouldPersist() else { return }
698
+ storedEvents.append(event)
699
+ if let maxValue = options?.maxStoredEvents, maxValue > 0 {
700
+ let max = Int(maxValue)
701
+ if storedEvents.count > max {
702
+ storedEvents = Array(storedEvents.suffix(max))
703
+ }
704
+ }
705
+ }
706
+
707
+ private func persistOptions(_ options: BackgroundLocationOptions) {
708
+ defaults.set(backgroundOptionsDictionary(options), forKey: optionsKey)
709
+ }
710
+
711
+ private func scheduleSyncIfNeeded() {
712
+ guard let sync = options?.sync else { return }
713
+ let threshold = positiveFiniteInt(sync.syncThreshold, defaultValue: 1)
714
+ let unsyncedCount = storedLocations.filter { !$0.synced }.count
715
+ guard unsyncedCount >= threshold else { return }
716
+
717
+ let now = Date().timeIntervalSince1970 * 1000
718
+ let interval = sync.syncInterval ?? 0
719
+ guard interval <= 0 || now - lastSyncAt >= interval else { return }
720
+ lastSyncAt = now
721
+
722
+ syncQueue.async {
723
+ let result = self.performSyncStoredLocations()
724
+ let event = BackgroundEventEnvelope(
725
+ location: nil,
726
+ geofence: nil,
727
+ activity: nil,
728
+ providerStatus: nil,
729
+ result: result,
730
+ error: nil,
731
+ id: UUID().uuidString,
732
+ type: .httpsync,
733
+ timestamp: Date().timeIntervalSince1970 * 1000,
734
+ deliveredToJS: false
735
+ )
736
+ self.appendStoredEvent(
737
+ StoredBackgroundEventEnvelope(
738
+ event: event,
739
+ createdAt: Date().timeIntervalSince1970 * 1000,
740
+ id: event.id,
741
+ type: event.type,
742
+ timestamp: event.timestamp,
743
+ deliveredToJS: false
744
+ )
745
+ )
746
+ self.persistStore()
747
+ self.eventListeners.values.forEach { $0(event) }
748
+ }
749
+ }
750
+
751
+ private func performSyncStoredLocations() -> BackgroundHttpSyncResult {
752
+ let allUnsynced = storedLocations.filter { !$0.synced }
753
+ let unsynced = allUnsynced.prefix(safePrefixCount(
754
+ options?.sync?.batchSize,
755
+ defaultValue: 50,
756
+ upperBound: allUnsynced.count
757
+ ))
758
+ let ids = unsynced.map(\.id)
759
+ if ids.isEmpty {
760
+ return BackgroundHttpSyncResult(
761
+ success: true,
762
+ statusCode: nil,
763
+ syncedLocationIds: [],
764
+ failedLocationIds: [],
765
+ error: nil
766
+ )
767
+ }
768
+ guard let sync = options?.sync else {
769
+ return BackgroundHttpSyncResult(
770
+ success: true,
771
+ statusCode: nil,
772
+ syncedLocationIds: [],
773
+ failedLocationIds: [],
774
+ error: nil
775
+ )
776
+ }
777
+ let result = httpSync.uploadWithRetry(locations: Array(unsynced), sync: sync)
778
+ if !result.success && result.syncedLocationIds.isEmpty {
779
+ return result
780
+ }
781
+ let syncedIds = result.syncedLocationIds
782
+ storedLocations = storedLocations.map { location in
783
+ syncedIds.contains(location.id)
784
+ ? StoredBackgroundLocation(
785
+ id: location.id,
786
+ deliveredToJS: location.deliveredToJS,
787
+ synced: true,
788
+ createdAt: location.createdAt,
789
+ source: location.source,
790
+ isFromBackground: location.isFromBackground,
791
+ provider: location.provider,
792
+ mocked: location.mocked,
793
+ recordedAt: location.recordedAt,
794
+ activity: location.activity,
795
+ battery: location.battery,
796
+ coords: location.coords,
797
+ timestamp: location.timestamp
798
+ )
799
+ : location
800
+ }
801
+ if options?.sync?.autoClear == true {
802
+ storedLocations.removeAll { syncedIds.contains($0.id) }
803
+ }
804
+ persistStore()
805
+ return result
806
+ }
807
+
808
+ private func permissionResult() -> BackgroundPermissionResult {
809
+ ensureManager()
810
+ let status = CLLocationManager.authorizationStatus()
811
+ let foreground: PermissionStatus
812
+ let background: BackgroundPermissionStatus
813
+ switch status {
814
+ case .authorizedAlways:
815
+ foreground = .granted
816
+ background = .granted
817
+ case .authorizedWhenInUse:
818
+ foreground = .granted
819
+ background = .denied
820
+ case .denied:
821
+ foreground = .denied
822
+ background = .denied
823
+ case .restricted:
824
+ foreground = .restricted
825
+ background = .restricted
826
+ case .notDetermined:
827
+ foreground = .undetermined
828
+ background = .undetermined
829
+ @unknown default:
830
+ foreground = .undetermined
831
+ background = .undetermined
832
+ }
833
+
834
+ let accuracy: AccuracyAuthorization
835
+ if #available(iOS 14.0, *) {
836
+ accuracy = manager?.accuracyAuthorization == .fullAccuracy ? .full : .reduced
837
+ } else {
838
+ accuracy = .unknown
839
+ }
840
+
841
+ return BackgroundPermissionResult(
842
+ foreground: foreground,
843
+ background: background,
844
+ accuracyAuthorization: accuracy,
845
+ canRequestBackgroundInline: true,
846
+ needsSettingsRedirect: background != .granted
847
+ )
848
+ }
849
+ }