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,161 @@
1
+ //
2
+ // BGLocationPushService.swift
3
+ //
4
+ // The principal class of the iOS Location Push Service Extension
5
+ // (CLLocationPushServiceExtension, iOS 15+).
6
+ //
7
+ // ── WHAT THIS IS ──────────────────────────────────────────────────────────
8
+ // When your server sends an APNs push (apns-push-type: location, apns-topic:
9
+ // <bundle-id>.location-query) to the device's location-push token, iOS launches
10
+ // THIS extension in a separate, short-lived process — even if the host app has
11
+ // been force-quit. The extension grabs one location fix and ships it to your
12
+ // server, then signals completion and is torn down.
13
+ //
14
+ // ── DELIVERY ──────────────────────────────────────────────────────────────
15
+ // The push payload carries a `location-query` id. Its presence means "capture
16
+ // and report a location now". We:
17
+ // 1. requestLocation() — one GPS fix.
18
+ // 2. Try Socket.IO (`location:update`) first, if socket config is present.
19
+ // 3. Fall back to REST POST to the configured `url`.
20
+ // The captured location-query id is echoed back so the server can correlate.
21
+ //
22
+ // ── REQUIREMENTS ──────────────────────────────────────────────────────────
23
+ // • Apple-approved `com.apple.developer.location.push` entitlement.
24
+ // • App Group shared with the host app (see BGLocationPushShared).
25
+ // • Host app calls startMonitoringLocationPushes + ships the token to server.
26
+ // • "Always" location authorization.
27
+ //
28
+ // ~30s wall-clock budget. The socket attempt is tightly timed so REST always
29
+ // has room to run.
30
+ //
31
+
32
+ import CoreLocation
33
+ import Foundation
34
+ import os.log
35
+
36
+ @available(iOS 15.0, *)
37
+ @objc(BGLocationPushService)
38
+ public final class BGLocationPushService: NSObject, CLLocationPushServiceExtension {
39
+
40
+ private var completion: (() -> Void)?
41
+ private var manager: CLLocationManager?
42
+ private var fetcher: BGLocationPushFetcher?
43
+
44
+ public override init() {
45
+ super.init()
46
+ }
47
+
48
+ public func didReceiveLocationPushPayload(
49
+ _ payload: [String: Any],
50
+ completion: @escaping () -> Void
51
+ ) {
52
+ BGLocationPushLog.log("didReceiveLocationPushPayload: \(payload)")
53
+ self.completion = completion
54
+
55
+ let queryId = Self.extractLocationQueryId(from: payload)
56
+ guard let queryId = queryId else {
57
+ // No location-query id → nothing to report. Finish cleanly.
58
+ BGLocationPushLog.log("no location-query id in payload — ignoring")
59
+ finish()
60
+ return
61
+ }
62
+ BGLocationPushLog.log("location-query id = \(queryId)")
63
+
64
+ let fetcher = BGLocationPushFetcher(queryId: queryId) { [weak self] in
65
+ self?.finish()
66
+ }
67
+ self.fetcher = fetcher
68
+
69
+ let mgr = CLLocationManager()
70
+ mgr.delegate = fetcher
71
+ mgr.desiredAccuracy = kCLLocationAccuracyBest
72
+ self.manager = mgr
73
+ mgr.requestLocation() // one-shot
74
+ }
75
+
76
+ public func serviceExtensionWillTerminate() {
77
+ BGLocationPushLog.log("serviceExtensionWillTerminate — flushing")
78
+ fetcher?.flushPendingIfNeeded()
79
+ finish()
80
+ }
81
+
82
+ private func finish() {
83
+ guard let completion = completion else { return }
84
+ self.completion = nil
85
+ self.manager = nil
86
+ self.fetcher = nil
87
+ completion()
88
+ }
89
+
90
+ /// Pull the location-query id out of the push payload. Accepts several key
91
+ /// spellings so it survives server-side naming differences.
92
+ static func extractLocationQueryId(from payload: [String: Any]) -> String? {
93
+ let candidates = ["location-query", "locationQuery", "location_query",
94
+ "locationQueryId", "queryId", "query-id"]
95
+ // Top level, then nested under "aps".
96
+ var scopes: [[String: Any]] = [payload]
97
+ if let aps = payload["aps"] as? [String: Any] { scopes.append(aps) }
98
+ for scope in scopes {
99
+ for key in candidates {
100
+ if let value = scope[key] {
101
+ if let s = value as? String, !s.isEmpty { return s }
102
+ if let n = value as? NSNumber { return n.stringValue }
103
+ }
104
+ }
105
+ }
106
+ return nil
107
+ }
108
+ }
109
+
110
+ // MARK: - Location fetcher + delivery
111
+
112
+ @available(iOS 15.0, *)
113
+ final class BGLocationPushFetcher: NSObject, CLLocationManagerDelegate {
114
+
115
+ private let queryId: String
116
+ private let completion: () -> Void
117
+ private var didComplete = false
118
+
119
+ init(queryId: String, completion: @escaping () -> Void) {
120
+ self.queryId = queryId
121
+ self.completion = completion
122
+ }
123
+
124
+ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
125
+ guard !didComplete, let location = locations.last else { return }
126
+ didComplete = true
127
+ BGLocationPushLog.log("got location \(location.coordinate.latitude),\(location.coordinate.longitude)")
128
+ // Delegate to the shared native deliverer (socket → REST), so the
129
+ // extension and the in-app handler use identical delivery logic.
130
+ BGLocationPushDeliverer.deliver(
131
+ latitude: location.coordinate.latitude,
132
+ longitude: location.coordinate.longitude,
133
+ accuracy: max(location.horizontalAccuracy, 0),
134
+ speed: location.speed,
135
+ heading: location.course,
136
+ altitude: location.altitude,
137
+ timestampISO: Self.iso8601(location.timestamp),
138
+ queryId: queryId
139
+ ) { [weak self] _ in
140
+ self?.completion()
141
+ }
142
+ }
143
+
144
+ func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
145
+ guard !didComplete else { return }
146
+ didComplete = true
147
+ BGLocationPushLog.log("CLLocationManager failed: \(error.localizedDescription)")
148
+ completion()
149
+ }
150
+
151
+ func flushPendingIfNeeded() {
152
+ guard !didComplete else { return }
153
+ BGLocationPushLog.log("terminated before location fix arrived")
154
+ }
155
+
156
+ private static func iso8601(_ date: Date) -> String {
157
+ let formatter = ISO8601DateFormatter()
158
+ formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
159
+ return formatter.string(from: date)
160
+ }
161
+ }
@@ -0,0 +1,98 @@
1
+ //
2
+ // BGLocationPushShared.swift
3
+ //
4
+ // Constants shared between the host app (BGLocationManager engine) and the
5
+ // CLLocationPushServiceExtension. Because the extension runs in a SEPARATE
6
+ // process, the only channel between them is an App Group shared UserDefaults
7
+ // suite. Both sides MUST agree on the suite name and the keys below.
8
+ //
9
+ // This file is compiled into BOTH:
10
+ // 1. The `BgGeolocation` pod (so BGLocationManager can WRITE the config)
11
+ // 2. The Location Push Service Extension target (so it can READ the config)
12
+ //
13
+ // ── HOST-APP INTEGRATION ──────────────────────────────────────────────────
14
+ // Consumers of this package must:
15
+ // • Set `BGLocationPushAppGroupIdentifier` in BOTH the host app and extension
16
+ // Info.plist files to the same App Group identifier.
17
+ // • Add that App Group to BOTH the main app target and the extension target.
18
+ //
19
+
20
+ import Foundation
21
+ import os.log
22
+
23
+ // Shared logger for all location-push code (extension + in-app). Compiled into
24
+ // both the pod and the extension target via BGLocationPushShared.swift.
25
+ enum BGLocationPushLog {
26
+ private static let logger = OSLog(subsystem: "com.bggeolocation.locationpush", category: "LocationPush")
27
+ static func log(_ message: String) {
28
+ os_log("%{public}@", log: logger, type: .info, message)
29
+ NSLog("[BGGEO][PushExt] \(message)")
30
+ }
31
+ }
32
+
33
+ @objc public final class BGLocationPushShared: NSObject {
34
+
35
+ /// Safe fallback used by the public example. Production apps should set
36
+ /// `BGLocationPushAppGroupIdentifier` in both target Info.plist files.
37
+ public static let defaultAppGroupIdentifier = "group.com.example.bggeolocation"
38
+ public static let infoPlistAppGroupKey = "BGLocationPushAppGroupIdentifier"
39
+
40
+ // UserDefaults keys written by the host app and read by the extension.
41
+ public static let keyAppGroup = "BGLocationPush_appGroup"
42
+ public static let keyUrl = "BGLocationPush_url"
43
+ public static let keyMethod = "BGLocationPush_method"
44
+ public static let keyHeaders = "BGLocationPush_headers"
45
+ public static let keyParams = "BGLocationPush_params"
46
+ public static let keyExtras = "BGLocationPush_extras"
47
+ public static let keyRootProperty = "BGLocationPush_rootProperty"
48
+ public static let keyAuthorization = "BGLocationPush_authorization"
49
+ public static let keyAccessToken = "BGLocationPush_accessToken"
50
+
51
+ // Socket delivery (preferred channel for the extension). Written by the host
52
+ // app via BackgroundGeolocation.setLocationPushConfig({...}). When socketUrl
53
+ // is present the extension tries Socket.IO first, then falls back to REST.
54
+ public static let keySocketUrl = "BGLocationPush_socketUrl"
55
+ public static let keySocketPath = "BGLocationPush_socketPath"
56
+ public static let keySocketEvent = "BGLocationPush_socketEvent"
57
+ public static let keySocketAuthToken = "BGLocationPush_socketAuthToken"
58
+ public static let keySocketTimeout = "BGLocationPush_socketTimeout"
59
+
60
+ // REST fallback used when the socket fails: POST {latitude, longitude,
61
+ // fcmToken, userCurrentTime} to fallbackUrl.
62
+ public static let keyFallbackUrl = "BGLocationPush_fallbackUrl"
63
+ public static let keyFcmToken = "BGLocationPush_fcmToken"
64
+
65
+ // Host-defined payload shape for the upload (socket + REST fallback). A dict
66
+ // whose values may contain tokens like "{latitude}", "{fcmToken}",
67
+ // "{userCurrentTime}" that the deliverer substitutes with live values. When
68
+ // absent, the deliverer uses its built-in default payload.
69
+ public static let keyPayloadTemplate = "BGLocationPush_payloadTemplate"
70
+
71
+ // The location push token (hex) the host app obtained from
72
+ // `startMonitoringLocationPushesWithCompletion`. Stored in BOTH the standard
73
+ // and the shared suite so JS can read it via getLocationPushToken().
74
+ public static let keyLocationPushToken = "BGLocationManager_locationPushToken"
75
+
76
+ /// Resolve the App Group identifier. Allows the host app to override the
77
+ /// default at runtime by writing `keyAppGroup` into standard UserDefaults
78
+ /// BEFORE the engine syncs config (rarely needed — the default is fine for
79
+ /// most apps).
80
+ @objc public static func resolveAppGroupIdentifier() -> String {
81
+ if let override = UserDefaults.standard.string(forKey: keyAppGroup),
82
+ !override.isEmpty {
83
+ return override
84
+ }
85
+ if let configured = Bundle.main.object(
86
+ forInfoDictionaryKey: infoPlistAppGroupKey
87
+ ) as? String, !configured.isEmpty {
88
+ return configured
89
+ }
90
+ return defaultAppGroupIdentifier
91
+ }
92
+
93
+ /// The shared UserDefaults suite, or nil if the App Group is not configured
94
+ /// (entitlement missing). Callers must handle nil gracefully.
95
+ @objc public static func sharedDefaults() -> UserDefaults? {
96
+ UserDefaults(suiteName: resolveAppGroupIdentifier())
97
+ }
98
+ }
@@ -0,0 +1,198 @@
1
+ //
2
+ // BGLocationPushSocketClient.swift
3
+ //
4
+ // A minimal Socket.IO v4 (Engine.IO v4) client implemented directly on
5
+ // URLSessionWebSocketTask. The Location Push Service Extension runs in a tiny,
6
+ // RN-less Swift process, so it cannot load `socket.io-client` (a JS library).
7
+ // This emits a single `location:update` event with an ack, matching the app's
8
+ // JS socket protocol, then tears the connection down.
9
+ //
10
+ // Protocol (Socket.IO v4 over a single WebSocket):
11
+ // • Connect to wss://<host><path>/?EIO=4&transport=websocket
12
+ // • Server → "0{...}" Engine.IO OPEN (we ignore the body)
13
+ // • Client → "40{auth-json}" Socket.IO CONNECT to namespace "/" with auth
14
+ // • Server → "40{\"sid\":...}" CONNECT ack (success) | "44{...}" connect_error
15
+ // • Client → "420[\"event\",payload]" MESSAGE + EVENT with ack id 0
16
+ // • Server → "430[ackPayload]" ack for id 0
17
+ // • Engine.IO PING "2" → we reply PONG "3"
18
+ //
19
+ // Everything is bounded by `timeout` so we never blow the extension budget.
20
+ // Extension-safe: only Foundation + URLSession.
21
+ //
22
+
23
+ import Foundation
24
+
25
+ @available(iOS 15.0, *)
26
+ final class BGLocationPushSocketClient: NSObject {
27
+
28
+ struct Config {
29
+ let url: URL // base, e.g. https://host (ws/http both accepted)
30
+ let path: String // socket.io path, e.g. /socket/location
31
+ let event: String // event name, e.g. location:update
32
+ let authToken: String? // sent in the CONNECT auth payload as {"token": ...}
33
+ let timeout: TimeInterval
34
+ }
35
+
36
+ private let config: Config
37
+ private var task: URLSessionWebSocketTask?
38
+ private var session: URLSession?
39
+ private var completion: ((Bool) -> Void)?
40
+ private var didFinish = false
41
+ private var connectAcked = false
42
+ private var payload: [String: Any] = [:]
43
+ private var timeoutWorkItem: DispatchWorkItem?
44
+
45
+ init(config: Config) {
46
+ self.config = config
47
+ }
48
+
49
+ /// Emit one event with `payload` and resolve `true` once the server acks it,
50
+ /// or `false` on any failure / timeout.
51
+ func emit(_ payload: [String: Any], completion: @escaping (Bool) -> Void) {
52
+ self.payload = payload
53
+ self.completion = completion
54
+
55
+ guard let wsURL = makeWebSocketURL() else {
56
+ finish(false)
57
+ return
58
+ }
59
+
60
+ let session = URLSession(configuration: .ephemeral)
61
+ self.session = session
62
+ let task = session.webSocketTask(with: wsURL)
63
+ self.task = task
64
+
65
+ let deadline = DispatchWorkItem { [weak self] in
66
+ BGLocationPushLog.log("socket timed out")
67
+ self?.finish(false)
68
+ }
69
+ timeoutWorkItem = deadline
70
+ DispatchQueue.global().asyncAfter(deadline: .now() + config.timeout, execute: deadline)
71
+
72
+ task.resume()
73
+ receiveLoop()
74
+ }
75
+
76
+ // MARK: - URL
77
+
78
+ private func makeWebSocketURL() -> URL? {
79
+ guard var components = URLComponents(url: config.url, resolvingAgainstBaseURL: false) else {
80
+ return nil
81
+ }
82
+ switch components.scheme {
83
+ case "https", "wss": components.scheme = "wss"
84
+ default: components.scheme = "ws"
85
+ }
86
+ // socket.io expects the engine.io handshake at <path>/ with these query items.
87
+ var path = config.path
88
+ if !path.hasPrefix("/") { path = "/" + path }
89
+ if !path.hasSuffix("/") { path += "/" }
90
+ components.path = path
91
+ components.queryItems = [
92
+ URLQueryItem(name: "EIO", value: "4"),
93
+ URLQueryItem(name: "transport", value: "websocket")
94
+ ]
95
+ return components.url
96
+ }
97
+
98
+ // MARK: - Receive loop
99
+
100
+ private func receiveLoop() {
101
+ task?.receive { [weak self] result in
102
+ guard let self = self, !self.didFinish else { return }
103
+ switch result {
104
+ case .failure(let error):
105
+ BGLocationPushLog.log("socket receive error: \(error.localizedDescription)")
106
+ self.finish(false)
107
+ case .success(let message):
108
+ switch message {
109
+ case .string(let text): self.handle(text)
110
+ case .data(let data): self.handle(String(decoding: data, as: UTF8.self))
111
+ @unknown default: break
112
+ }
113
+ if !self.didFinish { self.receiveLoop() }
114
+ }
115
+ }
116
+ }
117
+
118
+ private func handle(_ text: String) {
119
+ guard let first = text.first else { return }
120
+ switch first {
121
+ case "0": // Engine.IO OPEN → send Socket.IO CONNECT with auth
122
+ sendConnect()
123
+ case "2": // Engine.IO PING → PONG
124
+ send("3")
125
+ case "4": // Engine.IO MESSAGE → inspect Socket.IO packet type (2nd char)
126
+ handleSocketIO(text)
127
+ default:
128
+ break
129
+ }
130
+ }
131
+
132
+ private func handleSocketIO(_ text: String) {
133
+ // text[0] == "4" (MESSAGE). text[1] == Socket.IO packet type.
134
+ let idx = text.index(text.startIndex, offsetBy: 1)
135
+ guard idx < text.endIndex else { return }
136
+ let type = text[idx]
137
+ switch type {
138
+ case "0": // CONNECT success → now emit the event
139
+ connectAcked = true
140
+ sendEvent()
141
+ case "3": // ACK for our emit → success
142
+ BGLocationPushLog.log("socket ack received")
143
+ finish(true)
144
+ case "4": // CONNECT_ERROR
145
+ BGLocationPushLog.log("socket connect_error: \(text)")
146
+ finish(false)
147
+ default:
148
+ break
149
+ }
150
+ }
151
+
152
+ // MARK: - Send
153
+
154
+ private func sendConnect() {
155
+ var frame = "40"
156
+ if let token = config.authToken, !token.isEmpty,
157
+ let data = try? JSONSerialization.data(withJSONObject: ["token": token]),
158
+ let json = String(data: data, encoding: .utf8) {
159
+ frame += json
160
+ }
161
+ send(frame)
162
+ }
163
+
164
+ private func sendEvent() {
165
+ guard let data = try? JSONSerialization.data(withJSONObject: [config.event, payload]),
166
+ let json = String(data: data, encoding: .utf8) else {
167
+ finish(false)
168
+ return
169
+ }
170
+ // "42" = MESSAGE+EVENT, "0" = ack id we expect echoed back as "430[...]".
171
+ send("420" + json)
172
+ }
173
+
174
+ private func send(_ frame: String) {
175
+ task?.send(.string(frame)) { [weak self] error in
176
+ if let error = error {
177
+ BGLocationPushLog.log("socket send error: \(error.localizedDescription)")
178
+ self?.finish(false)
179
+ }
180
+ }
181
+ }
182
+
183
+ // MARK: - Teardown
184
+
185
+ private func finish(_ success: Bool) {
186
+ guard !didFinish else { return }
187
+ didFinish = true
188
+ timeoutWorkItem?.cancel()
189
+ timeoutWorkItem = nil
190
+ task?.cancel(with: .goingAway, reason: nil)
191
+ task = nil
192
+ session?.invalidateAndCancel()
193
+ session = nil
194
+ let cb = completion
195
+ completion = nil
196
+ cb?(success)
197
+ }
198
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('BgGeolocation');
5
+ //# sourceMappingURL=NativeBgGeolocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeBgGeolocation.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AA2OpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,eAAe,CAAC","ignoreList":[]}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ export const BOOT = 'boot';
4
+ export const TERMINATE = 'terminate';
5
+ export const LOCATION = 'location';
6
+ export const HTTP = 'http';
7
+ export const MOTIONCHANGE = 'motionchange';
8
+ export const PROVIDERCHANGE = 'providerchange';
9
+ export const HEARTBEAT = 'heartbeat';
10
+ export const ACTIVITYCHANGE = 'activitychange';
11
+ export const GEOFENCE = 'geofence';
12
+ export const GEOFENCESCHANGE = 'geofenceschange';
13
+ export const SCHEDULE = 'schedule';
14
+ export const CONNECTIVITYCHANGE = 'connectivitychange';
15
+ export const ENABLEDCHANGE = 'enabledchange';
16
+ export const POWERSAVECHANGE = 'powersavechange';
17
+ export const NOTIFICATIONACTION = 'notificationaction';
18
+ export const AUTHORIZATION = 'authorization';
19
+ export const LOCATIONPUSH = 'locationpush';
20
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["BOOT","TERMINATE","LOCATION","HTTP","MOTIONCHANGE","PROVIDERCHANGE","HEARTBEAT","ACTIVITYCHANGE","GEOFENCE","GEOFENCESCHANGE","SCHEDULE","CONNECTIVITYCHANGE","ENABLEDCHANGE","POWERSAVECHANGE","NOTIFICATIONACTION","AUTHORIZATION","LOCATIONPUSH"],"sourceRoot":"../../src","sources":["events.ts"],"mappings":";;AAAA,OAAO,MAAMA,IAAI,GAAG,MAAM;AAC1B,OAAO,MAAMC,SAAS,GAAG,WAAW;AACpC,OAAO,MAAMC,QAAQ,GAAG,UAAU;AAClC,OAAO,MAAMC,IAAI,GAAG,MAAM;AAC1B,OAAO,MAAMC,YAAY,GAAG,cAAc;AAC1C,OAAO,MAAMC,cAAc,GAAG,gBAAgB;AAC9C,OAAO,MAAMC,SAAS,GAAG,WAAW;AACpC,OAAO,MAAMC,cAAc,GAAG,gBAAgB;AAC9C,OAAO,MAAMC,QAAQ,GAAG,UAAU;AAClC,OAAO,MAAMC,eAAe,GAAG,iBAAiB;AAChD,OAAO,MAAMC,QAAQ,GAAG,UAAU;AAClC,OAAO,MAAMC,kBAAkB,GAAG,oBAAoB;AACtD,OAAO,MAAMC,aAAa,GAAG,eAAe;AAC5C,OAAO,MAAMC,eAAe,GAAG,iBAAiB;AAChD,OAAO,MAAMC,kBAAkB,GAAG,oBAAoB;AACtD,OAAO,MAAMC,aAAa,GAAG,eAAe;AAC5C,OAAO,MAAMC,YAAY,GAAG,cAAc","ignoreList":[]}