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,327 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreLocation
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
#if canImport(ActivityKit)
|
|
6
|
+
import ActivityKit
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
@objc public final class BGLiveActivityManager: NSObject {
|
|
10
|
+
private static let tokenDefaultsKey = "BGLocationManager_liveActivityPushToken"
|
|
11
|
+
private static let idDefaultsKey = "BGLocationManager_liveActivityId"
|
|
12
|
+
private static let trackingIdDefaultsKey = "BGLocationManager_liveActivityTrackingId"
|
|
13
|
+
|
|
14
|
+
@objc public static let shared = BGLiveActivityManager()
|
|
15
|
+
|
|
16
|
+
private let stateQueue = DispatchQueue(label: "BGLiveActivityManager.state")
|
|
17
|
+
private var lastUpdateAt: Date = .distantPast
|
|
18
|
+
private var locationCount = 0
|
|
19
|
+
private var pushTokenTask: Task<Void, Never>?
|
|
20
|
+
private var pendingStartIsMoving: Bool?
|
|
21
|
+
private var lastError: String?
|
|
22
|
+
|
|
23
|
+
private override init() {
|
|
24
|
+
super.init()
|
|
25
|
+
NotificationCenter.default.addObserver(
|
|
26
|
+
self,
|
|
27
|
+
selector: #selector(applicationDidBecomeActive),
|
|
28
|
+
name: UIApplication.didBecomeActiveNotification,
|
|
29
|
+
object: nil
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@objc public var pushToken: String? {
|
|
34
|
+
UserDefaults.standard.string(forKey: Self.tokenDefaultsKey)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@objc public var activityId: String? {
|
|
38
|
+
UserDefaults.standard.string(forKey: Self.idDefaultsKey)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@objc public func startIfNeeded(isMoving: Bool) {
|
|
42
|
+
guard BGConfig.sharedInstance().app.liveActivityEnabled else { return }
|
|
43
|
+
guard UIApplication.shared.applicationState == .active else {
|
|
44
|
+
pendingStartIsMoving = isMoving
|
|
45
|
+
log("Live Activity waiting for the app to become active")
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if #available(iOS 16.2, *) {
|
|
50
|
+
Task { @MainActor in
|
|
51
|
+
await self.startOrRecover(isMoving: isMoving)
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
lastError = "Live Activities require iOS 16.2 or later"
|
|
55
|
+
log(lastError!)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@objc private func applicationDidBecomeActive() {
|
|
60
|
+
guard let isMoving = pendingStartIsMoving else { return }
|
|
61
|
+
pendingStartIsMoving = nil
|
|
62
|
+
startIfNeeded(isMoving: isMoving)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@objc public func update(
|
|
66
|
+
location: CLLocation?,
|
|
67
|
+
isMoving: Bool,
|
|
68
|
+
activity: String,
|
|
69
|
+
force: Bool
|
|
70
|
+
) {
|
|
71
|
+
guard BGConfig.sharedInstance().app.liveActivityEnabled else { return }
|
|
72
|
+
|
|
73
|
+
stateQueue.async {
|
|
74
|
+
let minimumInterval = max(5, BGConfig.sharedInstance().app.liveActivityUpdateInterval)
|
|
75
|
+
guard force || Date().timeIntervalSince(self.lastUpdateAt) >= minimumInterval else { return }
|
|
76
|
+
self.lastUpdateAt = Date()
|
|
77
|
+
if location != nil {
|
|
78
|
+
self.locationCount += 1
|
|
79
|
+
}
|
|
80
|
+
let count = self.locationCount
|
|
81
|
+
|
|
82
|
+
if #available(iOS 16.2, *) {
|
|
83
|
+
Task { @MainActor in
|
|
84
|
+
await self.updateActivities(
|
|
85
|
+
location: location,
|
|
86
|
+
isMoving: isMoving,
|
|
87
|
+
activity: activity,
|
|
88
|
+
locationCount: count
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@objc public func end() {
|
|
96
|
+
pushTokenTask?.cancel()
|
|
97
|
+
pushTokenTask = nil
|
|
98
|
+
|
|
99
|
+
if #available(iOS 16.2, *) {
|
|
100
|
+
Task { @MainActor in
|
|
101
|
+
let final = self.makeState(
|
|
102
|
+
location: BGTrackingService.sharedInstance().lastGoodLocation,
|
|
103
|
+
isMoving: false,
|
|
104
|
+
activity: "stopped",
|
|
105
|
+
locationCount: self.locationCount,
|
|
106
|
+
status: "Tracking stopped"
|
|
107
|
+
)
|
|
108
|
+
let content = ActivityContent(
|
|
109
|
+
state: final,
|
|
110
|
+
staleDate: nil,
|
|
111
|
+
relevanceScore: 0
|
|
112
|
+
)
|
|
113
|
+
for activity in Activity<BGLiveTrackingAttributes>.activities {
|
|
114
|
+
await activity.end(content, dismissalPolicy: .immediate)
|
|
115
|
+
}
|
|
116
|
+
self.clearStoredActivity()
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
clearStoredActivity()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@objc public func stateDictionary() -> [String: Any] {
|
|
124
|
+
let activitiesEnabled: Bool = {
|
|
125
|
+
if #available(iOS 16.2, *) {
|
|
126
|
+
return ActivityAuthorizationInfo().areActivitiesEnabled
|
|
127
|
+
}
|
|
128
|
+
return false
|
|
129
|
+
}()
|
|
130
|
+
let active: Bool = {
|
|
131
|
+
if #available(iOS 16.2, *) {
|
|
132
|
+
return !Activity<BGLiveTrackingAttributes>.activities.isEmpty
|
|
133
|
+
}
|
|
134
|
+
return false
|
|
135
|
+
}()
|
|
136
|
+
var state: [String: Any] = [
|
|
137
|
+
"supported": {
|
|
138
|
+
if #available(iOS 16.2, *) { return true }
|
|
139
|
+
return false
|
|
140
|
+
}(),
|
|
141
|
+
"enabled": BGConfig.sharedInstance().app.liveActivityEnabled,
|
|
142
|
+
"activitiesEnabled": activitiesEnabled,
|
|
143
|
+
"active": active,
|
|
144
|
+
"pushUpdates": BGConfig.sharedInstance().app.liveActivityPushUpdates
|
|
145
|
+
]
|
|
146
|
+
if let activityId {
|
|
147
|
+
state["activityId"] = activityId
|
|
148
|
+
}
|
|
149
|
+
if let pushToken {
|
|
150
|
+
state["pushToken"] = pushToken
|
|
151
|
+
}
|
|
152
|
+
if let lastError {
|
|
153
|
+
state["error"] = lastError
|
|
154
|
+
}
|
|
155
|
+
return state
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@available(iOS 16.2, *)
|
|
159
|
+
@MainActor
|
|
160
|
+
private func startOrRecover(isMoving: Bool) async {
|
|
161
|
+
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
|
|
162
|
+
lastError = "Live Activities are disabled in iOS Settings"
|
|
163
|
+
log(lastError!)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if let activity = Activity<BGLiveTrackingAttributes>.activities.first {
|
|
168
|
+
lastError = nil
|
|
169
|
+
persist(activity: activity)
|
|
170
|
+
observePushToken(for: activity)
|
|
171
|
+
log("Recovered Live Activity \(activity.id)")
|
|
172
|
+
await updateActivities(
|
|
173
|
+
location: BGTrackingService.sharedInstance().lastGoodLocation,
|
|
174
|
+
isMoving: isMoving,
|
|
175
|
+
activity: BGTrackingService.sharedInstance().currentMotionActivity?.type ?? "unknown",
|
|
176
|
+
locationCount: locationCount
|
|
177
|
+
)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let config = BGConfig.sharedInstance().app
|
|
182
|
+
let trackingId = UserDefaults.standard.string(forKey: Self.trackingIdDefaultsKey)
|
|
183
|
+
?? UUID().uuidString
|
|
184
|
+
let attributes = BGLiveTrackingAttributes(
|
|
185
|
+
trackingId: trackingId,
|
|
186
|
+
title: config.liveActivityTitle,
|
|
187
|
+
subtitle: config.liveActivitySubtitle
|
|
188
|
+
)
|
|
189
|
+
let initial = makeState(
|
|
190
|
+
location: BGTrackingService.sharedInstance().lastGoodLocation,
|
|
191
|
+
isMoving: isMoving,
|
|
192
|
+
activity: BGTrackingService.sharedInstance().currentMotionActivity?.type ?? "unknown",
|
|
193
|
+
locationCount: locationCount
|
|
194
|
+
)
|
|
195
|
+
let staleDate = Date().addingTimeInterval(max(60, config.liveActivityStaleSeconds))
|
|
196
|
+
let content = ActivityContent(state: initial, staleDate: staleDate, relevanceScore: 100)
|
|
197
|
+
|
|
198
|
+
do {
|
|
199
|
+
let pushType: PushType? = config.liveActivityPushUpdates ? .token : nil
|
|
200
|
+
let activity = try Activity.request(
|
|
201
|
+
attributes: attributes,
|
|
202
|
+
content: content,
|
|
203
|
+
pushType: pushType
|
|
204
|
+
)
|
|
205
|
+
lastError = nil
|
|
206
|
+
UserDefaults.standard.set(trackingId, forKey: Self.trackingIdDefaultsKey)
|
|
207
|
+
persist(activity: activity)
|
|
208
|
+
observePushToken(for: activity)
|
|
209
|
+
log("Started Live Activity \(activity.id), pushUpdates=\(config.liveActivityPushUpdates)")
|
|
210
|
+
} catch {
|
|
211
|
+
let firstError = error.localizedDescription
|
|
212
|
+
guard config.liveActivityPushUpdates else {
|
|
213
|
+
lastError = firstError
|
|
214
|
+
log("Unable to start Live Activity: \(firstError)")
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// A push-enabled request requires the Push Notifications capability.
|
|
219
|
+
// Keep local tracking useful when the host app has not configured it.
|
|
220
|
+
do {
|
|
221
|
+
let activity = try Activity.request(
|
|
222
|
+
attributes: attributes,
|
|
223
|
+
content: content,
|
|
224
|
+
pushType: nil
|
|
225
|
+
)
|
|
226
|
+
lastError = nil
|
|
227
|
+
UserDefaults.standard.set(trackingId, forKey: Self.trackingIdDefaultsKey)
|
|
228
|
+
persist(activity: activity)
|
|
229
|
+
log("Started local Live Activity after push request failed: \(firstError)")
|
|
230
|
+
} catch {
|
|
231
|
+
lastError = error.localizedDescription
|
|
232
|
+
log("Unable to start local Live Activity: \(error.localizedDescription)")
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@available(iOS 16.2, *)
|
|
238
|
+
@MainActor
|
|
239
|
+
private func updateActivities(
|
|
240
|
+
location: CLLocation?,
|
|
241
|
+
isMoving: Bool,
|
|
242
|
+
activity: String,
|
|
243
|
+
locationCount: Int
|
|
244
|
+
) async {
|
|
245
|
+
let activities = Activity<BGLiveTrackingAttributes>.activities
|
|
246
|
+
if activities.isEmpty {
|
|
247
|
+
if UIApplication.shared.applicationState == .active {
|
|
248
|
+
await startOrRecover(isMoving: isMoving)
|
|
249
|
+
}
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let state = makeState(
|
|
254
|
+
location: location,
|
|
255
|
+
isMoving: isMoving,
|
|
256
|
+
activity: activity,
|
|
257
|
+
locationCount: locationCount
|
|
258
|
+
)
|
|
259
|
+
let config = BGConfig.sharedInstance().app
|
|
260
|
+
let content = ActivityContent(
|
|
261
|
+
state: state,
|
|
262
|
+
staleDate: Date().addingTimeInterval(max(60, config.liveActivityStaleSeconds)),
|
|
263
|
+
relevanceScore: isMoving ? 100 : 70
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
for liveActivity in activities {
|
|
267
|
+
persist(activity: liveActivity)
|
|
268
|
+
await liveActivity.update(content)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@available(iOS 16.2, *)
|
|
273
|
+
private func makeState(
|
|
274
|
+
location: CLLocation?,
|
|
275
|
+
isMoving: Bool,
|
|
276
|
+
activity: String,
|
|
277
|
+
locationCount: Int,
|
|
278
|
+
status: String? = nil
|
|
279
|
+
) -> BGLiveTrackingAttributes.ContentState {
|
|
280
|
+
BGLiveTrackingAttributes.ContentState(
|
|
281
|
+
status: status ?? (isMoving ? "Live tracking" : "Stationary"),
|
|
282
|
+
isMoving: isMoving,
|
|
283
|
+
activity: activity.replacingOccurrences(of: "_", with: " "),
|
|
284
|
+
latitude: location?.coordinate.latitude ?? 0,
|
|
285
|
+
longitude: location?.coordinate.longitude ?? 0,
|
|
286
|
+
accuracy: max(0, location?.horizontalAccuracy ?? 0),
|
|
287
|
+
speed: max(0, location?.speed ?? 0),
|
|
288
|
+
distance: BGOdometer.sharedInstance().getOdometer(),
|
|
289
|
+
locationCount: locationCount,
|
|
290
|
+
updatedAt: location?.timestamp ?? Date()
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@available(iOS 16.2, *)
|
|
295
|
+
private func persist(activity: Activity<BGLiveTrackingAttributes>) {
|
|
296
|
+
UserDefaults.standard.set(activity.id, forKey: Self.idDefaultsKey)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@available(iOS 16.2, *)
|
|
300
|
+
private func observePushToken(for activity: Activity<BGLiveTrackingAttributes>) {
|
|
301
|
+
guard BGConfig.sharedInstance().app.liveActivityPushUpdates else { return }
|
|
302
|
+
pushTokenTask?.cancel()
|
|
303
|
+
pushTokenTask = Task {
|
|
304
|
+
for await tokenData in activity.pushTokenUpdates {
|
|
305
|
+
guard !Task.isCancelled else { return }
|
|
306
|
+
let token = tokenData.map { String(format: "%02x", $0) }.joined()
|
|
307
|
+
UserDefaults.standard.set(token, forKey: Self.tokenDefaultsKey)
|
|
308
|
+
BGLog.sharedInstance().notify("Live Activity push token: \(token)", debug: true)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private func clearStoredActivity() {
|
|
314
|
+
UserDefaults.standard.removeObject(forKey: Self.idDefaultsKey)
|
|
315
|
+
UserDefaults.standard.removeObject(forKey: Self.trackingIdDefaultsKey)
|
|
316
|
+
UserDefaults.standard.removeObject(forKey: Self.tokenDefaultsKey)
|
|
317
|
+
stateQueue.async {
|
|
318
|
+
self.locationCount = 0
|
|
319
|
+
self.lastUpdateAt = .distantPast
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private func log(_ message: String) {
|
|
324
|
+
NSLog("[BGGEO][LiveActivity] \(message)")
|
|
325
|
+
BGLog.sharedInstance().notify(message, debug: true)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreLocation
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
@objc public class BGLocation: NSObject {
|
|
6
|
+
|
|
7
|
+
@objc public var location: CLLocation?
|
|
8
|
+
@objc public var uuid: String = UUID().uuidString
|
|
9
|
+
@objc public var event: String = ""
|
|
10
|
+
@objc public var type: String = ""
|
|
11
|
+
@objc public var isMoving: Bool = false
|
|
12
|
+
@objc public var isSample: Bool = false
|
|
13
|
+
@objc public var isHeartbeat: Bool = false
|
|
14
|
+
@objc public var odometer: Double = 0
|
|
15
|
+
@objc public var odometerError: Double = 0
|
|
16
|
+
@objc public var mock: Bool = false
|
|
17
|
+
@objc public var extras: [String: Any]?
|
|
18
|
+
@objc public var config: AnyObject?
|
|
19
|
+
@objc public var geofence: BGGeofence?
|
|
20
|
+
@objc public var geofenceEvent: BGGeofenceEvent?
|
|
21
|
+
@objc public var rawTimestampMs: Int64 = 0
|
|
22
|
+
@objc public var rawRecordedAtMs: Int64 = 0
|
|
23
|
+
|
|
24
|
+
private var batteryLevel: Float = 0
|
|
25
|
+
private var batteryIsCharging: Bool = false
|
|
26
|
+
private var activityType: String = "unknown"
|
|
27
|
+
private var activityConfidence: Int = 0
|
|
28
|
+
private var cachedDictionary: [String: Any]?
|
|
29
|
+
private var _uptime: TimeInterval = 0
|
|
30
|
+
|
|
31
|
+
@objc public class func isValidCoordinate(_ coordinate: CLLocationCoordinate2D) -> Bool {
|
|
32
|
+
return CLLocationCoordinate2DIsValid(coordinate) &&
|
|
33
|
+
coordinate.latitude != 0 &&
|
|
34
|
+
coordinate.longitude != 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@objc public class func uptime() -> TimeInterval {
|
|
38
|
+
return ProcessInfo.processInfo.systemUptime
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@objc public override init() {
|
|
42
|
+
super.init()
|
|
43
|
+
setupDefaultValues()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@objc public init(location: CLLocation) {
|
|
47
|
+
self.location = location
|
|
48
|
+
super.init()
|
|
49
|
+
setupDefaultValues()
|
|
50
|
+
extractLocationData(location)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@objc public init(location: CLLocation, geofence: BGGeofence) {
|
|
54
|
+
self.location = location
|
|
55
|
+
self.geofence = geofence
|
|
56
|
+
super.init()
|
|
57
|
+
setupDefaultValues()
|
|
58
|
+
extractLocationData(location)
|
|
59
|
+
event = "geofence"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@objc public init(location: CLLocation, geofenceEvent: BGGeofenceEvent) {
|
|
63
|
+
self.location = location
|
|
64
|
+
self.geofenceEvent = geofenceEvent
|
|
65
|
+
super.init()
|
|
66
|
+
setupDefaultValues()
|
|
67
|
+
extractLocationData(location)
|
|
68
|
+
event = "geofence"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@objc public init(location: CLLocation, type: String, extras: [String: Any]?) {
|
|
72
|
+
self.location = location
|
|
73
|
+
self.type = type
|
|
74
|
+
self.extras = extras
|
|
75
|
+
super.init()
|
|
76
|
+
setupDefaultValues()
|
|
77
|
+
extractLocationData(location)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@objc public func setupDefaultValues() {
|
|
81
|
+
_uptime = ProcessInfo.processInfo.systemUptime
|
|
82
|
+
updateDeviceStatus()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@objc public func configureWithType(_ type: String, extras: [String: Any]?) {
|
|
86
|
+
self.type = type
|
|
87
|
+
self.extras = extras
|
|
88
|
+
invalidateCache()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@objc public func extractLocationData(_ loc: CLLocation) {
|
|
92
|
+
rawTimestampMs = Int64(loc.timestamp.timeIntervalSince1970 * 1000)
|
|
93
|
+
rawRecordedAtMs = Int64(Date().timeIntervalSince1970 * 1000)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@objc public func updateDeviceStatus() {
|
|
97
|
+
let device = UIDevice.current
|
|
98
|
+
device.isBatteryMonitoringEnabled = true
|
|
99
|
+
batteryLevel = device.batteryLevel
|
|
100
|
+
batteryIsCharging = device.batteryState == .charging || device.batteryState == .full
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@objc public func updateActivityData() {
|
|
104
|
+
invalidateCache()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@objc public func invalidateCache() {
|
|
108
|
+
cachedDictionary = nil
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@objc public func age() -> TimeInterval {
|
|
112
|
+
guard let loc = location else { return 0 }
|
|
113
|
+
return Date().timeIntervalSince(loc.timestamp)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@objc public func timestamp() -> Date {
|
|
117
|
+
return location?.timestamp ?? Date()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@objc public func recordedAt() -> Date {
|
|
121
|
+
if rawRecordedAtMs > 0 {
|
|
122
|
+
return Date(timeIntervalSince1970: Double(rawRecordedAtMs) / 1000.0)
|
|
123
|
+
}
|
|
124
|
+
return Date()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@objc public func isValidCoordinate(_ coord: CLLocationCoordinate2D) -> Bool {
|
|
128
|
+
return BGLocation.isValidCoordinate(coord)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@objc public func toDictionary() -> [String: Any] {
|
|
132
|
+
if let cached = cachedDictionary { return cached }
|
|
133
|
+
let dict = buildDictionary()
|
|
134
|
+
cachedDictionary = dict
|
|
135
|
+
return dict
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@objc public func buildDictionary() -> [String: Any] {
|
|
139
|
+
var dict: [String: Any] = [:]
|
|
140
|
+
dict["uuid"] = uuid
|
|
141
|
+
dict["event"] = event.isEmpty ? NSNull() : event
|
|
142
|
+
dict["is_moving"] = isMoving
|
|
143
|
+
dict["odometer"] = odometer
|
|
144
|
+
|
|
145
|
+
if let loc = location {
|
|
146
|
+
dict["coords"] = buildCoordinatesDictionary()
|
|
147
|
+
dict["timestamp"] = ISO8601DateFormatter().string(from: loc.timestamp)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
dict["activity"] = activityDictionary()
|
|
151
|
+
dict["battery"] = batteryDictionary()
|
|
152
|
+
dict["timestamp_meta"] = timestampMetaDictionary()
|
|
153
|
+
|
|
154
|
+
if mock { dict["mock"] = true }
|
|
155
|
+
if isSample { dict["sample"] = true }
|
|
156
|
+
|
|
157
|
+
if let geofenceEvent = geofenceEvent {
|
|
158
|
+
dict["geofence"] = geofenceTemplateData()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if let extras = extras {
|
|
162
|
+
dict["extras"] = extras
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return dict
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@objc public func buildCoordinatesDictionary() -> [String: Any] {
|
|
169
|
+
guard let loc = location else { return [:] }
|
|
170
|
+
var coords: [String: Any] = [:]
|
|
171
|
+
coords["latitude"] = loc.coordinate.latitude
|
|
172
|
+
coords["longitude"] = loc.coordinate.longitude
|
|
173
|
+
coords["accuracy"] = loc.horizontalAccuracy
|
|
174
|
+
coords["altitude"] = loc.altitude
|
|
175
|
+
coords["altitude_accuracy"] = loc.verticalAccuracy
|
|
176
|
+
coords["heading"] = loc.course
|
|
177
|
+
coords["speed"] = loc.speed
|
|
178
|
+
if let heading = speedAccuracyNumber(loc) { coords["speed_accuracy"] = heading }
|
|
179
|
+
if let headingAccuracy = headingAccuracyNumber(loc) { coords["heading_accuracy"] = headingAccuracy }
|
|
180
|
+
if let ellipsoidal = ellipsoidalAltitudeNumber(loc) { coords["ellipsoidal_altitude"] = ellipsoidal }
|
|
181
|
+
return coords
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@objc public func activityDictionary() -> [String: Any] {
|
|
185
|
+
return ["type": activityType, "confidence": activityConfidence]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@objc public func batteryDictionary() -> [String: Any] {
|
|
189
|
+
return ["level": batteryLevel, "is_charging": batteryIsCharging]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@objc public func timestampMetaDictionary() -> [String: Any] {
|
|
193
|
+
var meta: [String: Any] = [:]
|
|
194
|
+
meta["system_time"] = Date().timeIntervalSince1970 * 1000
|
|
195
|
+
meta["system_clock_elapsed"] = ProcessInfo.processInfo.systemUptime * 1000
|
|
196
|
+
if rawTimestampMs > 0 { meta["event_elapsed"] = Double(rawTimestampMs) }
|
|
197
|
+
if rawRecordedAtMs > 0 { meta["recorded_at"] = Double(rawRecordedAtMs) }
|
|
198
|
+
return meta
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@objc public func timestampMetaJSONString() -> String {
|
|
202
|
+
let dict = timestampMetaDictionary()
|
|
203
|
+
guard let data = try? JSONSerialization.data(withJSONObject: dict) else { return "{}" }
|
|
204
|
+
return String(data: data, encoding: .utf8) ?? "{}"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@objc public func geofenceTemplateData() -> [String: Any] {
|
|
208
|
+
guard let event = geofenceEvent else { return [:] }
|
|
209
|
+
return event.toDictionary()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@objc public func locationTemplateData() -> [String: Any] {
|
|
213
|
+
return toDictionary()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@objc public func combinedGeofenceExtras() -> [String: Any]? {
|
|
217
|
+
var combined: [String: Any] = [:]
|
|
218
|
+
if let e = extras { combined.merge(e) { $1 } }
|
|
219
|
+
if let ge = geofenceEvent?.extras as? [String: Any] { combined.merge(ge) { $1 } }
|
|
220
|
+
return combined.isEmpty ? nil : combined
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@objc public func toJson(_ error: UnsafeMutablePointer<NSError?>?) -> String? {
|
|
224
|
+
let dict = toDictionary()
|
|
225
|
+
guard let data = try? JSONSerialization.data(withJSONObject: dict) else { return nil }
|
|
226
|
+
return String(data: data, encoding: .utf8)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@objc public func serializeClassicFormat(_ error: UnsafeMutablePointer<NSError?>?) -> Data? {
|
|
230
|
+
let dict = toDictionary()
|
|
231
|
+
return try? JSONSerialization.data(withJSONObject: dict)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@objc public func serializeJSONObject(_ error: UnsafeMutablePointer<NSError?>?) -> Data? {
|
|
235
|
+
let dict = toDictionary()
|
|
236
|
+
return try? JSONSerialization.data(withJSONObject: dict)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@objc public func renderTemplate(_ template: BGTemplate, error: UnsafeMutablePointer<NSError?>?) -> String? {
|
|
240
|
+
let data = toDictionary()
|
|
241
|
+
let rendered = template.render(with: data)
|
|
242
|
+
guard let jsonData = try? JSONSerialization.data(withJSONObject: rendered) else { return nil }
|
|
243
|
+
return String(data: jsonData, encoding: .utf8)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@objc public func finalizeTemplateJSON(_ json: String, error: UnsafeMutablePointer<NSError?>?) -> String? {
|
|
247
|
+
return json
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@objc public func handleTemplateError(_ error: Error, template: BGTemplate) {
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@objc public func selectTemplate() -> BGTemplate? {
|
|
254
|
+
return nil
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@objc public func templateName(forTemplate template: BGTemplate) -> String {
|
|
258
|
+
return template.name
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@objc public func processLocation(_ location: CLLocation, error: UnsafeMutablePointer<NSError?>?) -> Bool {
|
|
262
|
+
self.location = location
|
|
263
|
+
extractLocationData(location)
|
|
264
|
+
invalidateCache()
|
|
265
|
+
return true
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@objc public func mergeExtras(_ newExtras: [String: Any]) {
|
|
269
|
+
if extras == nil { extras = [:] }
|
|
270
|
+
extras?.merge(newExtras) { $1 }
|
|
271
|
+
invalidateCache()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@objc public func applyExtras(toDictionary dict: NSMutableDictionary) {
|
|
275
|
+
guard let extras = extras else { return }
|
|
276
|
+
dict["extras"] = extras
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@objc public func applyExtras(toArray array: NSMutableArray) {
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@objc public func applyExtras(toJSONObject obj: NSMutableDictionary) {
|
|
283
|
+
applyExtras(toDictionary: obj)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@objc public func errorWithCode(_ code: Int, description: String) -> NSError {
|
|
287
|
+
return NSError(domain: "BGLocation", code: code, userInfo: [NSLocalizedDescriptionKey: description])
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@objc public func roundValue(_ value: Double) -> Double {
|
|
291
|
+
return (value * 10000).rounded() / 10000
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
func speedAccuracyNumber(_ loc: CLLocation) -> Double? {
|
|
295
|
+
if #available(iOS 13.4, *) {
|
|
296
|
+
return loc.speedAccuracy >= 0 ? loc.speedAccuracy : nil
|
|
297
|
+
}
|
|
298
|
+
return nil
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
func headingAccuracyNumber(_ loc: CLLocation) -> Double? {
|
|
302
|
+
return loc.courseAccuracy >= 0 ? loc.courseAccuracy : nil
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
func ellipsoidalAltitudeNumber(_ loc: CLLocation) -> Double? {
|
|
306
|
+
if #available(iOS 15.0, *) {
|
|
307
|
+
return loc.ellipsoidalAltitude
|
|
308
|
+
}
|
|
309
|
+
return nil
|
|
310
|
+
}
|
|
311
|
+
}
|