react-native-bg-geolocation 0.2.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 (142) hide show
  1. package/BgGeolocation.podspec +39 -0
  2. package/LICENSE +20 -0
  3. package/README.md +366 -0
  4. package/android/build.gradle +69 -0
  5. package/android/src/main/AndroidManifest.xml +53 -0
  6. package/android/src/main/java/com/bggeolocation/BgGeolocationActivityRecognitionReceiver.kt +116 -0
  7. package/android/src/main/java/com/bggeolocation/BgGeolocationBootReceiver.kt +44 -0
  8. package/android/src/main/java/com/bggeolocation/BgGeolocationForegroundService.kt +373 -0
  9. package/android/src/main/java/com/bggeolocation/BgGeolocationGeofenceReceiver.kt +55 -0
  10. package/android/src/main/java/com/bggeolocation/BgGeolocationHeadlessTask.kt +138 -0
  11. package/android/src/main/java/com/bggeolocation/BgGeolocationModule.kt +1030 -0
  12. package/android/src/main/java/com/bggeolocation/BgGeolocationMotionStateMachine.kt +159 -0
  13. package/android/src/main/java/com/bggeolocation/BgGeolocationPackage.kt +31 -0
  14. package/android/src/main/res/drawable/bg_geo_notification.xml +9 -0
  15. package/ios/BgGeolocation.h +14 -0
  16. package/ios/BgGeolocation.mm +709 -0
  17. package/ios/engine/AtomicBoolean.swift +48 -0
  18. package/ios/engine/BGActivityChangeEvent.swift +20 -0
  19. package/ios/engine/BGActivityConfig.swift +71 -0
  20. package/ios/engine/BGAppConfig.swift +92 -0
  21. package/ios/engine/BGAppState.swift +147 -0
  22. package/ios/engine/BGAuthorization.swift +85 -0
  23. package/ios/engine/BGAuthorizationAlertPresenter.swift +39 -0
  24. package/ios/engine/BGAuthorizationConfig.swift +50 -0
  25. package/ios/engine/BGAuthorizationEvent.swift +40 -0
  26. package/ios/engine/BGBackgroundTaskManager.swift +143 -0
  27. package/ios/engine/BGCLRouter.swift +101 -0
  28. package/ios/engine/BGCallback.swift +19 -0
  29. package/ios/engine/BGConfig.swift +440 -0
  30. package/ios/engine/BGConfigModuleBase.swift +180 -0
  31. package/ios/engine/BGConfigOLD.swift +582 -0
  32. package/ios/engine/BGConnectivityChangeEvent.swift +15 -0
  33. package/ios/engine/BGCrashDetector.swift +122 -0
  34. package/ios/engine/BGCurrentPositionRequest.swift +87 -0
  35. package/ios/engine/BGDataStore.swift +75 -0
  36. package/ios/engine/BGDatabase.swift +677 -0
  37. package/ios/engine/BGDatabasePool.swift +220 -0
  38. package/ios/engine/BGDatabaseQueue.swift +215 -0
  39. package/ios/engine/BGDateUtils.swift +26 -0
  40. package/ios/engine/BGDeviceInfo.swift +54 -0
  41. package/ios/engine/BGDeviceManager.swift +65 -0
  42. package/ios/engine/BGEnabledChangeEvent.swift +11 -0
  43. package/ios/engine/BGEnv.swift +17 -0
  44. package/ios/engine/BGEventBus.swift +83 -0
  45. package/ios/engine/BGEventManager.swift +169 -0
  46. package/ios/engine/BGEventNames.swift +51 -0
  47. package/ios/engine/BGGeofence.swift +233 -0
  48. package/ios/engine/BGGeofenceDAO.swift +152 -0
  49. package/ios/engine/BGGeofenceEvent.swift +42 -0
  50. package/ios/engine/BGGeofenceLocationRequest.swift +94 -0
  51. package/ios/engine/BGGeofenceManager.swift +315 -0
  52. package/ios/engine/BGGeofenceTransition.swift +97 -0
  53. package/ios/engine/BGGeofencesChangeEvent.swift +26 -0
  54. package/ios/engine/BGGeolocationConfig.swift +136 -0
  55. package/ios/engine/BGHeartbeatEvent.swift +31 -0
  56. package/ios/engine/BGHeartbeatService.swift +51 -0
  57. package/ios/engine/BGHttpConfig.swift +105 -0
  58. package/ios/engine/BGHttpErrorCodes.swift +63 -0
  59. package/ios/engine/BGHttpEvent.swift +34 -0
  60. package/ios/engine/BGHttpRequest.swift +126 -0
  61. package/ios/engine/BGHttpResponse.swift +93 -0
  62. package/ios/engine/BGHttpService.swift +428 -0
  63. package/ios/engine/BGKalmanFilter.swift +105 -0
  64. package/ios/engine/BGLMActionNames.swift +55 -0
  65. package/ios/engine/BGLicenseManager.swift +26 -0
  66. package/ios/engine/BGLiveActivityManager.swift +327 -0
  67. package/ios/engine/BGLocation.swift +311 -0
  68. package/ios/engine/BGLocationAuthorization.swift +427 -0
  69. package/ios/engine/BGLocationDAO.swift +252 -0
  70. package/ios/engine/BGLocationErrors.swift +28 -0
  71. package/ios/engine/BGLocationEvent.swift +43 -0
  72. package/ios/engine/BGLocationFilter.swift +82 -0
  73. package/ios/engine/BGLocationFilterConfig.swift +57 -0
  74. package/ios/engine/BGLocationHelper.swift +54 -0
  75. package/ios/engine/BGLocationManager.swift +662 -0
  76. package/ios/engine/BGLocationMetricsEngine.swift +116 -0
  77. package/ios/engine/BGLocationRequestService.swift +459 -0
  78. package/ios/engine/BGLocationSatisfier.swift +14 -0
  79. package/ios/engine/BGLocationStreamEvent.swift +27 -0
  80. package/ios/engine/BGLog.swift +337 -0
  81. package/ios/engine/BGLogLevel.swift +26 -0
  82. package/ios/engine/BGLoggerConfig.swift +60 -0
  83. package/ios/engine/BGMotionActivity.swift +31 -0
  84. package/ios/engine/BGMotionActivityClassifier.swift +108 -0
  85. package/ios/engine/BGMotionActivityManagerAdapter.swift +40 -0
  86. package/ios/engine/BGMotionActivitySource.swift +46 -0
  87. package/ios/engine/BGMotionDetector.swift +377 -0
  88. package/ios/engine/BGMotionPermissionManager.swift +50 -0
  89. package/ios/engine/BGNativeLogger.swift +48 -0
  90. package/ios/engine/BGNotificaitons.swift +37 -0
  91. package/ios/engine/BGOdometer.swift +66 -0
  92. package/ios/engine/BGPersistenceConfig.swift +29 -0
  93. package/ios/engine/BGPolygonStreamRequest.swift +48 -0
  94. package/ios/engine/BGPowerSaveChangeEvent.swift +12 -0
  95. package/ios/engine/BGPropertySpec.swift +29 -0
  96. package/ios/engine/BGProviderChangeEvent.swift +31 -0
  97. package/ios/engine/BGQueue.swift +50 -0
  98. package/ios/engine/BGRPC.swift +194 -0
  99. package/ios/engine/BGReachability.swift +58 -0
  100. package/ios/engine/BGResultSet.swift +157 -0
  101. package/ios/engine/BGSchedule.swift +228 -0
  102. package/ios/engine/BGScheduleEvent.swift +13 -0
  103. package/ios/engine/BGScheduler.swift +116 -0
  104. package/ios/engine/BGSingleLocationRequest.swift +49 -0
  105. package/ios/engine/BGStreamLocationRequest.swift +42 -0
  106. package/ios/engine/BGTemplate.swift +54 -0
  107. package/ios/engine/BGTimerService.swift +46 -0
  108. package/ios/engine/BGTrackingAudioManager.swift +286 -0
  109. package/ios/engine/BGTrackingService.swift +879 -0
  110. package/ios/engine/BGWatchPositionRequest.swift +63 -0
  111. package/ios/engine/DatabaseQueue.swift +47 -0
  112. package/ios/engine/LogQuery.swift +10 -0
  113. package/ios/engine/SQLQuery.swift +65 -0
  114. package/ios/engine/TransistorAuthorizationToken.swift +182 -0
  115. package/ios/liveactivity/BGLiveTrackingAttributes.swift +52 -0
  116. package/ios/locationpush/BGLocationPushDeliverer.swift +260 -0
  117. package/ios/locationpush/BGLocationPushService.swift +161 -0
  118. package/ios/locationpush/BGLocationPushShared.swift +98 -0
  119. package/ios/locationpush/BGLocationPushSocketClient.swift +198 -0
  120. package/lib/module/NativeBgGeolocation.js +5 -0
  121. package/lib/module/NativeBgGeolocation.js.map +1 -0
  122. package/lib/module/events.js +20 -0
  123. package/lib/module/events.js.map +1 -0
  124. package/lib/module/index.js +706 -0
  125. package/lib/module/index.js.map +1 -0
  126. package/lib/module/package.json +1 -0
  127. package/lib/module/types.js +2 -0
  128. package/lib/module/types.js.map +1 -0
  129. package/lib/typescript/package.json +1 -0
  130. package/lib/typescript/src/NativeBgGeolocation.d.ts +57 -0
  131. package/lib/typescript/src/NativeBgGeolocation.d.ts.map +1 -0
  132. package/lib/typescript/src/events.d.ts +18 -0
  133. package/lib/typescript/src/events.d.ts.map +1 -0
  134. package/lib/typescript/src/index.d.ts +238 -0
  135. package/lib/typescript/src/index.d.ts.map +1 -0
  136. package/lib/typescript/src/types.d.ts +229 -0
  137. package/lib/typescript/src/types.d.ts.map +1 -0
  138. package/package.json +141 -0
  139. package/src/NativeBgGeolocation.ts +236 -0
  140. package/src/events.ts +17 -0
  141. package/src/index.tsx +935 -0
  142. package/src/types.ts +254 -0
@@ -0,0 +1,63 @@
1
+ import Foundation
2
+ import CoreLocation
3
+
4
+ public typealias BGWatchPositionSuccessBlock = (Any?) -> Void
5
+ public typealias BGWatchPositionFailureBlock = (Int) -> Void
6
+
7
+ @objc public final class BGWatchPositionRequest: NSObject {
8
+
9
+ @objc public var persist: Bool
10
+ @objc public var desiredAccuracy: CLLocationAccuracy
11
+ @objc public private(set) var interval: Double
12
+ @objc public var extras: [AnyHashable: Any]?
13
+ @objc public var timeout: Double
14
+ @objc public var success: BGWatchPositionSuccessBlock?
15
+ @objc public var failure: BGWatchPositionFailureBlock?
16
+
17
+ @objc public class func request(success: @escaping BGWatchPositionSuccessBlock,
18
+ failure: @escaping BGWatchPositionFailureBlock) -> BGWatchPositionRequest {
19
+ let config = BGConfig.sharedInstance()
20
+ let geo = config.geolocation
21
+ return BGWatchPositionRequest(interval: 60000.0,
22
+ timeout: geo.locationTimeout,
23
+ persist: config.enabled,
24
+ extras: nil,
25
+ success: success,
26
+ failure: failure)
27
+ }
28
+
29
+ @objc public class func request(interval: Double,
30
+ success: @escaping BGWatchPositionSuccessBlock,
31
+ failure: @escaping BGWatchPositionFailureBlock) -> BGWatchPositionRequest {
32
+ let config = BGConfig.sharedInstance()
33
+ let geo = config.geolocation
34
+ return BGWatchPositionRequest(interval: interval,
35
+ timeout: geo.locationTimeout,
36
+ persist: config.enabled,
37
+ extras: nil,
38
+ success: success,
39
+ failure: failure)
40
+ }
41
+
42
+ @objc public init(interval: Double,
43
+ timeout: Double,
44
+ persist: Bool,
45
+ extras: [AnyHashable: Any]?,
46
+ success: @escaping BGWatchPositionSuccessBlock,
47
+ failure: @escaping BGWatchPositionFailureBlock) {
48
+ self.desiredAccuracy = kCLLocationAccuracyBest
49
+ self.interval = max(interval, 1000.0)
50
+ self.timeout = max(timeout, 0.0)
51
+ self.persist = persist
52
+ self.extras = extras
53
+ self.success = success
54
+ self.failure = failure
55
+ super.init()
56
+ }
57
+
58
+ public func setInterval(_ newValue: Double) {
59
+ objc_sync_enter(self)
60
+ interval = max(newValue, 1000.0)
61
+ objc_sync_exit(self)
62
+ }
63
+ }
@@ -0,0 +1,47 @@
1
+ import Foundation
2
+
3
+ @objc public class DatabaseQueue: NSObject {
4
+
5
+ private static var _sharedInstance: DatabaseQueue?
6
+ private static let lock = NSLock()
7
+
8
+ @objc public var queue: Any?
9
+
10
+ @objc public class func sharedInstance() -> DatabaseQueue {
11
+ lock.lock()
12
+ defer { lock.unlock() }
13
+ if _sharedInstance == nil {
14
+ _sharedInstance = DatabaseQueue()
15
+ }
16
+ return _sharedInstance!
17
+ }
18
+
19
+ @objc public override init() {
20
+ super.init()
21
+ openDatabase()
22
+ }
23
+
24
+ private func databasePath() -> String {
25
+ let docs = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
26
+ return (docs as NSString).appendingPathComponent("BGDatabase.db")
27
+ }
28
+
29
+ private func openDatabase() {
30
+ let path = databasePath()
31
+ queue = path
32
+ }
33
+
34
+ @objc public func migrateDatabase(_ db: Any?) {
35
+ }
36
+
37
+ @objc public func hasMigratedLocations() -> Bool {
38
+ return false
39
+ }
40
+
41
+ @objc public func getMigratedLocations() -> [[String: Any]] {
42
+ return []
43
+ }
44
+
45
+ @objc public func finishMigration() {
46
+ }
47
+ }
@@ -0,0 +1,10 @@
1
+ import Foundation
2
+
3
+ @objc public class LogQuery: SQLQuery {
4
+
5
+ @objc public override init() {
6
+ super.init()
7
+ self.order = 1
8
+ self.limit = -1
9
+ }
10
+ }
@@ -0,0 +1,65 @@
1
+ import Foundation
2
+
3
+ @objc public class SQLQuery: NSObject {
4
+
5
+ @objc public var start: Double = 0
6
+ @objc public var end: Double = 0
7
+ @objc public var limit: Int = -1
8
+ @objc public var order: Int = 1
9
+ @objc public var arguments: NSMutableArray = NSMutableArray()
10
+
11
+ private var _orderString: String = "ASC"
12
+
13
+ @objc public override init() {
14
+ super.init()
15
+ self.limit = -1
16
+ self.order = 1
17
+ self.start = 0
18
+ self.end = 0
19
+ self.arguments = NSMutableArray()
20
+ }
21
+
22
+ @objc public init(dictionary: [String: Any]) {
23
+ super.init()
24
+ if let s = dictionary["start"] as? Double { self.start = s }
25
+ if let e = dictionary["end"] as? Double { self.end = e }
26
+ if let l = dictionary["limit"] as? Int { self.limit = l }
27
+ if let o = dictionary["order"] as? Int { self.order = o }
28
+ }
29
+
30
+ @objc public func addArgument(_ arg: Any) {
31
+ arguments.add(arg)
32
+ }
33
+
34
+ @objc public func render() -> String {
35
+ var clauses: [String] = []
36
+ var args: [Any] = []
37
+
38
+ if start > 0 {
39
+ clauses.append("timestamp >= ?")
40
+ args.append(start)
41
+ }
42
+ if end > 0 {
43
+ clauses.append("timestamp <= ?")
44
+ args.append(end)
45
+ }
46
+
47
+ arguments.addObjects(from: args)
48
+
49
+ var sql = clauses.isEmpty ? "" : "WHERE " + clauses.joined(separator: " AND ")
50
+ let orderDir = order >= 0 ? "ASC" : "DESC"
51
+ sql += " ORDER BY timestamp \(orderDir)"
52
+ if limit > 0 {
53
+ sql += " LIMIT \(limit)"
54
+ }
55
+ return sql
56
+ }
57
+
58
+ @objc public func toString() -> String {
59
+ return render()
60
+ }
61
+
62
+ @objc public override var description: String {
63
+ return "<SQLQuery start=\(start) end=\(end) limit=\(limit) order=\(order)>"
64
+ }
65
+ }
@@ -0,0 +1,182 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ @objc public class TransistorAuthorizationToken: NSObject {
5
+
6
+ @objc public var accessToken: String
7
+ @objc public var refreshToken: String
8
+ @objc public var expires: Date
9
+ @objc public var apiUrl: String?
10
+ @objc public var refreshUrl: String?
11
+
12
+ private static let storagePrefix = "BGLocationManager_transistor_token_"
13
+
14
+ @objc public class func storageKey(forHost host: String) -> String {
15
+ return "\(storagePrefix)\(host)"
16
+ }
17
+
18
+ @objc public class func hasToken(forHost host: String) -> Bool {
19
+ let key = storageKey(forHost: host)
20
+ return UserDefaults.standard.dictionary(forKey: key) != nil
21
+ }
22
+
23
+ @objc public class func destroy(url: URL) {
24
+ guard let host = url.host else { return }
25
+ let key = storageKey(forHost: host)
26
+ UserDefaults.standard.removeObject(forKey: key)
27
+ UserDefaults.standard.synchronize()
28
+ }
29
+
30
+ @objc public class func findOrCreate(
31
+ org: String,
32
+ username: String,
33
+ url: URL,
34
+ framework: String,
35
+ success: @escaping (_ token: TransistorAuthorizationToken) -> Void,
36
+ failure: @escaping (_ error: Error) -> Void
37
+ ) {
38
+ guard let host = url.host else {
39
+ failure(NSError(domain: "TransistorAuthorizationToken", code: -1,
40
+ userInfo: [NSLocalizedDescriptionKey: "Invalid URL: missing host"]))
41
+ return
42
+ }
43
+
44
+ let key = storageKey(forHost: host)
45
+
46
+ if let stored = UserDefaults.standard.dictionary(forKey: key) {
47
+ let token = TransistorAuthorizationToken(dictionary: stored, forUrl: url)
48
+ if token.expires > Date() {
49
+ DispatchQueue.main.async { success(token) }
50
+ return
51
+ }
52
+ }
53
+
54
+ let registrationURL = buildRegistrationURL(baseURL: url)
55
+
56
+ var request = URLRequest(url: registrationURL)
57
+ request.httpMethod = "POST"
58
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
59
+ request.timeoutInterval = 60.0
60
+
61
+ let deviceInfo = BGDeviceInfo.sharedInstance
62
+ let body: [String: Any] = [
63
+ "org": org,
64
+ "device": [
65
+ "unique_id": UIDevice.current.identifierForVendor?.uuidString ?? "",
66
+ "model": deviceInfo.model ?? "",
67
+ "platform": "iOS",
68
+ "manufacturer": "Apple",
69
+ "version": deviceInfo.version ?? "",
70
+ "framework": framework,
71
+ "username": username
72
+ ]
73
+ ]
74
+
75
+ request.httpBody = try? JSONSerialization.data(withJSONObject: body)
76
+
77
+ let session = URLSession(configuration: .default)
78
+ let task = session.dataTask(with: request) { data, response, error in
79
+ if let error = error {
80
+ DispatchQueue.main.async { failure(error) }
81
+ return
82
+ }
83
+
84
+ guard let httpResponse = response as? HTTPURLResponse else {
85
+ DispatchQueue.main.async {
86
+ failure(NSError(domain: "TransistorAuthorizationToken", code: -1,
87
+ userInfo: [NSLocalizedDescriptionKey: "Invalid response"]))
88
+ }
89
+ return
90
+ }
91
+
92
+ if httpResponse.statusCode == 200 {
93
+ guard let data = data,
94
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
95
+ DispatchQueue.main.async {
96
+ failure(NSError(domain: "TransistorAuthorizationToken", code: -2,
97
+ userInfo: [NSLocalizedDescriptionKey: "Failed to parse response"]))
98
+ }
99
+ return
100
+ }
101
+
102
+ let token = TransistorAuthorizationToken(dictionary: json, forUrl: url)
103
+ let stored = token.toDictionary()
104
+ UserDefaults.standard.set(stored, forKey: key)
105
+ UserDefaults.standard.synchronize()
106
+
107
+ DispatchQueue.main.async { success(token) }
108
+ } else {
109
+ guard let data = data,
110
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
111
+ DispatchQueue.main.async {
112
+ failure(NSError(domain: "TransistorAuthorizationToken", code: httpResponse.statusCode,
113
+ userInfo: [NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode)"]))
114
+ }
115
+ return
116
+ }
117
+ let msg = json["error"] as? String ?? "HTTP \(httpResponse.statusCode)"
118
+ DispatchQueue.main.async {
119
+ failure(NSError(domain: "TransistorAuthorizationToken", code: httpResponse.statusCode,
120
+ userInfo: [NSLocalizedDescriptionKey: msg]))
121
+ }
122
+ }
123
+ }
124
+ task.resume()
125
+ }
126
+
127
+ private class func buildRegistrationURL(baseURL: URL) -> URL {
128
+ var urlString = baseURL.absoluteString
129
+ if urlString.hasSuffix("/") {
130
+ urlString = String(urlString.dropLast())
131
+ }
132
+ let registrationPath = urlString + "/api/devices"
133
+ return URL(string: registrationPath) ?? baseURL
134
+ }
135
+
136
+ @objc public init(accessToken: String, refreshToken: String, expires: Date) {
137
+ self.accessToken = accessToken
138
+ self.refreshToken = refreshToken
139
+ self.expires = expires
140
+ super.init()
141
+ }
142
+
143
+ @objc public init(dictionary: [String: Any], forUrl url: URL) {
144
+ self.accessToken = dictionary["access_token"] as? String ?? ""
145
+ self.refreshToken = dictionary["refresh_token"] as? String ?? ""
146
+
147
+ if let expiresAt = dictionary["expires_at"] as? Double {
148
+ self.expires = Date(timeIntervalSince1970: expiresAt)
149
+ } else if let expiresStr = dictionary["expires_at"] as? String {
150
+ let formatter = ISO8601DateFormatter()
151
+ self.expires = formatter.date(from: expiresStr) ?? Date.distantFuture
152
+ } else {
153
+ self.expires = Date.distantFuture
154
+ }
155
+
156
+ self.apiUrl = url.absoluteString
157
+ if let baseUrl = dictionary["base_url"] as? String {
158
+ self.refreshUrl = baseUrl + "/api/refresh_token"
159
+ }
160
+ super.init()
161
+ }
162
+
163
+ @objc public override init() {
164
+ self.accessToken = ""
165
+ self.refreshToken = ""
166
+ self.expires = Date.distantFuture
167
+ super.init()
168
+ }
169
+
170
+ @objc public func toDictionary() -> [String: Any] {
171
+ return [
172
+ "access_token": accessToken,
173
+ "refresh_token": refreshToken,
174
+ "expires_at": expires.timeIntervalSince1970,
175
+ "api_url": apiUrl ?? ""
176
+ ]
177
+ }
178
+
179
+ @objc public override var description: String {
180
+ return "<TransistorAuthorizationToken accessToken=\(accessToken) expires=\(expires)>"
181
+ }
182
+ }
@@ -0,0 +1,52 @@
1
+ import ActivityKit
2
+ import Foundation
3
+
4
+ @available(iOS 16.2, *)
5
+ public struct BGLiveTrackingAttributes: ActivityAttributes {
6
+ public struct ContentState: Codable, Hashable {
7
+ public var status: String
8
+ public var isMoving: Bool
9
+ public var activity: String
10
+ public var latitude: Double
11
+ public var longitude: Double
12
+ public var accuracy: Double
13
+ public var speed: Double
14
+ public var distance: Double
15
+ public var locationCount: Int
16
+ public var updatedAt: Date
17
+
18
+ public init(
19
+ status: String,
20
+ isMoving: Bool,
21
+ activity: String,
22
+ latitude: Double,
23
+ longitude: Double,
24
+ accuracy: Double,
25
+ speed: Double,
26
+ distance: Double,
27
+ locationCount: Int,
28
+ updatedAt: Date
29
+ ) {
30
+ self.status = status
31
+ self.isMoving = isMoving
32
+ self.activity = activity
33
+ self.latitude = latitude
34
+ self.longitude = longitude
35
+ self.accuracy = accuracy
36
+ self.speed = speed
37
+ self.distance = distance
38
+ self.locationCount = locationCount
39
+ self.updatedAt = updatedAt
40
+ }
41
+ }
42
+
43
+ public var trackingId: String
44
+ public var title: String
45
+ public var subtitle: String
46
+
47
+ public init(trackingId: String, title: String, subtitle: String) {
48
+ self.trackingId = trackingId
49
+ self.title = title
50
+ self.subtitle = subtitle
51
+ }
52
+ }
@@ -0,0 +1,260 @@
1
+ //
2
+ // BGLocationPushDeliverer.swift
3
+ //
4
+ // Shared, JS-independent delivery for a push-triggered location. Used by BOTH:
5
+ // • BGLocationPushService (the kill-state extension, separate process)
6
+ // • BgGeolocation module (the in-app background-push handler)
7
+ //
8
+ // Push-triggered delivery CANNOT depend on the React Native bridge: when iOS
9
+ // wakes the app (or the extension) from a killed/suspended state, JS is often
10
+ // not booted and emitted events are dropped. So the SDK delivers natively here
11
+ // — socket first, then REST fallback — reading config the host app provided via
12
+ // BackgroundGeolocation.setLocationPushConfig({...}).
13
+ //
14
+ // Extension-safe: Foundation + URLSession only.
15
+ //
16
+
17
+ import CoreLocation
18
+ import Foundation
19
+
20
+ @available(iOS 15.0, *)
21
+ @objc public final class BGLocationPushDeliverer: NSObject {
22
+
23
+ /// Deliver a location captured for `queryId`. Tries the socket channel first
24
+ /// (if configured), then the REST fallback. `completion(true)` on the first
25
+ /// channel that succeeds, `completion(false)` if everything fails.
26
+ @objc public static func deliver(
27
+ latitude: Double,
28
+ longitude: Double,
29
+ accuracy: Double,
30
+ speed: Double,
31
+ heading: Double,
32
+ altitude: Double,
33
+ timestampISO: String,
34
+ queryId: String,
35
+ completion: @escaping (Bool) -> Void
36
+ ) {
37
+ let job = BGLocationPushDeliveryJob(
38
+ latitude: latitude, longitude: longitude, accuracy: accuracy,
39
+ speed: speed, heading: heading, altitude: altitude,
40
+ timestampISO: timestampISO, queryId: queryId, completion: completion
41
+ )
42
+ job.start()
43
+ }
44
+
45
+ static func currentTimeHHmm() -> String {
46
+ let formatter = DateFormatter()
47
+ formatter.dateFormat = "HH:mm"
48
+ return formatter.string(from: Date())
49
+ }
50
+ }
51
+
52
+ @available(iOS 15.0, *)
53
+ private final class BGLocationPushDeliveryJob {
54
+ let latitude, longitude, accuracy, speed, heading, altitude: Double
55
+ let timestampISO: String
56
+ let queryId: String
57
+ let completion: (Bool) -> Void
58
+ private var socketClient: BGLocationPushSocketClient?
59
+ private var retained: BGLocationPushDeliveryJob?
60
+
61
+ init(latitude: Double, longitude: Double, accuracy: Double, speed: Double,
62
+ heading: Double, altitude: Double, timestampISO: String,
63
+ queryId: String, completion: @escaping (Bool) -> Void) {
64
+ self.latitude = latitude; self.longitude = longitude
65
+ self.accuracy = accuracy; self.speed = speed
66
+ self.heading = heading; self.altitude = altitude
67
+ self.timestampISO = timestampISO; self.queryId = queryId
68
+ self.completion = completion
69
+ }
70
+
71
+ func start() {
72
+ retained = self // keep alive across async callbacks
73
+ guard let defaults = BGLocationPushShared.sharedDefaults() else {
74
+ BGLocationPushLog.log("no shared config — cannot deliver")
75
+ finish(false)
76
+ return
77
+ }
78
+
79
+ if let socketUrlString = defaults.string(forKey: BGLocationPushShared.keySocketUrl),
80
+ !socketUrlString.isEmpty, let socketUrl = URL(string: socketUrlString) {
81
+ let path = defaults.string(forKey: BGLocationPushShared.keySocketPath) ?? "/socket.io"
82
+ let event = defaults.string(forKey: BGLocationPushShared.keySocketEvent) ?? "location:update"
83
+ let token = defaults.string(forKey: BGLocationPushShared.keySocketAuthToken)
84
+ let timeout = defaults.object(forKey: BGLocationPushShared.keySocketTimeout) as? Double ?? 8.0
85
+
86
+ BGLocationPushLog.log("socket delivery → \(socketUrlString)\(path)")
87
+ let client = BGLocationPushSocketClient(config: .init(
88
+ url: socketUrl, path: path, event: event, authToken: token, timeout: timeout
89
+ ))
90
+ socketClient = client
91
+ client.emit(payload(defaults: defaults, default: defaultPayload())) { [weak self] ok in
92
+ guard let self = self else { return }
93
+ if ok {
94
+ BGLocationPushLog.log("✅ delivered via socket")
95
+ self.finish(true)
96
+ } else {
97
+ BGLocationPushLog.log("socket failed → REST fallback")
98
+ self.postViaRest(defaults: defaults)
99
+ }
100
+ }
101
+ return
102
+ }
103
+
104
+ postViaRest(defaults: defaults)
105
+ }
106
+
107
+ private func postViaRest(defaults: UserDefaults) {
108
+ let headers = defaults.dictionary(forKey: BGLocationPushShared.keyHeaders) as? [String: String] ?? [:]
109
+ let bearer = defaults.string(forKey: BGLocationPushShared.keySocketAuthToken)
110
+ ?? defaults.string(forKey: BGLocationPushShared.keyAccessToken)
111
+
112
+ let url: URL
113
+ let body: [String: Any]
114
+
115
+ if let fallbackString = defaults.string(forKey: BGLocationPushShared.keyFallbackUrl),
116
+ !fallbackString.isEmpty, let fallbackUrl = URL(string: fallbackString) {
117
+ url = fallbackUrl
118
+ body = payload(defaults: defaults, default: defaultPayload())
119
+ BGLocationPushLog.log("REST fallback → \(fallbackString)")
120
+ } else {
121
+ let urlString = defaults.string(forKey: BGLocationPushShared.keyUrl) ?? ""
122
+ guard !urlString.isEmpty, let generic = URL(string: urlString) else {
123
+ BGLocationPushLog.log("no REST url configured — giving up")
124
+ finish(false)
125
+ return
126
+ }
127
+ url = generic
128
+ let rootProperty = defaults.string(forKey: BGLocationPushShared.keyRootProperty) ?? "location"
129
+ let params = defaults.dictionary(forKey: BGLocationPushShared.keyParams) ?? [:]
130
+ let locationDict = restLocation(defaults: defaults)
131
+ var generated: [String: Any] = [:]
132
+ if rootProperty.isEmpty || rootProperty == "." { generated = locationDict }
133
+ else { generated[rootProperty] = locationDict }
134
+ for (k, v) in params { generated[k] = v }
135
+ generated["location_query_id"] = queryId
136
+ body = generated
137
+ BGLocationPushLog.log("REST → \(urlString)")
138
+ }
139
+
140
+ var request = URLRequest(url: url)
141
+ request.httpMethod = "POST"
142
+ request.timeoutInterval = 20
143
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
144
+ if let token = bearer, !token.isEmpty {
145
+ request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
146
+ }
147
+ for (k, v) in headers { request.setValue(v, forHTTPHeaderField: k) }
148
+ request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])
149
+
150
+ URLSession.shared.dataTask(with: request) { [weak self] _, response, error in
151
+ if let error = error {
152
+ BGLocationPushLog.log("REST error: \(error.localizedDescription)")
153
+ self?.finish(false)
154
+ } else if let http = response as? HTTPURLResponse {
155
+ BGLocationPushLog.log("REST HTTP \(http.statusCode)")
156
+ self?.finish((200...299).contains(http.statusCode))
157
+ } else {
158
+ self?.finish(false)
159
+ }
160
+ }.resume()
161
+ }
162
+
163
+ /// Resolve the upload body: if the host supplied a `payloadTemplate` via
164
+ /// setLocationPushConfig, render it (substituting {tokens} with live values);
165
+ /// otherwise use the built-in `default` payload.
166
+ private func payload(defaults: UserDefaults, default fallback: [String: Any]) -> [String: Any] {
167
+ guard let template = defaults.dictionary(forKey: BGLocationPushShared.keyPayloadTemplate) else {
168
+ return fallback
169
+ }
170
+ return (render(template) as? [String: Any]) ?? fallback
171
+ }
172
+
173
+ /// Live values that template tokens like "{latitude}" / "{fcmToken}" map to.
174
+ private func tokenValues() -> [String: Any] {
175
+ let fcm = BGLocationPushShared.sharedDefaults()?
176
+ .string(forKey: BGLocationPushShared.keyFcmToken) ?? ""
177
+ let time = BGLocationPushDeliverer.currentTimeHHmm()
178
+ return [
179
+ "latitude": latitude, "lat": latitude,
180
+ "longitude": longitude, "long": longitude,
181
+ "accuracy": max(accuracy, 0),
182
+ "speed": speed, "heading": heading, "altitude": altitude,
183
+ "timestamp": timestampISO,
184
+ "fcmToken": fcm, "fcm_token": fcm,
185
+ "queryId": queryId, "locationQueryId": queryId, "location_query_id": queryId,
186
+ "userCurrentTime": time, "user_current_time": time, "time": time,
187
+ "deviceType": "ios", "device_type": "ios"
188
+ ]
189
+ }
190
+
191
+ /// Recursively substitute tokens in a template value. A string that is exactly
192
+ /// one token ("{latitude}") yields the typed value (Double/String); a string
193
+ /// with inline tokens is interpolated; dicts/arrays recurse; other values pass
194
+ /// through unchanged.
195
+ private func render(_ value: Any) -> Any {
196
+ let tokens = tokenValues()
197
+ if let dict = value as? [String: Any] {
198
+ var out: [String: Any] = [:]
199
+ for (k, v) in dict { out[k] = render(v) }
200
+ return out
201
+ }
202
+ if let arr = value as? [Any] {
203
+ return arr.map { render($0) }
204
+ }
205
+ if let s = value as? String {
206
+ if s.hasPrefix("{"), s.hasSuffix("}"),
207
+ !s.dropFirst().dropLast().contains("{"),
208
+ let typed = tokens[String(s.dropFirst().dropLast())] {
209
+ return typed // exact single-token → typed value (keeps numbers numeric)
210
+ }
211
+ var result = s
212
+ for (name, val) in tokens {
213
+ result = result.replacingOccurrences(of: "{\(name)}", with: "\(val)")
214
+ }
215
+ return result
216
+ }
217
+ return value
218
+ }
219
+
220
+ /// Built-in default upload body (socket emit + REST fallback) when the host
221
+ /// does not supply a `payloadTemplate`. Matches the backend's expected shape.
222
+ private func defaultPayload() -> [String: Any] {
223
+ let fcm = BGLocationPushShared.sharedDefaults()?
224
+ .string(forKey: BGLocationPushShared.keyFcmToken) ?? ""
225
+ return [
226
+ "lat": latitude,
227
+ "long": longitude,
228
+ "fcm_token": fcm,
229
+ "device_type": "ios",
230
+ "user_current_time": BGLocationPushDeliverer.currentTimeHHmm(),
231
+ "location_query_id": queryId
232
+ ]
233
+ }
234
+
235
+ private func restLocation(defaults: UserDefaults) -> [String: Any] {
236
+ let extras = defaults.dictionary(forKey: BGLocationPushShared.keyExtras) ?? [:]
237
+ var mergedExtras: [String: Any] = ["LocationPushService": true, "location_query_id": queryId]
238
+ for (k, v) in extras { mergedExtras[k] = v }
239
+ return [
240
+ "coords": [
241
+ "latitude": latitude, "longitude": longitude,
242
+ "accuracy": max(accuracy, 0), "speed": speed,
243
+ "heading": heading, "altitude": altitude
244
+ ],
245
+ "latitude": latitude, "longitude": longitude,
246
+ "lat": latitude, "long": longitude,
247
+ "timestamp": timestampISO, "is_moving": false,
248
+ "event": "location-push", "extras": mergedExtras
249
+ ]
250
+ }
251
+
252
+ private func finish(_ success: Bool) {
253
+ let cb = completion
254
+ socketClient = nil
255
+ let keepAlive = retained
256
+ retained = nil
257
+ cb(success)
258
+ _ = keepAlive
259
+ }
260
+ }