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,39 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+ react_native_package = Pod::Executable.execute_command(
5
+ "node",
6
+ [
7
+ "-p",
8
+ "require.resolve('react-native/package.json', { paths: [process.argv[1]] })",
9
+ __dir__,
10
+ ]
11
+ ).strip
12
+ require File.join(File.dirname(react_native_package), "scripts/react_native_pods")
13
+
14
+ Pod::Spec.new do |s|
15
+ s.name = "BgGeolocation"
16
+ s.version = package["version"]
17
+ s.summary = package["description"]
18
+ s.homepage = package["homepage"]
19
+ s.license = package["license"]
20
+ s.authors = package["author"]
21
+
22
+ s.platforms = { :ios => "15.1" }
23
+ s.source = { :git => "https://github.com/ZohaibNaseer786/react-native-bg-geolocation.git", :tag => "#{s.version}" }
24
+
25
+ # ObjC++ TurboModule wrapper + all Swift engine files.
26
+ # No binary dependency — the engine is compiled from Swift source directly.
27
+ # The location-push extension's PRINCIPAL class (BGLocationPushService.swift)
28
+ # is excluded from the pod — it belongs only to the
29
+ # CLLocationPushServiceExtension target. The shared constants, socket client,
30
+ # and deliverer ARE compiled into the engine so the in-app background-push
31
+ # handler can deliver natively without React Native.
32
+ s.source_files = "ios/**/*.{h,m,mm,cpp}", "ios/engine/**/*.swift", "ios/liveactivity/**/*.swift", "ios/locationpush/BGLocationPushShared.swift", "ios/locationpush/BGLocationPushSocketClient.swift", "ios/locationpush/BGLocationPushDeliverer.swift"
33
+
34
+ s.libraries = 'sqlite3', 'z', 'stdc++'
35
+ s.frameworks = "CoreLocation", "CoreMotion", "AVFoundation", "AudioToolbox", "MediaPlayer", "UIKit", "CoreData",
36
+ "MessageUI", "UserNotifications", "BackgroundTasks", "ActivityKit"
37
+
38
+ install_modules_dependencies(s)
39
+ end
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zohaib Naseer
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,366 @@
1
+ # react-native-bg-geolocation
2
+
3
+ Background geolocation tracking for React Native in foreground, background, Android kill state, and iOS kill state (server-triggered via the Location Push Service Extension, plus eligible OS-managed relaunches), with motion detection (moving/stationary), geofencing, and pluggable delivery (socket / HTTP / headless).
4
+
5
+ > Educational re-implementation of the `react-native-background-geolocation` API using only public platform APIs (FusedLocationProvider + Activity Recognition on Android, CoreLocation + CoreMotion + BGTaskScheduler on iOS). No proprietary binaries.
6
+
7
+ ## Features
8
+
9
+ - ✅ Foreground / background location tracking with OS relaunch-capable events
10
+ - ✅ Moving ↔ stationary detection (motion state machine + activity recognition)
11
+ - ✅ OS-owned delivery (Android PendingIntent, iOS significant-change + regions)
12
+ - ✅ **iOS Location Push Service Extension** — server-triggered location even when the app is force-quit (APNs `location` push → extension captures + uploads natively)
13
+ - ✅ iOS Live Activity for Lock Screen and Dynamic Island tracking status
14
+ - ✅ Headless JS task (Android) for kill-state JS execution
15
+ - ✅ Pluggable delivery: native socket (Socket.IO) → REST fallback, or your own JS handler
16
+ - ✅ Geofencing (`CLCircularRegion` / `GeofencingClient`)
17
+ - ✅ Reboot persistence
18
+ - ✅ Same API shape as `react-native-background-geolocation` (`AuthorizationStatus`, `DesiredAccuracy`, nested `Config`, etc.)
19
+
20
+ ## Installation
21
+
22
+ ```sh
23
+ npm install react-native-bg-geolocation
24
+ # or
25
+ yarn add react-native-bg-geolocation
26
+ ```
27
+
28
+ ### iOS
29
+
30
+ Add to `Info.plist`:
31
+
32
+ ```xml
33
+ <key>NSLocationWhenInUseUsageDescription</key>
34
+ <string>Used to track your location.</string>
35
+ <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
36
+ <string>Used to track your location in the background.</string>
37
+ <key>NSMotionUsageDescription</key>
38
+ <string>Used to detect movement.</string>
39
+ <key>UIBackgroundModes</key>
40
+ <array>
41
+ <string>location</string>
42
+ <!-- Required only when app.trackingAudioEnabled is approved and enabled. -->
43
+ <string>audio</string>
44
+ <string>fetch</string>
45
+ <string>processing</string>
46
+ <!-- Required for the app-alive background push path (Location Push). -->
47
+ <string>remote-notification</string>
48
+ </array>
49
+ <key>BGTaskSchedulerPermittedIdentifiers</key>
50
+ <array>
51
+ <string>com.bggeolocation.location-refresh</string>
52
+ </array>
53
+ ```
54
+
55
+ For the Location Push Service Extension (kill-state), the app and extension also
56
+ need the `aps-environment` and `com.apple.developer.location.push` entitlements
57
+ and a shared App Group — see [iOS Location Push Service Extension](#ios-location-push-service-extension-kill-state-tracking).
58
+
59
+ Register the BGTask in `AppDelegate` (see the example app's `AppDelegate.swift`).
60
+
61
+ #### iOS Live Activity
62
+
63
+ Live Activities require iOS 16.2 or newer and a WidgetKit extension in the
64
+ host application. The example includes a complete extension in
65
+ `example/ios/BgGeolocationLiveActivity`.
66
+
67
+ Add these keys to the app `Info.plist`:
68
+
69
+ ```xml
70
+ <key>NSSupportsLiveActivities</key>
71
+ <true/>
72
+ <key>NSSupportsLiveActivitiesFrequentUpdates</key>
73
+ <true/>
74
+ ```
75
+
76
+ Add `ios/liveactivity/TSLiveTrackingAttributes.swift` to the WidgetKit
77
+ extension target and create an `ActivityConfiguration` for
78
+ `TSLiveTrackingAttributes`. The package starts, updates, recovers, and ends the
79
+ activity from native iOS location callbacks.
80
+
81
+ #### iOS Tracking Audio
82
+
83
+ For Apple-approved use cases, the package can run a real audible audio session
84
+ beside Core Location while tracking is active:
85
+
86
+ ```ts
87
+ app: {
88
+ trackingAudioEnabled: true,
89
+ trackingAudioVolume: 0.04,
90
+ trackingAudioMixWithOthers: true,
91
+ }
92
+ ```
93
+
94
+ Audio playback has no iOS runtime permission dialog. The host app must obtain
95
+ clear user consent in its own UI before starting it. The app's Start and Stop
96
+ actions control both tracking and audio. The package publishes the approved
97
+ playback session through Now Playing, where Pause or Stop ends both playback and
98
+ tracking. The tracking Live Activity remains a separate status surface. Add
99
+ `audio` to `UIBackgroundModes` and enable this only
100
+ for an audio use case and distribution model Apple has approved. Explicitly
101
+ swiping the app away still terminates audio and location execution.
102
+ Runtime status is available from
103
+ `(await BackgroundGeolocation.getState()).trackingAudio`.
104
+
105
+ #### iOS Location Push Service Extension (kill-state tracking)
106
+
107
+ This is the production mechanism for getting a location from a **force-quit**
108
+ app. Your server sends an APNs push to the device's *location-push token*; iOS
109
+ launches a tiny `CLLocationPushServiceExtension` (a **separate process**, no
110
+ React Native), which grabs one fix and uploads it — then the app is left
111
+ untouched. This is how rideshare / delivery apps keep tracking after the user
112
+ swipes the app away.
113
+
114
+ > **There is no JavaScript in the extension process.** Delivery from a push is
115
+ > done **natively** by the SDK (Socket.IO → REST fallback), because on a
116
+ > kill-state / background wake the RN bridge is not running and JS event
117
+ > listeners would be dropped.
118
+
119
+ **Two push types, two paths** (your server may send either/both; the device
120
+ uses whichever fires):
121
+
122
+ | App state | Push to send | Wakes | Who delivers |
123
+ |---|---|---|---|
124
+ | Alive (fg / backgrounded) | `apns-push-type: background`, `content-available: 1` (standard APNs token) | the app | SDK natively, then fires the `locationpush` JS event for awareness |
125
+ | **Force-quit** | `apns-push-type: location` to `<bundle-id>.location-query` (location-push token) | the **extension** | SDK natively inside the extension |
126
+
127
+ The push payload must carry a `location-query` id (any of `location-query`,
128
+ `locationQuery`, `location_query`, `locationQueryId`, `queryId`). Its presence
129
+ means "report a location now"; it is echoed back in the upload so the server can
130
+ correlate.
131
+
132
+ **1. Request the Apple entitlement** (one-time, ~24–48h):
133
+ <https://developer.apple.com/contact/request/location-push-service-extension>.
134
+ Once approved, add `com.apple.developer.location.push` to the app entitlements.
135
+
136
+ **2. Add an App Group** shared by the app and the extension (e.g.
137
+ `group.<your-bundle-id>`). The SDK writes its delivery config into this suite so
138
+ the separate-process extension can read where/how to upload. Add the same
139
+ identifier to both targets' `Info.plist` files:
140
+
141
+ ```xml
142
+ <key>BGLocationPushAppGroupIdentifier</key>
143
+ <string>group.your.bundle.id</string>
144
+ ```
145
+
146
+ **3. Add a Location Push Service Extension target** whose principal class is the
147
+ SDK's `BGLocationPushService`. The example app ships a ready-made target and a
148
+ script that wires it (`example/ios/add_location_push_target.rb`) — copy that
149
+ target, or run the equivalent for your project. The extension's `Info.plist`:
150
+
151
+ ```xml
152
+ <key>NSExtension</key>
153
+ <dict>
154
+ <key>NSExtensionPointIdentifier</key>
155
+ <string>com.apple.location.push.service</string>
156
+ <key>NSExtensionPrincipalClass</key>
157
+ <string>BGLocationPushService</string>
158
+ </dict>
159
+ ```
160
+
161
+ **4. Register the location-push token in `AppDelegate`** and ship it to your
162
+ server:
163
+
164
+ ```swift
165
+ import CoreLocation
166
+ // keep a strong ref or the manager is deallocated before the callback
167
+ private var locationPushManager: CLLocationManager?
168
+
169
+ if #available(iOS 15.0, *) {
170
+ let mgr = CLLocationManager()
171
+ locationPushManager = mgr
172
+ mgr.startMonitoringLocationPushes { tokenData, error in
173
+ guard let data = tokenData else { return }
174
+ let token = data.map { String(format: "%02hhx", $0) }.joined()
175
+ UserDefaults.standard.set(token, forKey: "BGLocationManager_locationPushToken")
176
+ }
177
+ }
178
+ ```
179
+
180
+ Then in JS, after `ready()`:
181
+
182
+ ```ts
183
+ const token = await BackgroundGeolocation.getLocationPushToken(); // iOS only, else null
184
+ if (token) await myApi.registerLocationPushToken(token); // your endpoint
185
+ ```
186
+
187
+ **5. Configure delivery** (where the extension uploads). Call once after
188
+ `ready()`:
189
+
190
+ ```ts
191
+ await BackgroundGeolocation.setLocationPushConfig({
192
+ socketUrl: 'https://your.server', // Socket.IO base URL (tried first)
193
+ socketPath: '/socket/location',
194
+ socketEvent: 'location:update',
195
+ socketAuthToken: jwt, // sent in the socket CONNECT auth + REST Bearer
196
+ socketTimeout: 8, // seconds before falling back to REST
197
+ fallbackUrl: 'https://your.server/api/location/fallback',
198
+ fcmToken, // available as the {fcmToken} token
199
+ // Custom HTTP headers for the REST fallback.
200
+ headers: { 'Device-Type': 'ios' },
201
+ // OPTIONAL: define the exact upload shape (socket emit + REST fallback).
202
+ // Values may contain {tokens} the SDK substitutes with live values. Omit to
203
+ // use the built-in default body.
204
+ payloadTemplate: {
205
+ lat: '{latitude}', // exact single token → stays numeric
206
+ long: '{longitude}',
207
+ fcm_token: '{fcmToken}',
208
+ device_type: 'ios', // literal value, passed through
209
+ user_current_time: '{userCurrentTime}',
210
+ location_query_id: '{queryId}',
211
+ },
212
+ });
213
+ ```
214
+
215
+ **`payloadTemplate`** lets you ship any payload shape from JS — no native
216
+ patching. Tokens (use inside string values): `{latitude}`/`{lat}`,
217
+ `{longitude}`/`{long}`, `{accuracy}`, `{speed}`, `{heading}`, `{altitude}`,
218
+ `{timestamp}`, `{fcmToken}`, `{queryId}`, `{userCurrentTime}` (local `HH:mm`),
219
+ `{deviceType}`. A value that is exactly one token (`'{latitude}'`) keeps its
220
+ native type (numbers stay numbers); inline tokens (`'v2-{queryId}'`) interpolate
221
+ as strings; nested objects/arrays are supported. Without `payloadTemplate` the
222
+ deliverer posts the default body
223
+ `{ lat, long, fcm_token, device_type: "ios", user_current_time, location_query_id }`
224
+ (applies to both the socket emit and the REST fallback).
225
+
226
+ **6. App-alive handler (optional).** When the app is alive, the SDK still
227
+ delivers natively and emits a `locationpush` event so your JS can react. The
228
+ event carries `delivered: true` when native already sent it — **do not re-send**:
229
+
230
+ ```ts
231
+ BackgroundGeolocation.onLocationPush(async (event) => {
232
+ if (event.delivered) return; // native already uploaded it
233
+ // fallback: deliver event.location yourself, then:
234
+ await BackgroundGeolocation.finishLocationPush(event.requestId);
235
+ });
236
+ ```
237
+
238
+ **7. Server.** Send `apns-push-type: location` to the location-push token with
239
+ topic `<bundle-id>.location-query` (and/or a `background` push to the standard
240
+ APNs token for the app-alive path). Requires "Always" location authorization.
241
+
242
+ Set `app.locationPushEnabled: true` in your config to signal this flow is in use.
243
+
244
+ ### Android
245
+
246
+ Permissions are declared by the library manifest (`ACCESS_FINE_LOCATION`, `ACCESS_BACKGROUND_LOCATION`, `FOREGROUND_SERVICE_LOCATION`, `ACTIVITY_RECOGNITION`, `WAKE_LOCK`, `RECEIVE_BOOT_COMPLETED`). Request the runtime permissions in JS (see the example).
247
+
248
+ ## Usage
249
+
250
+ ```ts
251
+ import BackgroundGeolocation from 'react-native-bg-geolocation';
252
+
253
+ // 1. Configure (nested config, same shape as the original library)
254
+ await BackgroundGeolocation.ready({
255
+ geolocation: {
256
+ desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
257
+ distanceFilter: 10,
258
+ stopTimeout: 5,
259
+ locationAuthorizationRequest: 'Always',
260
+ },
261
+ app: {
262
+ stopOnTerminate: false,
263
+ startOnBoot: true,
264
+ enableHeadless: true,
265
+ heartbeatInterval: 60,
266
+ liveActivityEnabled: true,
267
+ liveActivityTitle: 'Live location',
268
+ liveActivitySubtitle: 'Background tracking is active',
269
+ // Enable only after adding Push Notifications and ActivityKit APNs.
270
+ liveActivityPushUpdates: false,
271
+ trackingAudioEnabled: true,
272
+ trackingAudioVolume: 0.04,
273
+ trackingAudioMixWithOthers: true,
274
+ foregroundService: true,
275
+ notification: { title: 'Tracking', text: 'Location is active' },
276
+ },
277
+ logger: { logLevel: BackgroundGeolocation.LogLevel.Verbose },
278
+ });
279
+
280
+ // 2. Listen
281
+ const sub = BackgroundGeolocation.onLocation(location => {
282
+ console.log('[location]', location.coords.latitude, location.coords.longitude);
283
+ });
284
+ BackgroundGeolocation.onMotionChange(event => {
285
+ console.log('[motionchange]', event.isMoving ? 'moving' : 'stationary');
286
+ });
287
+
288
+ // 3. Request permission + start
289
+ const status = await BackgroundGeolocation.requestPermission();
290
+ if (status === BackgroundGeolocation.AuthorizationStatus.Always) {
291
+ await BackgroundGeolocation.start();
292
+ }
293
+
294
+ // 4. Cleanup
295
+ sub.remove();
296
+ await BackgroundGeolocation.stop();
297
+ ```
298
+
299
+ ### Headless task (Android kill state)
300
+
301
+ Register at module scope (e.g. in `index.js`) so it survives app kill:
302
+
303
+ ```ts
304
+ import BackgroundGeolocation from 'react-native-bg-geolocation';
305
+
306
+ const headlessTask = async (event) => {
307
+ if (event.name === 'location') {
308
+ const { coords } = event.params;
309
+ // POST to your server with fetch(), connect a socket, etc.
310
+ }
311
+ };
312
+
313
+ BackgroundGeolocation.registerHeadlessTask(headlessTask);
314
+ ```
315
+
316
+ ## How kill-state delivery works
317
+
318
+ | Platform | Mechanism |
319
+ |---|---|
320
+ | **Android** | A FusedLocation `PendingIntent` is owned by the OS and delivered to a `BroadcastReceiver` even when the process is dead. A `START_STICKY` foreground service keeps the process priority high; `onTaskRemoved` reschedules it via `AlarmManager`. Kill-state events fire the **HeadlessJsTask**, where your JS runs. |
321
+ | **iOS (OS relaunch)** | Continuous background updates use Core Location. Significant-change and region events can relaunch an app the system terminated. Each native fix is persisted before upload; interrupted HTTP delivery retries on the next wake. After an explicit force-quit, iOS will **not** relaunch via Core Location until the app is reopened. |
322
+ | **iOS (force-quit, push-driven)** | Your server sends an APNs `location` push (topic `<bundle-id>.location-query`) to the device's location-push token. iOS launches the **Location Push Service Extension** — a separate process, even when the app is force-quit — which captures one fix and uploads it natively (Socket.IO → REST fallback). See the [Location Push section](#ios-location-push-service-extension-kill-state-tracking). Requires the Apple `location.push` entitlement, an App Group, and "Always" authorization. |
323
+
324
+ Live Activities do not collect location and do not provide unrestricted
325
+ background execution. Their extension cannot access the network or receive
326
+ location updates. Core Location performs tracking; ActivityKit presents the
327
+ latest state. For updates while the app process is unavailable, read
328
+ `(await BackgroundGeolocation.getState()).liveActivity.pushToken`, send it to
329
+ your server, and update the activity through APNs using the
330
+ `<bundle-id>.push-type.liveactivity` topic.
331
+
332
+ Devices without a Dynamic Island, including iPhone 12, show the Live Activity
333
+ on the Lock Screen rather than persistently in the status bar.
334
+
335
+ ## API
336
+
337
+ - **Lifecycle:** `ready`, `start`, `stop`, `getState`, `setConfig`, `reset`
338
+ - **Location:** `getCurrentPosition`, `watchPosition`, `stopWatchPosition`, `changePace`, `getOdometer`, `setOdometer`, `resetOdometer`
339
+ - **Permissions:** `requestPermission`, `requestTemporaryFullAccuracy`, `getProviderState`
340
+ - **Persistence:** `getLocations`, `getCount`, `destroyLocations`, `insertLocation`, `sync`
341
+ - **Geofencing:** `addGeofence(s)`, `removeGeofence(s)`, `getGeofences`, `getGeofence`, `geofenceExists`
342
+ - **Events:** `onLocation`, `onMotionChange`, `onActivityChange`, `onHeartbeat`, `onProviderChange`, `onGeofence`, `onEnabledChange`, `onHttp`, `onLocationPush`, …
343
+ - **iOS Location Push (kill-state):** `getLocationPushToken`, `getApnsDeviceToken`, `setLocationPushConfig`, `onLocationPush`, `finishLocationPush` — all iOS-only (resolve `null` / no-op on Android)
344
+ - **Enums:** `AuthorizationStatus`, `DesiredAccuracy`, `LogLevel`, `NotificationPriority`, `ActivityType`, `PersistMode`, `AccuracyAuthorization`
345
+
346
+ ## Example
347
+
348
+ The [`example/`](example) app demonstrates app-level tracking, socket delivery, motion detection and a live event log. See `example/src/backgroundLocationService.ts` for the recommended app-level integration pattern.
349
+
350
+ ```sh
351
+ yarn
352
+ yarn example android # or: yarn example ios
353
+ ```
354
+
355
+ ## Contributing
356
+
357
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
358
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
359
+
360
+ ## License
361
+
362
+ MIT
363
+
364
+ ---
365
+
366
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,69 @@
1
+ buildscript {
2
+ ext.BgGeolocation = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return BgGeolocation[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ }
21
+
22
+ dependencies {
23
+ classpath "com.android.tools.build:gradle:8.7.2"
24
+ // noinspection DifferentKotlinGradleVersion
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+
30
+ apply plugin: "com.android.library"
31
+ apply plugin: "kotlin-android"
32
+
33
+ apply plugin: "com.facebook.react"
34
+
35
+ android {
36
+ namespace "com.bggeolocation"
37
+
38
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
39
+
40
+ defaultConfig {
41
+ minSdkVersion getExtOrDefault("minSdkVersion")
42
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
43
+ }
44
+
45
+ buildFeatures {
46
+ buildConfig true
47
+ }
48
+
49
+ buildTypes {
50
+ release {
51
+ minifyEnabled false
52
+ }
53
+ }
54
+
55
+ lint {
56
+ disable "GradleCompatible"
57
+ }
58
+
59
+ compileOptions {
60
+ sourceCompatibility JavaVersion.VERSION_1_8
61
+ targetCompatibility JavaVersion.VERSION_1_8
62
+ }
63
+ }
64
+
65
+ dependencies {
66
+ implementation "com.facebook.react:react-android"
67
+ implementation "com.google.android.gms:play-services-location:21.0.1"
68
+ implementation "com.squareup.okhttp3:okhttp:4.12.0"
69
+ }
@@ -0,0 +1,53 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+
3
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
4
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
5
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
6
+ <uses-permission android:name="android.permission.INTERNET" />
7
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
8
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
9
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
10
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
11
+ <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
12
+ <!-- Required by React Native's HeadlessJsTaskService (kill-state JS execution) -->
13
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
14
+
15
+ <application>
16
+
17
+ <!-- Foreground service — the single active location source. Runs FusedLocation
18
+ with foregroundServiceType=location, so the OS grants continuous fresh GPS
19
+ in the background & after kill (this is what shows the location indicator). -->
20
+ <service
21
+ android:name=".BgGeolocationForegroundService"
22
+ android:foregroundServiceType="location"
23
+ android:exported="false"
24
+ android:stopWithTask="false" />
25
+
26
+ <!-- Boot receiver — restarts tracking after device reboot -->
27
+ <receiver
28
+ android:name=".BgGeolocationBootReceiver"
29
+ android:exported="false">
30
+ <intent-filter>
31
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
32
+ <action android:name="android.intent.action.QUICKBOOT_POWERON" />
33
+ </intent-filter>
34
+ </receiver>
35
+
36
+ <!-- Activity recognition receiver — receives motion activity updates from Play Services -->
37
+ <receiver
38
+ android:name=".BgGeolocationActivityRecognitionReceiver"
39
+ android:exported="false" />
40
+
41
+ <!-- Geofence receiver — receives ENTER/EXIT/DWELL transitions (bg + kill state) -->
42
+ <receiver
43
+ android:name=".BgGeolocationGeofenceReceiver"
44
+ android:exported="false" />
45
+
46
+ <!-- HeadlessTask service — executes JS in a headless context when app is killed -->
47
+ <service
48
+ android:name=".BgGeolocationHeadlessTask"
49
+ android:exported="false" />
50
+
51
+ </application>
52
+
53
+ </manifest>
@@ -0,0 +1,116 @@
1
+ package com.bggeolocation
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.util.Log
7
+ import com.facebook.react.bridge.Arguments
8
+ import com.facebook.react.modules.core.DeviceEventManagerModule
9
+ import com.google.android.gms.location.ActivityRecognitionResult
10
+ import com.google.android.gms.location.DetectedActivity
11
+
12
+ /**
13
+ * Receives activity-recognition updates from Google Play Services.
14
+ *
15
+ * Because the PendingIntent that drives this receiver is owned by the OS, it
16
+ * fires in ALL app states — foreground, background AND after the app is killed.
17
+ *
18
+ * On every update it:
19
+ * 1. Persists the latest {type, confidence, isMoving} to SharedPreferences so
20
+ * the module / foreground-service can stamp it onto every location.
21
+ * 2. Emits a `motionchange` event when the moving<->stationary state flips:
22
+ * - to JS via DeviceEventEmitter when the React context is alive
23
+ * - via the HeadlessTask when the app is killed.
24
+ *
25
+ * Declared in AndroidManifest.xml as a non-exported receiver.
26
+ */
27
+ class BgGeolocationActivityRecognitionReceiver : BroadcastReceiver() {
28
+
29
+ companion object {
30
+ private const val TAG = "BgGeoActivity"
31
+ private const val PREFS = "bg_geolocation_prefs"
32
+ const val KEY_ACTIVITY_TYPE = "activity_type"
33
+ const val KEY_ACTIVITY_CONF = "activity_confidence"
34
+ const val KEY_IS_MOVING = "activity_is_moving"
35
+
36
+ /** Map a Play-Services activity to our string + moving flag. */
37
+ fun describe(type: Int): Pair<String, Boolean> = when (type) {
38
+ DetectedActivity.IN_VEHICLE -> "in_vehicle" to true
39
+ DetectedActivity.ON_BICYCLE -> "on_bicycle" to true
40
+ DetectedActivity.ON_FOOT -> "on_foot" to true
41
+ DetectedActivity.RUNNING -> "running" to true
42
+ DetectedActivity.WALKING -> "walking" to true
43
+ DetectedActivity.STILL -> "still" to false
44
+ DetectedActivity.TILTING -> "tilting" to false
45
+ else -> "unknown" to false
46
+ }
47
+
48
+ fun readActivityType(context: Context): String =
49
+ context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
50
+ .getString(KEY_ACTIVITY_TYPE, "unknown") ?: "unknown"
51
+
52
+ fun readActivityConfidence(context: Context): Int =
53
+ context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
54
+ .getInt(KEY_ACTIVITY_CONF, 0)
55
+
56
+ fun readIsMoving(context: Context): Boolean =
57
+ context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
58
+ .getBoolean(KEY_IS_MOVING, false)
59
+ }
60
+
61
+ override fun onReceive(context: Context, intent: Intent) {
62
+ if (!ActivityRecognitionResult.hasResult(intent)) return
63
+ val result = ActivityRecognitionResult.extractResult(intent) ?: return
64
+
65
+ val mostProbable = result.mostProbableActivity
66
+ val (typeStr, isMoving) = describe(mostProbable.type)
67
+ val confidence = mostProbable.confidence
68
+
69
+ val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
70
+ val wasMoving = prefs.getBoolean(KEY_IS_MOVING, false)
71
+ val hadPrevious = prefs.contains(KEY_IS_MOVING)
72
+
73
+ // Persist latest activity so locations can be stamped with it in any state.
74
+ prefs.edit()
75
+ .putString(KEY_ACTIVITY_TYPE, typeStr)
76
+ .putInt(KEY_ACTIVITY_CONF, confidence)
77
+ .putBoolean(KEY_IS_MOVING, isMoving)
78
+ .apply()
79
+
80
+ Log.d("BgGeoTest", "[ACTIVITY] $typeStr (moving=$isMoving conf=$confidence)")
81
+
82
+ // Only emit motionchange on an actual transition.
83
+ if (hadPrevious && wasMoving == isMoving) return
84
+
85
+ val payload = Arguments.createMap().apply {
86
+ putBoolean("isMoving", isMoving)
87
+ putMap("activity", Arguments.createMap().apply {
88
+ putString("type", typeStr)
89
+ putInt("confidence", confidence)
90
+ })
91
+ }
92
+
93
+ val reactContext = BgGeolocationModule.getReactContext()
94
+
95
+ if (reactContext != null) {
96
+ // App alive — emit straight to JS.
97
+ try {
98
+ reactContext
99
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
100
+ ?.emit("motionchange", payload)
101
+ Log.d("BgGeoTest", "[ACTIVITY] motionchange → JS (isMoving=$isMoving)")
102
+ } catch (e: Exception) {
103
+ Log.w(TAG, "emit motionchange failed: ${e.message}")
104
+ }
105
+ } else {
106
+ // App killed — route through the headless task.
107
+ val headlessMap = Arguments.createMap().apply {
108
+ putBoolean("isMoving", isMoving)
109
+ putString("activityType", typeStr)
110
+ putInt("confidence", confidence)
111
+ }
112
+ BgGeolocationHeadlessTask.onEvent(context.applicationContext, "motionchange", headlessMap)
113
+ Log.d("BgGeoTest", "[ACTIVITY] motionchange → HEADLESS (isMoving=$isMoving)")
114
+ }
115
+ }
116
+ }