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.
- package/BgGeolocation.podspec +39 -0
- package/LICENSE +20 -0
- package/README.md +366 -0
- package/android/build.gradle +69 -0
- package/android/src/main/AndroidManifest.xml +53 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationActivityRecognitionReceiver.kt +116 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationBootReceiver.kt +44 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationForegroundService.kt +373 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationGeofenceReceiver.kt +55 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationHeadlessTask.kt +138 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationModule.kt +1030 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationMotionStateMachine.kt +159 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationPackage.kt +31 -0
- package/android/src/main/res/drawable/bg_geo_notification.xml +9 -0
- package/ios/BgGeolocation.h +14 -0
- package/ios/BgGeolocation.mm +709 -0
- package/ios/engine/AtomicBoolean.swift +48 -0
- package/ios/engine/BGActivityChangeEvent.swift +20 -0
- package/ios/engine/BGActivityConfig.swift +71 -0
- package/ios/engine/BGAppConfig.swift +92 -0
- package/ios/engine/BGAppState.swift +147 -0
- package/ios/engine/BGAuthorization.swift +85 -0
- package/ios/engine/BGAuthorizationAlertPresenter.swift +39 -0
- package/ios/engine/BGAuthorizationConfig.swift +50 -0
- package/ios/engine/BGAuthorizationEvent.swift +40 -0
- package/ios/engine/BGBackgroundTaskManager.swift +143 -0
- package/ios/engine/BGCLRouter.swift +101 -0
- package/ios/engine/BGCallback.swift +19 -0
- package/ios/engine/BGConfig.swift +440 -0
- package/ios/engine/BGConfigModuleBase.swift +180 -0
- package/ios/engine/BGConfigOLD.swift +582 -0
- package/ios/engine/BGConnectivityChangeEvent.swift +15 -0
- package/ios/engine/BGCrashDetector.swift +122 -0
- package/ios/engine/BGCurrentPositionRequest.swift +87 -0
- package/ios/engine/BGDataStore.swift +75 -0
- package/ios/engine/BGDatabase.swift +677 -0
- package/ios/engine/BGDatabasePool.swift +220 -0
- package/ios/engine/BGDatabaseQueue.swift +215 -0
- package/ios/engine/BGDateUtils.swift +26 -0
- package/ios/engine/BGDeviceInfo.swift +54 -0
- package/ios/engine/BGDeviceManager.swift +65 -0
- package/ios/engine/BGEnabledChangeEvent.swift +11 -0
- package/ios/engine/BGEnv.swift +17 -0
- package/ios/engine/BGEventBus.swift +83 -0
- package/ios/engine/BGEventManager.swift +169 -0
- package/ios/engine/BGEventNames.swift +51 -0
- package/ios/engine/BGGeofence.swift +233 -0
- package/ios/engine/BGGeofenceDAO.swift +152 -0
- package/ios/engine/BGGeofenceEvent.swift +42 -0
- package/ios/engine/BGGeofenceLocationRequest.swift +94 -0
- package/ios/engine/BGGeofenceManager.swift +315 -0
- package/ios/engine/BGGeofenceTransition.swift +97 -0
- package/ios/engine/BGGeofencesChangeEvent.swift +26 -0
- package/ios/engine/BGGeolocationConfig.swift +136 -0
- package/ios/engine/BGHeartbeatEvent.swift +31 -0
- package/ios/engine/BGHeartbeatService.swift +51 -0
- package/ios/engine/BGHttpConfig.swift +105 -0
- package/ios/engine/BGHttpErrorCodes.swift +63 -0
- package/ios/engine/BGHttpEvent.swift +34 -0
- package/ios/engine/BGHttpRequest.swift +126 -0
- package/ios/engine/BGHttpResponse.swift +93 -0
- package/ios/engine/BGHttpService.swift +428 -0
- package/ios/engine/BGKalmanFilter.swift +105 -0
- package/ios/engine/BGLMActionNames.swift +55 -0
- package/ios/engine/BGLicenseManager.swift +26 -0
- package/ios/engine/BGLiveActivityManager.swift +327 -0
- package/ios/engine/BGLocation.swift +311 -0
- package/ios/engine/BGLocationAuthorization.swift +427 -0
- package/ios/engine/BGLocationDAO.swift +252 -0
- package/ios/engine/BGLocationErrors.swift +28 -0
- package/ios/engine/BGLocationEvent.swift +43 -0
- package/ios/engine/BGLocationFilter.swift +82 -0
- package/ios/engine/BGLocationFilterConfig.swift +57 -0
- package/ios/engine/BGLocationHelper.swift +54 -0
- package/ios/engine/BGLocationManager.swift +662 -0
- package/ios/engine/BGLocationMetricsEngine.swift +116 -0
- package/ios/engine/BGLocationRequestService.swift +459 -0
- package/ios/engine/BGLocationSatisfier.swift +14 -0
- package/ios/engine/BGLocationStreamEvent.swift +27 -0
- package/ios/engine/BGLog.swift +337 -0
- package/ios/engine/BGLogLevel.swift +26 -0
- package/ios/engine/BGLoggerConfig.swift +60 -0
- package/ios/engine/BGMotionActivity.swift +31 -0
- package/ios/engine/BGMotionActivityClassifier.swift +108 -0
- package/ios/engine/BGMotionActivityManagerAdapter.swift +40 -0
- package/ios/engine/BGMotionActivitySource.swift +46 -0
- package/ios/engine/BGMotionDetector.swift +377 -0
- package/ios/engine/BGMotionPermissionManager.swift +50 -0
- package/ios/engine/BGNativeLogger.swift +48 -0
- package/ios/engine/BGNotificaitons.swift +37 -0
- package/ios/engine/BGOdometer.swift +66 -0
- package/ios/engine/BGPersistenceConfig.swift +29 -0
- package/ios/engine/BGPolygonStreamRequest.swift +48 -0
- package/ios/engine/BGPowerSaveChangeEvent.swift +12 -0
- package/ios/engine/BGPropertySpec.swift +29 -0
- package/ios/engine/BGProviderChangeEvent.swift +31 -0
- package/ios/engine/BGQueue.swift +50 -0
- package/ios/engine/BGRPC.swift +194 -0
- package/ios/engine/BGReachability.swift +58 -0
- package/ios/engine/BGResultSet.swift +157 -0
- package/ios/engine/BGSchedule.swift +228 -0
- package/ios/engine/BGScheduleEvent.swift +13 -0
- package/ios/engine/BGScheduler.swift +116 -0
- package/ios/engine/BGSingleLocationRequest.swift +49 -0
- package/ios/engine/BGStreamLocationRequest.swift +42 -0
- package/ios/engine/BGTemplate.swift +54 -0
- package/ios/engine/BGTimerService.swift +46 -0
- package/ios/engine/BGTrackingAudioManager.swift +286 -0
- package/ios/engine/BGTrackingService.swift +879 -0
- package/ios/engine/BGWatchPositionRequest.swift +63 -0
- package/ios/engine/DatabaseQueue.swift +47 -0
- package/ios/engine/LogQuery.swift +10 -0
- package/ios/engine/SQLQuery.swift +65 -0
- package/ios/engine/TransistorAuthorizationToken.swift +182 -0
- package/ios/liveactivity/BGLiveTrackingAttributes.swift +52 -0
- package/ios/locationpush/BGLocationPushDeliverer.swift +260 -0
- package/ios/locationpush/BGLocationPushService.swift +161 -0
- package/ios/locationpush/BGLocationPushShared.swift +98 -0
- package/ios/locationpush/BGLocationPushSocketClient.swift +198 -0
- package/lib/module/NativeBgGeolocation.js +5 -0
- package/lib/module/NativeBgGeolocation.js.map +1 -0
- package/lib/module/events.js +20 -0
- package/lib/module/events.js.map +1 -0
- package/lib/module/index.js +706 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeBgGeolocation.d.ts +57 -0
- package/lib/typescript/src/NativeBgGeolocation.d.ts.map +1 -0
- package/lib/typescript/src/events.d.ts +18 -0
- package/lib/typescript/src/events.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +238 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +229 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +141 -0
- package/src/NativeBgGeolocation.ts +236 -0
- package/src/events.ts +17 -0
- package/src/index.tsx +935 -0
- 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,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
|
+
}
|