react-native-nitro-location-tracking 0.1.10 → 0.1.12

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 (23) hide show
  1. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/MockLocationMonitor.kt +145 -0
  2. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NitroLocationTracking.kt +10 -0
  3. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/PermissionStatusMonitor.kt +16 -9
  4. package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/ProviderStatusMonitor.kt +87 -23
  5. package/ios/LocationEngine.swift +53 -16
  6. package/ios/MockLocationMonitor.swift +120 -0
  7. package/ios/NitroLocationTracking.swift +8 -0
  8. package/lib/module/index.js.map +1 -1
  9. package/lib/typescript/src/NitroLocationTracking.nitro.d.ts +2 -0
  10. package/lib/typescript/src/NitroLocationTracking.nitro.d.ts.map +1 -1
  11. package/lib/typescript/src/index.d.ts +1 -1
  12. package/lib/typescript/src/index.d.ts.map +1 -1
  13. package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.cpp +4 -0
  14. package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.hpp +1 -0
  15. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/HybridNitroLocationTrackingSpec.kt +9 -0
  16. package/nitrogen/generated/ios/c++/HybridNitroLocationTrackingSpecSwift.hpp +6 -0
  17. package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec.swift +1 -0
  18. package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec_cxx.swift +16 -0
  19. package/nitrogen/generated/shared/c++/HybridNitroLocationTrackingSpec.cpp +1 -0
  20. package/nitrogen/generated/shared/c++/HybridNitroLocationTrackingSpec.hpp +1 -0
  21. package/package.json +1 -1
  22. package/src/NitroLocationTracking.nitro.ts +2 -0
  23. package/src/index.tsx +1 -0
@@ -0,0 +1,145 @@
1
+ package com.margelo.nitro.nitrolocationtracking
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.AppOpsManager
5
+ import android.content.Context
6
+ import android.os.Build
7
+ import android.os.Handler
8
+ import android.os.Looper
9
+ import android.provider.Settings
10
+ import android.util.Log
11
+
12
+ /**
13
+ * Periodically polls whether a mock/fake GPS provider is active on the device.
14
+ * This works independently of location tracking — it detects the presence of
15
+ * mock location apps (e.g. Fake GPS, Mock Locations) at the system level.
16
+ *
17
+ * On each poll it checks:
18
+ * - Pre-API 23: Settings.Secure.ALLOW_MOCK_LOCATION
19
+ * - API 23+: AppOpsManager OP_MOCK_LOCATION (op code 58)
20
+ * - Also checks all installed packages for mock location permission
21
+ *
22
+ * The callback fires only when the state changes (deduplicated).
23
+ */
24
+ class MockLocationMonitor(private val context: Context) {
25
+
26
+ companion object {
27
+ private const val TAG = "MockLocationMonitor"
28
+ /** Default poll interval in milliseconds */
29
+ private const val DEFAULT_POLL_INTERVAL_MS = 3000L
30
+ }
31
+
32
+ private val mainHandler = Handler(Looper.getMainLooper())
33
+ private var callback: ((Boolean) -> Unit)? = null
34
+ private var lastState: Boolean? = null
35
+ private var polling = false
36
+ private var pollIntervalMs = DEFAULT_POLL_INTERVAL_MS
37
+
38
+ private val pollRunnable = object : Runnable {
39
+ override fun run() {
40
+ if (!polling) return
41
+ checkAndNotify()
42
+ mainHandler.postDelayed(this, pollIntervalMs)
43
+ }
44
+ }
45
+
46
+ fun setCallback(callback: (Boolean) -> Unit) {
47
+ this.callback = callback
48
+ // Emit current state immediately
49
+ val current = isMockLocationActive()
50
+ lastState = current
51
+ callback.invoke(current)
52
+ // Start polling
53
+ startPolling()
54
+ }
55
+
56
+ fun startPolling() {
57
+ if (polling) return
58
+ polling = true
59
+ mainHandler.postDelayed(pollRunnable, pollIntervalMs)
60
+ Log.d(TAG, "Mock location polling started (interval=${pollIntervalMs}ms)")
61
+ }
62
+
63
+ fun stopPolling() {
64
+ polling = false
65
+ mainHandler.removeCallbacks(pollRunnable)
66
+ Log.d(TAG, "Mock location polling stopped")
67
+ }
68
+
69
+ private fun checkAndNotify() {
70
+ val current = isMockLocationActive()
71
+ if (current != lastState) {
72
+ lastState = current
73
+ Log.d(TAG, "Mock location state changed: isMockEnabled=$current")
74
+ callback?.invoke(current)
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Comprehensive check for mock location activity on the device.
80
+ */
81
+ @SuppressLint("DiscouragedPrivateApi")
82
+ fun isMockLocationActive(): Boolean {
83
+ // Pre-API 23: check the global mock location setting
84
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
85
+ @Suppress("DEPRECATION")
86
+ val mockSetting = Settings.Secure.getString(
87
+ context.contentResolver,
88
+ Settings.Secure.ALLOW_MOCK_LOCATION
89
+ )
90
+ return mockSetting == "1"
91
+ }
92
+
93
+ // API 23+: check if any app holds MOCK_LOCATION permission via AppOpsManager
94
+ try {
95
+ val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
96
+ val opMockLocation = 58 // OP_MOCK_LOCATION
97
+ val method = AppOpsManager::class.java.getMethod(
98
+ "checkOp",
99
+ Int::class.javaPrimitiveType,
100
+ Int::class.javaPrimitiveType,
101
+ String::class.java
102
+ )
103
+ val result = method.invoke(
104
+ appOps, opMockLocation, android.os.Process.myUid(), context.packageName
105
+ ) as Int
106
+ if (result == AppOpsManager.MODE_ALLOWED) return true
107
+ } catch (e: Exception) {
108
+ Log.w(TAG, "Could not check mock location app ops: ${e.message}")
109
+ }
110
+
111
+ // Additional check: scan installed packages for known mock location apps
112
+ try {
113
+ val pm = context.packageManager
114
+ val knownMockApps = listOf(
115
+ "com.lexa.fakegps",
116
+ "com.incorporateapps.fakegps.fre",
117
+ "com.fakegps.mock",
118
+ "com.lkr.fakegps",
119
+ "com.fake.gps.go.location.spoofer.free",
120
+ "com.theappninjas.gpsjoystick",
121
+ "com.evezzon.fgl",
122
+ "com.mock.location"
123
+ )
124
+ for (pkg in knownMockApps) {
125
+ try {
126
+ pm.getApplicationInfo(pkg, 0)
127
+ Log.d(TAG, "Known mock location app detected: $pkg")
128
+ return true
129
+ } catch (_: Exception) {
130
+ // Not installed
131
+ }
132
+ }
133
+ } catch (e: Exception) {
134
+ Log.w(TAG, "Could not check for mock location apps: ${e.message}")
135
+ }
136
+
137
+ return false
138
+ }
139
+
140
+ fun destroy() {
141
+ stopPolling()
142
+ callback = null
143
+ lastState = null
144
+ }
145
+ }
@@ -27,6 +27,7 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
27
27
  private var geofenceManager: GeofenceManager? = null
28
28
  private var providerStatusMonitor: ProviderStatusMonitor? = null
29
29
  private var permissionStatusMonitor: PermissionStatusMonitor? = null
30
+ private var mockLocationMonitor: MockLocationMonitor? = null
30
31
 
31
32
  private var locationCallback: ((LocationData) -> Unit)? = null
32
33
  private var motionCallback: ((Boolean) -> Unit)? = null
@@ -36,6 +37,7 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
36
37
  private var speedAlertCallback: ((SpeedAlertType, Double) -> Unit)? = null
37
38
  private var providerStatusCallback: ((LocationProviderStatus, LocationProviderStatus) -> Unit)? = null
38
39
  private var permissionStatusCallback: ((PermissionStatus) -> Unit)? = null
40
+ private var mockLocationCallback: ((Boolean) -> Unit)? = null
39
41
 
40
42
  private var locationConfig: LocationConfig? = null
41
43
 
@@ -53,6 +55,7 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
53
55
  geofenceManager = GeofenceManager(context)
54
56
  providerStatusMonitor = ProviderStatusMonitor(context)
55
57
  permissionStatusMonitor = PermissionStatusMonitor(context)
58
+ mockLocationMonitor = MockLocationMonitor(context)
56
59
  locationEngine?.dbWriter = dbWriter
57
60
  connectionManager.dbWriter = dbWriter
58
61
  Log.d(TAG, "Components initialized successfully")
@@ -186,6 +189,12 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
186
189
  locationEngine?.rejectMockLocations = reject
187
190
  }
188
191
 
192
+ override fun onMockLocationDetected(callback: (isMockEnabled: Boolean) -> Unit) {
193
+ mockLocationCallback = callback
194
+ ensureInitialized()
195
+ mockLocationMonitor?.setCallback(callback)
196
+ }
197
+
189
198
  // === Geofencing ===
190
199
 
191
200
  override fun addGeofence(region: GeofenceRegion) {
@@ -389,5 +398,6 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
389
398
  geofenceManager?.destroy()
390
399
  providerStatusMonitor?.destroy()
391
400
  permissionStatusMonitor?.destroy()
401
+ mockLocationMonitor?.destroy()
392
402
  }
393
403
  }
@@ -3,6 +3,8 @@ package com.margelo.nitro.nitrolocationtracking
3
3
  import android.content.Context
4
4
  import android.content.pm.PackageManager
5
5
  import android.os.Build
6
+ import android.os.Handler
7
+ import android.os.Looper
6
8
  import android.util.Log
7
9
  import androidx.core.content.ContextCompat
8
10
  import androidx.lifecycle.DefaultLifecycleObserver
@@ -24,6 +26,7 @@ class PermissionStatusMonitor(private val context: Context) {
24
26
 
25
27
  private var callback: ((PermissionStatus) -> Unit)? = null
26
28
  private var lastStatus: PermissionStatus? = null
29
+ private val mainHandler = Handler(Looper.getMainLooper())
27
30
 
28
31
  private val lifecycleObserver = object : DefaultLifecycleObserver {
29
32
  override fun onStart(owner: LifecycleOwner) {
@@ -36,12 +39,14 @@ class PermissionStatusMonitor(private val context: Context) {
36
39
  // Capture the current status so we only fire on actual changes
37
40
  lastStatus = getCurrentPermissionStatus()
38
41
 
39
- // Observe the process lifecycle (app foreground / background)
40
- try {
41
- ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
42
- Log.d(TAG, "Registered lifecycle observer for permission changes")
43
- } catch (e: Exception) {
44
- Log.e(TAG, "Failed to register lifecycle observer: ${e.message}")
42
+ // addObserver MUST be called on the main thread
43
+ mainHandler.post {
44
+ try {
45
+ ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
46
+ Log.d(TAG, "Registered lifecycle observer for permission changes")
47
+ } catch (e: Exception) {
48
+ Log.e(TAG, "Failed to register lifecycle observer: ${e.message}")
49
+ }
45
50
  }
46
51
  }
47
52
 
@@ -76,9 +81,11 @@ class PermissionStatusMonitor(private val context: Context) {
76
81
  }
77
82
 
78
83
  fun destroy() {
79
- try {
80
- ProcessLifecycleOwner.get().lifecycle.removeObserver(lifecycleObserver)
81
- } catch (_: Exception) {}
84
+ mainHandler.post {
85
+ try {
86
+ ProcessLifecycleOwner.get().lifecycle.removeObserver(lifecycleObserver)
87
+ } catch (_: Exception) {}
88
+ }
82
89
  callback = null
83
90
  lastStatus = null
84
91
  }
@@ -1,27 +1,54 @@
1
1
  package com.margelo.nitro.nitrolocationtracking
2
2
 
3
- import android.content.BroadcastReceiver
4
3
  import android.content.Context
5
- import android.content.Intent
6
- import android.content.IntentFilter
4
+ import android.database.ContentObserver
7
5
  import android.location.LocationManager
8
6
  import android.os.Build
7
+ import android.os.Handler
8
+ import android.os.Looper
9
+ import android.provider.Settings
9
10
  import android.util.Log
10
11
 
12
+ /**
13
+ * Monitors GPS / network location provider status changes using a ContentObserver
14
+ * on Settings.Secure.LOCATION_MODE.
15
+ *
16
+ * Why ContentObserver instead of BroadcastReceiver?
17
+ * Several OEMs (notably Samsung) suppress PROVIDERS_CHANGED_ACTION broadcasts,
18
+ * making BroadcastReceiver unreliable. ContentObserver watches the Settings DB
19
+ * directly and fires on all devices.
20
+ *
21
+ * Implementation notes:
22
+ * - ContentObserver.onChange fires BEFORE the LocationManager reflects the new
23
+ * state, so we post a short delayed read (150 ms) to get the correct values.
24
+ * - onChange can fire multiple times per toggle; we debounce with a pending
25
+ * Runnable and deduplicate by comparing with lastGps / lastNetwork.
26
+ */
11
27
  class ProviderStatusMonitor(private val context: Context) {
12
28
 
13
29
  companion object {
14
30
  private const val TAG = "ProviderStatusMonitor"
31
+ /** Delay (ms) to let the system apply the setting before we read it. */
32
+ private const val READ_DELAY_MS = 150L
15
33
  }
16
34
 
17
35
  private val locationManager =
18
36
  context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
37
+ private val mainHandler = Handler(Looper.getMainLooper())
38
+
19
39
  private var callback: ((LocationProviderStatus, LocationProviderStatus) -> Unit)? = null
20
- private var receiver: BroadcastReceiver? = null
40
+ private var contentObserver: ContentObserver? = null
41
+ private var pendingNotify: Runnable? = null
42
+
43
+ // Track last-notified values to avoid duplicate callbacks
44
+ private var lastGps: LocationProviderStatus? = null
45
+ private var lastNetwork: LocationProviderStatus? = null
21
46
 
22
47
  fun setCallback(callback: (LocationProviderStatus, LocationProviderStatus) -> Unit) {
23
48
  this.callback = callback
24
- registerReceiver()
49
+ registerObserver()
50
+ // Emit current status immediately (no delay needed — values are already settled)
51
+ emitCurrentStatus()
25
52
  }
26
53
 
27
54
  fun isLocationServicesEnabled(): Boolean {
@@ -33,41 +60,78 @@ class ProviderStatusMonitor(private val context: Context) {
33
60
  }
34
61
  }
35
62
 
36
- private fun registerReceiver() {
37
- if (receiver != null) return
63
+ // ── Observer registration ────────────────────────────────────────────
38
64
 
39
- receiver = object : BroadcastReceiver() {
40
- override fun onReceive(context: Context?, intent: Intent?) {
41
- if (intent?.action == LocationManager.PROVIDERS_CHANGED_ACTION) {
42
- notifyStatus()
43
- }
65
+ private fun registerObserver() {
66
+ if (contentObserver != null) return
67
+
68
+ contentObserver = object : ContentObserver(mainHandler) {
69
+ override fun onChange(selfChange: Boolean) {
70
+ super.onChange(selfChange)
71
+ scheduleNotify()
44
72
  }
45
73
  }
46
74
 
47
- val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
48
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
49
- context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
50
- } else {
51
- context.registerReceiver(receiver, filter)
52
- }
53
- Log.d(TAG, "Registered provider status receiver")
75
+ // LOCATION_MODE is the single authoritative setting for the global
76
+ // location toggle (GPS + Network). Watching only this avoids the
77
+ // double-fire that occurs when observing LOCATION_PROVIDERS_ALLOWED too.
78
+ context.contentResolver.registerContentObserver(
79
+ Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE),
80
+ false,
81
+ contentObserver!!
82
+ )
83
+
84
+ Log.d(TAG, "ContentObserver registered")
85
+ }
86
+
87
+ // ── Debounced + delayed notification ─────────────────────────────────
88
+
89
+ /**
90
+ * Schedule a delayed read of the provider status.
91
+ * If onChange fires multiple times in quick succession, the previous
92
+ * pending Runnable is cancelled so we only read once after things settle.
93
+ */
94
+ private fun scheduleNotify() {
95
+ pendingNotify?.let { mainHandler.removeCallbacks(it) }
96
+
97
+ val runnable = Runnable { emitCurrentStatus() }
98
+ pendingNotify = runnable
99
+ mainHandler.postDelayed(runnable, READ_DELAY_MS)
54
100
  }
55
101
 
56
- private fun notifyStatus() {
102
+ /**
103
+ * Read the current GPS + Network provider state and invoke the callback
104
+ * only if the values actually changed since the last notification.
105
+ */
106
+ private fun emitCurrentStatus() {
57
107
  val gps = if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
58
108
  LocationProviderStatus.ENABLED else LocationProviderStatus.DISABLED
59
109
  val network = if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
60
110
  LocationProviderStatus.ENABLED else LocationProviderStatus.DISABLED
111
+
112
+ // Deduplicate — only fire if something actually changed
113
+ if (gps == lastGps && network == lastNetwork) return
114
+
115
+ lastGps = gps
116
+ lastNetwork = network
117
+ Log.d(TAG, "Provider status changed: GPS=$gps, Network=$network")
61
118
  callback?.invoke(gps, network)
62
119
  }
63
120
 
121
+ // ── Cleanup ──────────────────────────────────────────────────────────
122
+
64
123
  fun destroy() {
65
- receiver?.let {
124
+ pendingNotify?.let { mainHandler.removeCallbacks(it) }
125
+ pendingNotify = null
126
+
127
+ contentObserver?.let {
66
128
  try {
67
- context.unregisterReceiver(it)
129
+ context.contentResolver.unregisterContentObserver(it)
68
130
  } catch (_: Exception) {}
69
131
  }
70
- receiver = null
132
+ contentObserver = null
71
133
  callback = null
134
+ lastGps = null
135
+ lastNetwork = null
72
136
  }
73
137
  }
@@ -21,8 +21,31 @@ class LocationEngine: NSObject, CLLocationManagerDelegate {
21
21
  var rejectMockLocations: Bool = false
22
22
  let speedMonitor = SpeedMonitor()
23
23
  let tripCalculator = TripCalculator()
24
- var providerStatusCallback: ((LocationProviderStatus, LocationProviderStatus) -> Void)?
25
- var permissionStatusCallback: ((PermissionStatus) -> Void)?
24
+ var providerStatusCallback: ((LocationProviderStatus, LocationProviderStatus) -> Void)? {
25
+ didSet {
26
+ // Emit current status immediately when the callback is first set
27
+ if providerStatusCallback != nil {
28
+ let enabled = CLLocationManager.locationServicesEnabled()
29
+ let status: LocationProviderStatus = enabled ? .enabled : .disabled
30
+ lastProviderEnabled = enabled
31
+ providerStatusCallback?(status, status)
32
+ }
33
+ }
34
+ }
35
+ var permissionStatusCallback: ((PermissionStatus) -> Void)? {
36
+ didSet {
37
+ // Emit current status immediately when the callback is first set
38
+ if permissionStatusCallback != nil {
39
+ let current = Self.mapAuthStatus(CLLocationManager.authorizationStatus())
40
+ lastPermissionStatus = current
41
+ permissionStatusCallback?(current)
42
+ }
43
+ }
44
+ }
45
+
46
+ // Deduplication: track last-notified values
47
+ private var lastPermissionStatus: PermissionStatus?
48
+ private var lastProviderEnabled: Bool?
26
49
 
27
50
  /// The most recently received location from Core Location (for distance calculations)
28
51
  var lastCLLocation: CLLocation? {
@@ -193,6 +216,8 @@ class LocationEngine: NSObject, CLLocationManagerDelegate {
193
216
  return CLLocationManager.locationServicesEnabled()
194
217
  }
195
218
 
219
+ // MARK: - Authorization / Provider change delegate
220
+
196
221
  func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
197
222
  guard manager === locationManager else { return }
198
223
 
@@ -213,27 +238,39 @@ class LocationEngine: NSObject, CLLocationManagerDelegate {
213
238
  }
214
239
  }
215
240
 
216
- // Notify JS about permission status change
217
- let permStatus: PermissionStatus
218
- switch authStatus {
241
+ // Notify JS about permission status change (deduplicated)
242
+ let permStatus = Self.mapAuthStatus(authStatus)
243
+ if permStatus != lastPermissionStatus {
244
+ lastPermissionStatus = permStatus
245
+ permissionStatusCallback?(permStatus)
246
+ }
247
+
248
+ // Notify JS about provider status change (deduplicated)
249
+ let enabled = CLLocationManager.locationServicesEnabled()
250
+ if enabled != lastProviderEnabled {
251
+ lastProviderEnabled = enabled
252
+ let status: LocationProviderStatus = enabled ? .enabled : .disabled
253
+ providerStatusCallback?(status, status)
254
+ }
255
+ }
256
+
257
+ // MARK: - Helpers
258
+
259
+ private static func mapAuthStatus(_ status: CLAuthorizationStatus) -> PermissionStatus {
260
+ switch status {
219
261
  case .notDetermined:
220
- permStatus = .notdetermined
262
+ return .notdetermined
221
263
  case .restricted:
222
- permStatus = .restricted
264
+ return .restricted
223
265
  case .denied:
224
- permStatus = .denied
266
+ return .denied
225
267
  case .authorizedWhenInUse:
226
- permStatus = .wheninuse
268
+ return .wheninuse
227
269
  case .authorizedAlways:
228
- permStatus = .always
270
+ return .always
229
271
  @unknown default:
230
- permStatus = .notdetermined
272
+ return .notdetermined
231
273
  }
232
- permissionStatusCallback?(permStatus)
233
-
234
- let enabled = CLLocationManager.locationServicesEnabled()
235
- let status: LocationProviderStatus = enabled ? .enabled : .disabled
236
- providerStatusCallback?(status, status)
237
274
  }
238
275
  }
239
276
 
@@ -0,0 +1,120 @@
1
+ import Foundation
2
+ import CoreLocation
3
+
4
+ /// Periodically checks whether the device is using simulated/mock locations.
5
+ /// Works independently of location tracking — fires the callback when the state changes.
6
+ ///
7
+ /// Detection methods:
8
+ /// - iOS 15+: Uses CLLocation.sourceInformation.isSimulatedBySoftware on a one-shot location request
9
+ /// - Simulator: Compile-time detection via #targetEnvironment(simulator)
10
+ /// - Jailbreak indicators: Checks for known mock location tool files
11
+ class MockLocationMonitor: NSObject, CLLocationManagerDelegate {
12
+
13
+ private let locationManager = CLLocationManager()
14
+ private var callback: ((Bool) -> Void)?
15
+ private var lastState: Bool?
16
+ private var pollTimer: Timer?
17
+ private let pollInterval: TimeInterval = 3.0
18
+
19
+ override init() {
20
+ super.init()
21
+ locationManager.delegate = self
22
+ locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
23
+ }
24
+
25
+ func setCallback(_ callback: @escaping (Bool) -> Void) {
26
+ self.callback = callback
27
+
28
+ // Emit current state immediately
29
+ let current = checkMockLocationSync()
30
+ lastState = current
31
+ callback(current)
32
+
33
+ // Start periodic polling
34
+ startPolling()
35
+ }
36
+
37
+ private func startPolling() {
38
+ stopPolling()
39
+ pollTimer = Timer.scheduledTimer(withTimeInterval: pollInterval, repeats: true) { [weak self] _ in
40
+ self?.performCheck()
41
+ }
42
+ }
43
+
44
+ private func stopPolling() {
45
+ pollTimer?.invalidate()
46
+ pollTimer = nil
47
+ }
48
+
49
+ private func performCheck() {
50
+ // First check compile-time and file-system indicators
51
+ let syncResult = checkMockLocationSync()
52
+ if syncResult != lastState {
53
+ lastState = syncResult
54
+ callback?(syncResult)
55
+ return
56
+ }
57
+
58
+ // On iOS 15+, also do a one-shot location check for runtime detection
59
+ if #available(iOS 15.0, *) {
60
+ let status = CLLocationManager.authorizationStatus()
61
+ if status == .authorizedAlways || status == .authorizedWhenInUse {
62
+ locationManager.requestLocation()
63
+ }
64
+ }
65
+ }
66
+
67
+ // MARK: - Synchronous checks (no location needed)
68
+
69
+ private func checkMockLocationSync() -> Bool {
70
+ #if targetEnvironment(simulator)
71
+ return true
72
+ #else
73
+ // Check for common jailbreak/mock location indicators
74
+ let suspiciousPaths = [
75
+ "/Applications/Cydia.app",
76
+ "/Library/MobileSubstrate/MobileSubstrate.dylib",
77
+ "/usr/sbin/sshd",
78
+ "/etc/apt",
79
+ "/private/var/lib/apt/",
80
+ "/Applications/LocationFaker.app",
81
+ "/Applications/LocationHandle.app",
82
+ "/Applications/LocationChanger.app"
83
+ ]
84
+
85
+ for path in suspiciousPaths {
86
+ if FileManager.default.fileExists(atPath: path) {
87
+ return true
88
+ }
89
+ }
90
+
91
+ return false
92
+ #endif
93
+ }
94
+
95
+ // MARK: - CLLocationManagerDelegate (for iOS 15+ runtime detection)
96
+
97
+ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
98
+ guard let location = locations.last else { return }
99
+
100
+ if #available(iOS 15.0, *) {
101
+ if let sourceInfo = location.sourceInformation {
102
+ let isMock = sourceInfo.isSimulatedBySoftware
103
+ if isMock != lastState {
104
+ lastState = isMock
105
+ callback?(isMock)
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
112
+ // Silently ignore — this is a best-effort check
113
+ }
114
+
115
+ func destroy() {
116
+ stopPolling()
117
+ callback = nil
118
+ lastState = nil
119
+ }
120
+ }
@@ -8,6 +8,7 @@ class NitroLocationTracking: HybridNitroLocationTrackingSpec {
8
8
  private let dbWriter = NativeDBWriter()
9
9
  private let notificationService = NotificationService()
10
10
  private let geofenceManager = GeofenceManager()
11
+ private let mockLocationMonitor = MockLocationMonitor()
11
12
 
12
13
  private var locationCallback: ((LocationData) -> Void)?
13
14
  private var motionCallback: ((Bool) -> Void)?
@@ -17,6 +18,7 @@ class NitroLocationTracking: HybridNitroLocationTrackingSpec {
17
18
  private var speedAlertCallback: ((SpeedAlertType, Double) -> Void)?
18
19
  private var providerStatusCallback: ((LocationProviderStatus, LocationProviderStatus) -> Void)?
19
20
  private var permissionStatusCallback: ((PermissionStatus) -> Void)?
21
+ private var mockLocationCallback: ((Bool) -> Void)?
20
22
  private var permissionPromise: Promise<PermissionStatus>?
21
23
 
22
24
  override init() {
@@ -125,6 +127,11 @@ class NitroLocationTracking: HybridNitroLocationTrackingSpec {
125
127
  locationEngine.rejectMockLocations = reject
126
128
  }
127
129
 
130
+ func onMockLocationDetected(callback: @escaping (Bool) -> Void) throws {
131
+ mockLocationCallback = callback
132
+ mockLocationMonitor.setCallback(callback)
133
+ }
134
+
128
135
  // MARK: - Geofencing
129
136
 
130
137
  func addGeofence(region: GeofenceRegion) throws {
@@ -270,5 +277,6 @@ class NitroLocationTracking: HybridNitroLocationTrackingSpec {
270
277
  locationEngine.stop()
271
278
  connectionManager.disconnect()
272
279
  geofenceManager.destroy()
280
+ mockLocationMonitor.destroy()
273
281
  }
274
282
  }
@@ -1 +1 @@
1
- {"version":3,"names":["useState","useEffect","useCallback","useRef","NitroModules","NitroLocationModule","createHybridObject","requestLocationPermission","LocationSmoother","shortestRotation","calculateBearing","useDriverLocation","config","location","setLocation","isMoving","setIsMoving","isTracking","setIsTracking","configJson","JSON","stringify","trackingRef","parsed","parse","configure","onLocation","onMotionChange","current","stopTracking","startTracking","useRideConnection","connectionState","setConnectionState","lastMessage","setLastMessage","configureConnection","onConnectionStateChange","onMessage","disconnectWebSocket","connect","connectWebSocket","disconnect","send","m","sendMessage"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["useState","useEffect","useCallback","useRef","NitroModules","NitroLocationModule","createHybridObject","requestLocationPermission","LocationSmoother","shortestRotation","calculateBearing","useDriverLocation","config","location","setLocation","isMoving","setIsMoving","isTracking","setIsTracking","configJson","JSON","stringify","trackingRef","parsed","parse","configure","onLocation","onMotionChange","current","stopTracking","startTracking","useRideConnection","connectionState","setConnectionState","lastMessage","setLastMessage","configureConnection","onConnectionStateChange","onMessage","disconnectWebSocket","connect","connectWebSocket","disconnect","send","m","sendMessage"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -34,6 +34,7 @@ export interface ConnectionConfig {
34
34
  export type LocationCallback = (location: LocationData) => void;
35
35
  export type ConnectionStateCallback = (state: ConnectionState) => void;
36
36
  export type MessageCallback = (message: string) => void;
37
+ export type MockLocationCallback = (isMockEnabled: boolean) => void;
37
38
  export interface GeofenceRegion {
38
39
  id: string;
39
40
  latitude: number;
@@ -83,6 +84,7 @@ export interface NitroLocationTracking extends HybridObject<{
83
84
  forceSync(): Promise<boolean>;
84
85
  isFakeGpsEnabled(): boolean;
85
86
  setRejectMockLocations(reject: boolean): void;
87
+ onMockLocationDetected(callback: MockLocationCallback): void;
86
88
  addGeofence(region: GeofenceRegion): void;
87
89
  removeGeofence(regionId: string): void;
88
90
  removeAllGeofences(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"NitroLocationTracking.nitro.d.ts","sourceRoot":"","sources":["../../../src/NitroLocationTracking.nitro.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NitroLocationTracking.nitro.d.ts","sourceRoot":"","sources":["../../../src/NitroLocationTracking.nitro.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ export default NitroLocationModule;
4
4
  export { requestLocationPermission } from './requestPermission';
5
5
  export { LocationSmoother } from './LocationSmoother';
6
6
  export { shortestRotation, calculateBearing } from './bearing';
7
- export type { NitroLocationTracking, LocationData, LocationConfig, ConnectionConfig, GeofenceRegion, GeofenceEvent, GeofenceCallback, SpeedConfig, SpeedAlertType, SpeedAlertCallback, TripStats, LocationProviderStatus, ProviderStatusCallback, PermissionStatus, PermissionStatusCallback, } from './NitroLocationTracking.nitro';
7
+ export type { NitroLocationTracking, LocationData, LocationConfig, ConnectionConfig, GeofenceRegion, GeofenceEvent, GeofenceCallback, SpeedConfig, SpeedAlertType, SpeedAlertCallback, TripStats, LocationProviderStatus, ProviderStatusCallback, PermissionStatus, PermissionStatusCallback, MockLocationCallback, } from './NitroLocationTracking.nitro';
8
8
  export declare function useDriverLocation(config: LocationConfig): {
9
9
  location: LocationData | null;
10
10
  isMoving: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,qBAAqB,EACrB,YAAY,EACZ,cAAc,EACd,gBAAgB,EACjB,MAAM,+BAA+B,CAAC;AAEvC,QAAA,MAAM,mBAAmB,uBAGtB,CAAC;AAEJ,eAAe,mBAAmB,CAAC;AACnC,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE/D,YAAY,EACV,qBAAqB,EACrB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,sBAAsB,EACtB,sBAAsB,EACtB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AAEvC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc;;;;;;EAuCvD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB;;;;;cAuBhC,MAAM;EAE/B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,qBAAqB,EACrB,YAAY,EACZ,cAAc,EACd,gBAAgB,EACjB,MAAM,+BAA+B,CAAC;AAEvC,QAAA,MAAM,mBAAmB,uBAGtB,CAAC;AAEJ,eAAe,mBAAmB,CAAC;AACnC,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE/D,YAAY,EACV,qBAAqB,EACrB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,sBAAsB,EACtB,sBAAsB,EACtB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,+BAA+B,CAAC;AAEvC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc;;;;;;EAuCvD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB;;;;;cAuBhC,MAAM;EAE/B"}
@@ -205,6 +205,10 @@ namespace margelo::nitro::nitrolocationtracking {
205
205
  static const auto method = javaClassStatic()->getMethod<void(jboolean /* reject */)>("setRejectMockLocations");
206
206
  method(_javaPart, reject);
207
207
  }
208
+ void JHybridNitroLocationTrackingSpec::onMockLocationDetected(const std::function<void(bool /* isMockEnabled */)>& callback) {
209
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<JFunc_void_bool::javaobject> /* callback */)>("onMockLocationDetected_cxx");
210
+ method(_javaPart, JFunc_void_bool_cxx::fromCpp(callback));
211
+ }
208
212
  void JHybridNitroLocationTrackingSpec::addGeofence(const GeofenceRegion& region) {
209
213
  static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<JGeofenceRegion> /* region */)>("addGeofence");
210
214
  method(_javaPart, JGeofenceRegion::fromCpp(region));
@@ -72,6 +72,7 @@ namespace margelo::nitro::nitrolocationtracking {
72
72
  std::shared_ptr<Promise<bool>> forceSync() override;
73
73
  bool isFakeGpsEnabled() override;
74
74
  void setRejectMockLocations(bool reject) override;
75
+ void onMockLocationDetected(const std::function<void(bool /* isMockEnabled */)>& callback) override;
75
76
  void addGeofence(const GeofenceRegion& region) override;
76
77
  void removeGeofence(const std::string& regionId) override;
77
78
  void removeAllGeofences() override;
@@ -134,6 +134,15 @@ abstract class HybridNitroLocationTrackingSpec: HybridObject() {
134
134
  @Keep
135
135
  abstract fun setRejectMockLocations(reject: Boolean): Unit
136
136
 
137
+ abstract fun onMockLocationDetected(callback: (isMockEnabled: Boolean) -> Unit): Unit
138
+
139
+ @DoNotStrip
140
+ @Keep
141
+ private fun onMockLocationDetected_cxx(callback: Func_void_bool): Unit {
142
+ val __result = onMockLocationDetected(callback)
143
+ return __result
144
+ }
145
+
137
146
  @DoNotStrip
138
147
  @Keep
139
148
  abstract fun addGeofence(region: GeofenceRegion): Unit
@@ -216,6 +216,12 @@ namespace margelo::nitro::nitrolocationtracking {
216
216
  std::rethrow_exception(__result.error());
217
217
  }
218
218
  }
219
+ inline void onMockLocationDetected(const std::function<void(bool /* isMockEnabled */)>& callback) override {
220
+ auto __result = _swiftPart.onMockLocationDetected(callback);
221
+ if (__result.hasError()) [[unlikely]] {
222
+ std::rethrow_exception(__result.error());
223
+ }
224
+ }
219
225
  inline void addGeofence(const GeofenceRegion& region) override {
220
226
  auto __result = _swiftPart.addGeofence(std::forward<decltype(region)>(region));
221
227
  if (__result.hasError()) [[unlikely]] {
@@ -30,6 +30,7 @@ public protocol HybridNitroLocationTrackingSpec_protocol: HybridObject {
30
30
  func forceSync() throws -> Promise<Bool>
31
31
  func isFakeGpsEnabled() throws -> Bool
32
32
  func setRejectMockLocations(reject: Bool) throws -> Void
33
+ func onMockLocationDetected(callback: @escaping (_ isMockEnabled: Bool) -> Void) throws -> Void
33
34
  func addGeofence(region: GeofenceRegion) throws -> Void
34
35
  func removeGeofence(regionId: String) throws -> Void
35
36
  func removeAllGeofences() throws -> Void
@@ -350,6 +350,22 @@ open class HybridNitroLocationTrackingSpec_cxx {
350
350
  }
351
351
  }
352
352
 
353
+ @inline(__always)
354
+ public final func onMockLocationDetected(callback: bridge.Func_void_bool) -> bridge.Result_void_ {
355
+ do {
356
+ try self.__implementation.onMockLocationDetected(callback: { () -> (Bool) -> Void in
357
+ let __wrappedFunction = bridge.wrap_Func_void_bool(callback)
358
+ return { (__isMockEnabled: Bool) -> Void in
359
+ __wrappedFunction.call(__isMockEnabled)
360
+ }
361
+ }())
362
+ return bridge.create_Result_void_()
363
+ } catch (let __error) {
364
+ let __exceptionPtr = __error.toCpp()
365
+ return bridge.create_Result_void_(__exceptionPtr)
366
+ }
367
+ }
368
+
353
369
  @inline(__always)
354
370
  public final func addGeofence(region: GeofenceRegion) -> bridge.Result_void_ {
355
371
  do {
@@ -31,6 +31,7 @@ namespace margelo::nitro::nitrolocationtracking {
31
31
  prototype.registerHybridMethod("forceSync", &HybridNitroLocationTrackingSpec::forceSync);
32
32
  prototype.registerHybridMethod("isFakeGpsEnabled", &HybridNitroLocationTrackingSpec::isFakeGpsEnabled);
33
33
  prototype.registerHybridMethod("setRejectMockLocations", &HybridNitroLocationTrackingSpec::setRejectMockLocations);
34
+ prototype.registerHybridMethod("onMockLocationDetected", &HybridNitroLocationTrackingSpec::onMockLocationDetected);
34
35
  prototype.registerHybridMethod("addGeofence", &HybridNitroLocationTrackingSpec::addGeofence);
35
36
  prototype.registerHybridMethod("removeGeofence", &HybridNitroLocationTrackingSpec::removeGeofence);
36
37
  prototype.registerHybridMethod("removeAllGeofences", &HybridNitroLocationTrackingSpec::removeAllGeofences);
@@ -99,6 +99,7 @@ namespace margelo::nitro::nitrolocationtracking {
99
99
  virtual std::shared_ptr<Promise<bool>> forceSync() = 0;
100
100
  virtual bool isFakeGpsEnabled() = 0;
101
101
  virtual void setRejectMockLocations(bool reject) = 0;
102
+ virtual void onMockLocationDetected(const std::function<void(bool /* isMockEnabled */)>& callback) = 0;
102
103
  virtual void addGeofence(const GeofenceRegion& region) = 0;
103
104
  virtual void removeGeofence(const std::string& regionId) = 0;
104
105
  virtual void removeAllGeofences() = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-location-tracking",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A React Native Nitro module for location tracking",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -43,6 +43,7 @@ export interface ConnectionConfig {
43
43
  export type LocationCallback = (location: LocationData) => void;
44
44
  export type ConnectionStateCallback = (state: ConnectionState) => void;
45
45
  export type MessageCallback = (message: string) => void;
46
+ export type MockLocationCallback = (isMockEnabled: boolean) => void;
46
47
 
47
48
  export interface GeofenceRegion {
48
49
  id: string;
@@ -121,6 +122,7 @@ export interface NitroLocationTracking
121
122
  // === Fake GPS Detection ===
122
123
  isFakeGpsEnabled(): boolean;
123
124
  setRejectMockLocations(reject: boolean): void;
125
+ onMockLocationDetected(callback: MockLocationCallback): void;
124
126
 
125
127
  // === Geofencing ===
126
128
  addGeofence(region: GeofenceRegion): void;
package/src/index.tsx CHANGED
@@ -33,6 +33,7 @@ export type {
33
33
  ProviderStatusCallback,
34
34
  PermissionStatus,
35
35
  PermissionStatusCallback,
36
+ MockLocationCallback,
36
37
  } from './NitroLocationTracking.nitro';
37
38
 
38
39
  export function useDriverLocation(config: LocationConfig) {