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,31 @@
1
+ import Foundation
2
+ import CoreLocation
3
+
4
+ @objc public final class BGHeartbeatEvent: NSObject {
5
+
6
+ @objc public private(set) var location: CLLocation?
7
+ @objc public private(set) var data: [AnyHashable: Any]?
8
+
9
+ @objc public init(location: CLLocation?) {
10
+ super.init()
11
+ guard let location = location else {
12
+ self.location = nil
13
+ self.data = nil
14
+ return
15
+ }
16
+ let rebuilt = CLLocation(coordinate: location.coordinate,
17
+ altitude: location.altitude,
18
+ horizontalAccuracy: location.horizontalAccuracy,
19
+ verticalAccuracy: location.verticalAccuracy,
20
+ course: location.course,
21
+ speed: location.speed,
22
+ timestamp: location.timestamp)
23
+ self.location = rebuilt
24
+ let tsLocation = BGLocation(location: rebuilt, type: "heartbeat", extras: nil)
25
+ self.data = tsLocation.toDictionary()
26
+ }
27
+
28
+ @objc public func toDictionary() -> [String: Any] {
29
+ return ["location": data ?? NSNull()]
30
+ }
31
+ }
@@ -0,0 +1,51 @@
1
+ import Foundation
2
+
3
+ @objc public class BGHeartbeatService: NSObject {
4
+
5
+ private static var _sharedInstance: BGHeartbeatService?
6
+ private static let lock = NSLock()
7
+
8
+ @objc public var timer: Timer?
9
+ @objc public var callback: ((Any?) -> Void)?
10
+
11
+ @objc public class func sharedInstance() -> BGHeartbeatService {
12
+ lock.lock()
13
+ defer { lock.unlock() }
14
+ if _sharedInstance == nil {
15
+ _sharedInstance = BGHeartbeatService()
16
+ }
17
+ return _sharedInstance!
18
+ }
19
+
20
+ @objc public override init() {
21
+ super.init()
22
+ }
23
+
24
+ @objc public var isRunning: Bool {
25
+ return timer != nil && timer!.isValid
26
+ }
27
+
28
+ @objc public func startWithInterval(_ interval: TimeInterval, callback: @escaping (Any?) -> Void) {
29
+ stop()
30
+ self.callback = callback
31
+ timer = Timer.scheduledTimer(timeInterval: interval,
32
+ target: self,
33
+ selector: #selector(onHeartbeat(_:)),
34
+ userInfo: nil,
35
+ repeats: true)
36
+ }
37
+
38
+ @objc public func stop() {
39
+ timer?.invalidate()
40
+ timer = nil
41
+ callback = nil
42
+ }
43
+
44
+ @objc public func evaluate() {
45
+ onHeartbeat(timer)
46
+ }
47
+
48
+ @objc private func onHeartbeat(_ timer: Timer?) {
49
+ callback?(nil)
50
+ }
51
+ }
@@ -0,0 +1,105 @@
1
+ import Foundation
2
+
3
+ @objc public class BGHttpConfig: BGConfigModuleBase {
4
+
5
+ @objc public var url: String = ""
6
+ @objc public var method: String = "POST"
7
+ @objc public var headers: [String: String] = [:]
8
+ @objc public var params: [String: Any] = [:]
9
+ @objc public var rootProperty: String = "location"
10
+ @objc public var timeout: Double = 60.0
11
+ @objc public var autoSync: Bool = true
12
+ @objc public var autoSyncThreshold: Int = 0
13
+ @objc public var batchSync: Bool = false
14
+ @objc public var maxBatchSize: Int = -1
15
+ @objc public var disableAutoSyncOnCellular: Bool = false
16
+
17
+ @objc public override func applyDefaults() {
18
+ url = ""
19
+ method = "POST"
20
+ headers = [:]
21
+ params = [:]
22
+ rootProperty = "location"
23
+ timeout = 60.0
24
+ autoSync = true
25
+ autoSyncThreshold = 0
26
+ batchSync = false
27
+ maxBatchSize = -1
28
+ disableAutoSyncOnCellular = false
29
+ }
30
+
31
+ @objc public var hasValidUrl: Bool {
32
+ return !url.isEmpty && URL(string: url) != nil
33
+ }
34
+
35
+ @objc public var timeoutSeconds: TimeInterval {
36
+ return timeout
37
+ }
38
+
39
+ @objc public var effectiveBatchSize: Int {
40
+ return maxBatchSize > 0 ? maxBatchSize : 50
41
+ }
42
+
43
+ @objc public var isImmediateSyncEnabled: Bool {
44
+ return autoSync && autoSyncThreshold == 0
45
+ }
46
+
47
+ @objc public func fullUrlWithParams() -> String {
48
+ guard !url.isEmpty else { return url }
49
+ if params.isEmpty { return url }
50
+ var components = URLComponents(string: url)
51
+ let queryItems = params.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
52
+ let existing = components?.queryItems ?? []
53
+ components?.queryItems = existing + queryItems
54
+ return components?.url?.absoluteString ?? url
55
+ }
56
+
57
+ @objc public func validateAndCleanUrl(_ rawUrl: String) -> String? {
58
+ let trimmed = rawUrl.trimmingCharacters(in: .whitespaces)
59
+ guard !trimmed.isEmpty, URL(string: trimmed) != nil else { return nil }
60
+ return trimmed
61
+ }
62
+
63
+ @objc public func headersWithAuth(_ auth: BGAuthorization?) -> [String: String] {
64
+ var result = headers
65
+ if let auth = auth, let token = auth.accessToken, !token.isEmpty {
66
+ result["Authorization"] = "Bearer \(token)"
67
+ }
68
+ return result
69
+ }
70
+
71
+ @objc public override func sensitivePropertyNames() -> [String] {
72
+ return ["headers"]
73
+ }
74
+
75
+ @objc public override func deprecatedPropertyMappings() -> [String: String] {
76
+ return [
77
+ "httpRootProperty": "rootProperty",
78
+ "httpTimeout": "timeout"
79
+ ]
80
+ }
81
+
82
+ @objc public override func propertySpecs() -> [BGPropertySpecImpl] {
83
+ return [
84
+ BGPropertySpec(name: "url", type: "string"),
85
+ BGPropertySpec(name: "method", type: "string"),
86
+ BGPropertySpec(name: "headers", type: "object"),
87
+ BGPropertySpec(name: "params", type: "object"),
88
+ BGPropertySpec(name: "rootProperty", type: "string"),
89
+ BGPropertySpec(name: "timeout", type: "double"),
90
+ BGPropertySpec(name: "autoSync", type: "bool"),
91
+ BGPropertySpec(name: "autoSyncThreshold", type: "int"),
92
+ BGPropertySpec(name: "batchSync", type: "bool"),
93
+ BGPropertySpec(name: "maxBatchSize", type: "int"),
94
+ BGPropertySpec(name: "disableAutoSyncOnCellular", type: "bool")
95
+ ]
96
+ }
97
+
98
+ @objc public override func validateConfiguration() -> Bool {
99
+ return true
100
+ }
101
+
102
+ @objc public override var description: String {
103
+ return "<BGHttpConfig url=\(url) method=\(method) autoSync=\(autoSync)>"
104
+ }
105
+ }
@@ -0,0 +1,63 @@
1
+ import Foundation
2
+
3
+ public let BGHttpServiceErrorDomain = "BGHttpServiceErrorDomain"
4
+ public let BGHttpErrorKeyUnderlying = "BGHttpErrorKeyUnderlying"
5
+ public let BGHttpErrorKeyStatus = "BGHttpErrorKeyStatus"
6
+ public let BGHttpErrorKeyResponseBody = "BGHttpErrorKeyResponseBody"
7
+ public let BGHttpErrorKeyURL = "BGHttpErrorKeyURL"
8
+ public let BGHttpErrorKeyFromURL = "BGHttpErrorKeyFromURL"
9
+ public let BGHttpErrorKeyToURL = "BGHttpErrorKeyToURL"
10
+
11
+ @objc public final class BGHttpErrorCodes: NSObject {
12
+
13
+ @objc public class func localizedDescription(forErrorCode code: Int) -> String {
14
+ switch code {
15
+ case 1: return "Invalid URL"
16
+ case 2: return "Network connection failed"
17
+ case 3: return "Sync operation already in progress"
18
+ case 4: return "HTTP response error"
19
+ case 5: return "HTTP redirect disallowed"
20
+ default: return ""
21
+ }
22
+ }
23
+
24
+ @objc public class func error(withCode code: Int, description: String?, userInfo: [AnyHashable: Any]?) -> NSError {
25
+ var info: [String: Any] = [:]
26
+ info[NSLocalizedDescriptionKey] = description ?? localizedDescription(forErrorCode: code)
27
+ if let userInfo = userInfo {
28
+ for (key, value) in userInfo {
29
+ if let key = key as? String { info[key] = value }
30
+ }
31
+ }
32
+ return NSError(domain: BGHttpServiceErrorDomain, code: code, userInfo: info)
33
+ }
34
+
35
+ @objc public class func invalidURLError(_ url: String?) -> NSError {
36
+ let userInfo: [AnyHashable: Any] = [BGHttpErrorKeyURL: url ?? ""]
37
+ return error(withCode: 1, description: localizedDescription(forErrorCode: 1), userInfo: userInfo)
38
+ }
39
+
40
+ @objc public class func noNetworkError() -> NSError {
41
+ return error(withCode: 2, description: localizedDescription(forErrorCode: 2), userInfo: nil)
42
+ }
43
+
44
+ @objc public class func syncInProgressError() -> NSError {
45
+ return error(withCode: 3, description: localizedDescription(forErrorCode: 3), userInfo: nil)
46
+ }
47
+
48
+ @objc public class func responseError(withStatus status: Int, url: String?, bodyBytes: Any?, underlying: Error?) -> NSError {
49
+ var userInfo: [AnyHashable: Any] = [BGHttpErrorKeyStatus: status]
50
+ if let url = url { userInfo[BGHttpErrorKeyURL] = url }
51
+ if let bodyBytes = bodyBytes { userInfo[BGHttpErrorKeyResponseBody] = bodyBytes }
52
+ if let underlying = underlying { userInfo[BGHttpErrorKeyUnderlying] = underlying }
53
+ return error(withCode: 4, description: localizedDescription(forErrorCode: 4), userInfo: userInfo)
54
+ }
55
+
56
+ @objc public class func redirectError(from fromURL: String?, to toURL: String?) -> NSError {
57
+ let userInfo: [AnyHashable: Any] = [
58
+ BGHttpErrorKeyFromURL: fromURL ?? "",
59
+ BGHttpErrorKeyToURL: toURL ?? ""
60
+ ]
61
+ return error(withCode: 5, description: localizedDescription(forErrorCode: 5), userInfo: userInfo)
62
+ }
63
+ }
@@ -0,0 +1,34 @@
1
+ import Foundation
2
+
3
+ @objc public final class BGHttpEvent: NSObject {
4
+
5
+ @objc public private(set) var isSuccess: Bool = false
6
+ @objc public private(set) var statusCode: Int = 0
7
+ @objc public private(set) var requestData: String?
8
+ @objc public private(set) var responseText: String?
9
+ @objc public private(set) var error: Error?
10
+
11
+ @objc public init(statusCode: Int, requestData: String?, responseData: Data?, error: Error?) {
12
+ super.init()
13
+ self.isSuccess = (statusCode >= 200 && statusCode < 205)
14
+ self.statusCode = statusCode
15
+ self.requestData = requestData
16
+ if let responseData = responseData {
17
+ self.responseText = String(data: responseData, encoding: .utf8) ?? ""
18
+ } else {
19
+ self.responseText = ""
20
+ }
21
+ self.error = error
22
+ }
23
+
24
+ @objc public func toDictionary() -> [String: Any] {
25
+ let dict = NSMutableDictionary()
26
+ dict["success"] = isSuccess
27
+ dict["status"] = statusCode
28
+ dict["responseText"] = responseText ?? ""
29
+ if let error = error as NSError? {
30
+ dict["error"] = error.localizedDescription
31
+ }
32
+ return dict as! [String: Any]
33
+ }
34
+ }
@@ -0,0 +1,126 @@
1
+ import Foundation
2
+
3
+ @objc public final class BGHttpRequest: NSObject {
4
+
5
+ @objc public private(set) var records: [Any]
6
+ @objc public var completion: ((BGHttpRequest, BGHttpResponse) -> Void)?
7
+ @objc public private(set) var requestData: Any?
8
+ @objc public private(set) var url: URL?
9
+
10
+ @objc public static func execute(_ records: [Any], callback: @escaping (BGHttpRequest, BGHttpResponse) -> Void) -> BGHttpRequest {
11
+ let request = BGHttpRequest(records: records, callback: callback)
12
+ request.run()
13
+ return request
14
+ }
15
+
16
+ @objc public init(records: [Any], callback: ((BGHttpRequest, BGHttpResponse) -> Void)?) {
17
+ self.records = records
18
+ super.init()
19
+ let urlString = BGConfig.sharedInstance().http.url
20
+ if !urlString.isEmpty {
21
+ self.url = URL(string: urlString)
22
+ }
23
+ self.completion = callback
24
+ }
25
+
26
+ @objc public func run() {
27
+ let config = BGConfig.sharedInstance()
28
+ let http = config.http
29
+
30
+ guard let url = url else { return }
31
+ let request = NSMutableURLRequest(url: url,
32
+ cachePolicy: .reloadIgnoringLocalCacheData,
33
+ timeoutInterval: http.timeoutSeconds)
34
+ request.httpMethod = http.method
35
+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
36
+
37
+ // Build the JSON payload from the records' "location" entries.
38
+ let rootProperty = http.rootProperty
39
+ var payload: Any
40
+ if http.batchSync {
41
+ var array: [Any] = []
42
+ for record in records {
43
+ if let dict = record as? [AnyHashable: Any], let location = dict["location"] {
44
+ array.append(location)
45
+ }
46
+ }
47
+ payload = array
48
+ } else {
49
+ payload = (records.first as? [AnyHashable: Any])?["location"] ?? [:]
50
+ }
51
+
52
+ var requestObject: Any
53
+ if rootProperty == "." {
54
+ requestObject = payload
55
+ } else {
56
+ requestObject = [rootProperty ?? "": payload]
57
+ }
58
+
59
+ // Merge #params (only valid when the payload is an object).
60
+ let params = http.params
61
+ if !params.isEmpty {
62
+ if var dict = requestObject as? [AnyHashable: Any] {
63
+ dict.merge(params) { _, new in new }
64
+ requestObject = dict
65
+ } else {
66
+ let logger = BGLog.sharedInstance()
67
+ if logger.shouldLog(2) {
68
+ let message = "Cannot attach HTTP #params to an HTTP request with JSON data of type [Array], not an {Object]. Specify an #httpRootProperty other than '.' (eg: 'data')"
69
+ logger.log(2, tag: 9, function: "-[BGHttpRequest run]", message: message)
70
+ }
71
+ }
72
+ }
73
+
74
+ // Custom headers.
75
+ let headers = http.headers
76
+ for (key, value) in headers {
77
+ request.setValue(String(format: "%@", "\(value)"), forHTTPHeaderField: "\(key)")
78
+ }
79
+
80
+ // Authorization strategy: apply auth headers if configured.
81
+ if let token = config.authorization.accessToken, !token.isEmpty {
82
+ request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
83
+ }
84
+
85
+ // Serialize body.
86
+ var jsonError: NSError?
87
+ let body: Data?
88
+ do {
89
+ body = try JSONSerialization.data(withJSONObject: requestObject, options: [])
90
+ } catch let error as NSError {
91
+ jsonError = error
92
+ body = nil
93
+ }
94
+
95
+ if let jsonError = jsonError {
96
+ let logger = BGLog.sharedInstance()
97
+ if logger.shouldLog(1) {
98
+ let message = String(format: "JSON error composing HTTP POST data: %@", jsonError.localizedDescription)
99
+ logger.log(1, tag: 0, function: "-[BGHttpRequest run]", message: message)
100
+ }
101
+ let response = BGHttpResponse(status: 200, data: nil, error: jsonError)
102
+ completion?(self, response)
103
+ return
104
+ }
105
+
106
+ guard let body = body else { return }
107
+
108
+ let logger = BGLog.sharedInstance()
109
+ if logger.shouldLog(1) {
110
+ let bodyString = String(data: body, encoding: .utf8) ?? ""
111
+ let message = String(format: "%@", bodyString)
112
+ logger.log(1, tag: 0, function: "-[BGHttpRequest run]", message: message)
113
+ }
114
+
115
+ request.setValue(String(format: "%lu", body.count), forHTTPHeaderField: "Content-Length")
116
+ request.httpBody = body
117
+
118
+ let session = URLSession(configuration: .default)
119
+ let task = session.dataTask(with: request as URLRequest) { [weak self] (data, response, error) in
120
+ guard let self = self else { return }
121
+ let httpResponse = BGHttpResponse(data: data, response: response, error: error)
122
+ self.completion?(self, httpResponse)
123
+ }
124
+ task.resume()
125
+ }
126
+ }
@@ -0,0 +1,93 @@
1
+ import Foundation
2
+
3
+ @objc public final class BGHttpResponse: NSObject {
4
+
5
+ @objc public var error: Error?
6
+ @objc public var data: Data?
7
+ @objc public var response: URLResponse?
8
+ @objc public var status: Int = 0
9
+
10
+ @objc public init(data: Data?, response: URLResponse?, error: Error?) {
11
+ super.init()
12
+ self.data = data
13
+ self.response = response
14
+ self.error = error
15
+ if let httpResponse = response as? HTTPURLResponse {
16
+ self.status = httpResponse.statusCode
17
+ }
18
+ handleResponse()
19
+ }
20
+
21
+ @objc public init(status: Int, data: Data?, error: Error?) {
22
+ super.init()
23
+ self.data = data
24
+ self.response = nil
25
+ self.error = error
26
+ self.status = status
27
+ handleResponse()
28
+ }
29
+
30
+ @objc public func handleResponse() {
31
+ let config = BGConfig.sharedInstance()
32
+ var requestURL: URL? = nil
33
+ let urlString = config.http.url
34
+ if !urlString.isEmpty {
35
+ requestURL = URL(string: urlString)
36
+ }
37
+
38
+ // Redirect detection (301, 302, 307, 308) → flag redirect error.
39
+ if [301, 302, 307, 308].contains(status) {
40
+ let requestUrlString = requestURL?.absoluteString ?? ""
41
+ let responseUrlString = (response as? HTTPURLResponse)?.url?.absoluteString ?? ""
42
+ if !requestUrlString.isEmpty && !responseUrlString.isEmpty {
43
+ self.error = NSError(domain: NSURLErrorDomain, code: status, userInfo: [NSLocalizedDescriptionKey: "Redirect from \(requestUrlString) to \(responseUrlString)"])
44
+ }
45
+ }
46
+
47
+ // NSURLErrorUserCancelledAuthentication (-1012) → treat as 401.
48
+ if let err = error as NSError?, err.code == -1012 {
49
+ self.status = 401
50
+ } else if [200, 201, 204].contains(status) {
51
+ // Success — log and return.
52
+ let logger = BGLog.sharedInstance()
53
+ if logger.shouldLog(3) {
54
+ let message = String(format: "Response: %ld", status)
55
+ logger.log(3, tag: 1, function: "-[BGHttpResponse handleResponse]", message: message)
56
+ }
57
+ return
58
+ } else if status == 410 {
59
+ // Gone — destroy auth token for host if present.
60
+ if let host = requestURL?.host, !host.isEmpty,
61
+ let hostURL = URL(string: "https://\(host)") {
62
+ if TransistorAuthorizationToken.hasToken(forHost: host) {
63
+ TransistorAuthorizationToken.destroy(url: hostURL)
64
+ }
65
+ }
66
+ }
67
+
68
+ // Build a descriptive error for any non-success response.
69
+ var bodyString = ""
70
+ if let data = data {
71
+ bodyString = String(data: data, encoding: .utf8) ?? ""
72
+ }
73
+ var message = String(format: "HTTP ERROR: %ld", status)
74
+ if !bodyString.isEmpty {
75
+ message = message + String(format: "* %@\n", bodyString)
76
+ }
77
+ if let err = error as NSError? {
78
+ message = message + String(format: "* %@\n*\n%@", err.localizedDescription, err.userInfo)
79
+ }
80
+
81
+ let logger = BGLog.sharedInstance()
82
+ if logger.shouldLog(2) {
83
+ let logMessage = String(format: "%@", message)
84
+ logger.log(2, tag: 9, function: "-[BGHttpResponse handleResponse]", message: logMessage)
85
+ }
86
+
87
+ if error == nil {
88
+ self.error = NSError(domain: NSURLErrorDomain,
89
+ code: status,
90
+ userInfo: [NSUnderlyingErrorKey: message])
91
+ }
92
+ }
93
+ }