fix(track-location): try 7 (+ up rnbl to v5)

This commit is contained in:
devthejo 2026-01-23 01:09:25 +01:00
parent b61aff7078
commit f4f7708e71
No known key found for this signature in database
GPG key ID: 00CCA7A92B1D5351
12 changed files with 293 additions and 188 deletions

View file

@ -16,5 +16,5 @@ ASC_API_KEY_PATH=
PROVIDER_ID=
# Background Geolocation License Keys
BACKGROUND_GEOLOCATION_LICENSE=your_license_key_here
BACKGROUND_GEOLOCATION_HMS_LICENSE=your_hms_license_key_here
BACKGROUND_GEOLOCATION_LICENSE_ANDROID=your_license_key_here
BACKGROUND_GEOLOCATION_LICENSE_IOS=your_license_key_here

View file

@ -37,8 +37,7 @@
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:networkSecurityConfig="@xml/network_security_config" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color" tools:replace="android:resource"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="com.transistorsoft.locationmanager.hms.license" android:value="ba479a3c61fbe471c826a39d1ee8b1a088df6d2249fad51f6ab5f24346f6bf87"/>
<meta-data android:name="com.transistorsoft.locationmanager.license" android:value="90f34e070bebc8e433a9b604e1728dfa17a62ab484602e3cb7ef7ece784a23eb"/>
<meta-data android:name="com.transistorsoft.locationmanager.license" android:value="eyJhbGciOiJFZERTQSIsImtpZCI6ImVkMjU1MTktbWFpbi12MSJ9.eyJvcyI6ImFuZHJvaWQiLCJhcHBfaWQiOiJjb20uYWxlcnRlc2Vjb3VycyIsIm9yZGVyX251bWJlciI6ODY0MCwicmVuZXdhbF91cmwiOiJodHRwczovL3Nob3AudHJhbnNpc3RvcnNvZnQuY29tL2NhcnQvMTY1MDc4NjE1MDU6MT9ub3RlPTYxOTMiLCJjdXN0b21lcl9pZCI6NTc0MiwicHJvZHVjdCI6InJlYWN0LW5hdGl2ZS1iYWNrZ3JvdW5kLWdlb2xvY2F0aW9uIiwia2V5X3ZlcnNpb24iOjEsImFsbG93ZWRfc3VmZml4ZXMiOlsiLmRldiIsIi5kZXZlbG9wbWVudCIsIi5zdGFnaW5nIiwiLnN0YWdlIiwiLnFhIiwiLnVhdCIsIi50ZXN0IiwiLmRlYnVnIl0sIm1heF9idWlsZF9zdGFtcCI6MjAyNzAyMjIsImdyYWNlX2J1aWxkcyI6MCwiZW50aXRsZW1lbnRzIjpbImNvcmUiXSwiaWF0IjoxNzY5MTI2Mjg5fQ.sCJRdXJ78r2B0BLfht-AnYXSoF5pcTxAfEkCcX6BN7FiYc8GT9RUMlqDdwacD6UlI0v6WLhQck6lPkRgCq4MDQ"/>
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="expo.modules.updates.CODE_SIGNING_CERTIFICATE" android:value="-----BEGIN CERTIFICATE-----&#xD;&#xA;MIICzTCCAbWgAwIBAgIJR0KfFDoMJrYZMA0GCSqGSIb3DQEBCwUAMA8xDTALBgNV&#xD;&#xA;BAMTBHRlc3QwIBcNMjQwODE4MTU0OTExWhgPMjEyNDA4MTgxNTQ5MTFaMA8xDTAL&#xD;&#xA;BgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk46qR&#xD;&#xA;a0Do2fpBDtif18a/WQNWHm/xseHsh97bZdt8ooV4PQooK6VZUbADUhhJXqqomapa&#xD;&#xA;yFMJX7sZzfBUF7/xMrWDrgS0R4FLbXijAolhpXoqMkBCx3toKUCbU4ljA+Lz/BX1&#xD;&#xA;AEqVWqAweNzNDi4bvd1PG1/sQuuEtoZuSVfTPRAjF8vVkWyn8nfkorTtMYaw2QFu&#xD;&#xA;ugs1wp7YieD4C8CIK5gMX7f8bxx3l7BR50bf+9MHJFI+eTjmoFoJFEVWbCcrOrky&#xD;&#xA;FLM0+NrMI2fZYunrN6jcKc/NKEaDKb1VDO9yrLcFQOtXJJIXz94/lS6kHDjzEgUV&#xD;&#xA;zx3uaDbAdSQsyZxxAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8E&#xD;&#xA;DDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEASIjMvS3N9NEPeakUmYXg&#xD;&#xA;MEyjaX+N/62Pbcp4taU4G9vDB/fyDqMMef8+CWBpo/noXqzt4K6k1id7UwdZhRks&#xD;&#xA;xdBTSf1x5yzDB24mbqNAvPa2q8G6KIoNZuvLUDz35366FxR+vTHQmp2d4Yz92kIL&#xD;&#xA;EEmFr8eMHf60tfHG+em97p+evmXDyBjF3CwOvtuzog1wCF/AsJ1d0gbPPMKdAHKC&#xD;&#xA;LZHsiXJ5i/oFuYzWkDDJkO9bb6HaQplt/46iC0CyM6SsT6H8kkDVQbfQCH1JAXsL&#xD;&#xA;Knk10FbAMKJ7GWbAdsdcbNZlDMrzPprw8N/fpGc7RHdHBwKcFm44mNtrMrzEd4eX&#xD;&#xA;pQ==&#xD;&#xA;-----END CERTIFICATE-----&#xD;&#xA;"/>

View file

@ -30,14 +30,7 @@ apply plugin: "com.facebook.react.rootproject"
allprojects {
repositories {
// @generated begin react-native-background-fetch-maven - expo prebuild (DO NOT MODIFY) sync-b86324ce2eb77b03cc8b69ba206ef8275cd006ff
maven { url "${project(":react-native-background-fetch").projectDir}/libs" }
// @generated begin react-native-background-geolocation-maven - expo prebuild (DO NOT MODIFY) sync-4b2bae87fd8579c445d8885f6bdc8542d9d0bbca
maven { url "${project(":react-native-background-geolocation").projectDir}/libs" }
maven { url 'https://developer.huawei.com/repo/' }
// @generated end react-native-background-geolocation-maven
// @generated end react-native-background-fetch-maven
maven {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
}

View file

@ -140,15 +140,9 @@ let config = {
],
},
UIBackgroundModes: ["location", "fetch", "processing"],
TSLocationManagerLicense: process.env.BACKGROUND_GEOLOCATION_LICENSE_IOS,
},
plugins: [
[
"react-native-background-geolocation",
{
license: process.env.BACKGROUND_GEOLOCATION_LICENSE,
hmsLicense: process.env.BACKGROUND_GEOLOCATION_HMS_LICENSE,
},
],
[
"expo-gradle-ext-vars",
{

View file

@ -11,7 +11,6 @@ The app uses environment variables for configuration and sensitive information.
Copy `.env.default` to `.env.local` (which is git-ignored) and fill in the required values:
- `BACKGROUND_GEOLOCATION_LICENSE`: License key for react-native-background-geolocation
- `BACKGROUND_GEOLOCATION_HMS_LICENSE`: HMS license key for react-native-background-geolocation
### Production Environment

View file

@ -15,14 +15,14 @@ Applies to the BackgroundGeolocation integration:
## Current implementation notes
- Movement-driven recording only:
- IDLE uses `distanceFilter: 200` (aim: no updates while not moving).
- ACTIVE uses `distanceFilter: 25`.
- IDLE uses `geolocation.distanceFilter: 200` (aim: no updates while not moving).
- ACTIVE uses `geolocation.distanceFilter: 25`.
- JS may request a persisted fix when entering ACTIVE (see [`applyProfile()`](src/location/trackLocation.js:351)).
- Upload strategy is intentionally simple:
- Keep only the latest persisted geopoint: `maxRecordsToPersist: 1`.
- Keep only the latest persisted geopoint: `persistence.maxRecordsToPersist: 1`.
- No batching / thresholds: `batchSync: false`, `autoSyncThreshold: 0`.
- When authenticated, each persisted location should upload immediately via native HTTP (works while JS is suspended).
- Pre-auth: tracking may persist locally but `url` is empty so nothing is uploaded until auth is ready.
- Pre-auth: tracking may persist locally but `http.url` is empty so nothing is uploaded until auth is ready.
## Basic preconditions
@ -69,18 +69,40 @@ Applies to the BackgroundGeolocation integration:
### Android
1. Ensure tracking enabled and authenticated.
2. Force-stop the app task.
2. Swipe the app away from recents / kill the task.
3. Move ~250m in IDLE.
- Expect: native service still records + uploads.
4. Move ~30m in ACTIVE.
- Expect: native service still records + uploads.
> Note: This does **not** include Android Settings → **Force stop**.
> Force-stop prevents background services and receivers from running; no SDK can reliably track after that.
### iOS
1. Swipe-kill the app.
2. Move significantly (expect iOS to relaunch app on stationary-geofence exit).
- Expect: tracking resumes and uploads after movement.
## Test matrix (quick)
| Platform | App state | Profile | Move | Expected signals |
|---|---|---|---:|---|
| Android | foreground | IDLE | ~250m | [`onLocation`](src/location/trackLocation.js:693) (sample=false), then [`onHttp`](src/location/trackLocation.js:733) |
| Android | background | IDLE | ~250m | same as above |
| Android | swipe-away | IDLE | ~250m | native persists + uploads; verify server + `onHttp` when app relaunches |
| Android | foreground | ACTIVE | ~30m | location + upload continues |
| iOS | background | IDLE | ~250m | movement-driven update; no periodic uploads while stationary |
| iOS | swipe-killed | IDLE | significant | OS relaunch on movement; upload after relaunch |
## What to look for in logs
- App lifecycle tagging: [`updateTrackingContextExtras()`](src/location/trackLocation.js:63) should update `tracking_ctx.app_state` on AppState changes.
- No time-based uploads: heartbeat is disabled (`heartbeatInterval: 0`), so no `Heartbeat` logs from [`onHeartbeat`](src/location/trackLocation.js:762).
- Movement-only uploads:
- IDLE distance threshold: `distanceFilter: 200` in [`TRACKING_PROFILES`](src/location/backgroundGeolocationConfig.js:148).
- ACTIVE distance threshold: `distanceFilter: 25` in [`TRACKING_PROFILES`](src/location/backgroundGeolocationConfig.js:148).
## Debugging tips
- Observe logs in app:

View file

@ -146,6 +146,8 @@
<string>[Alerte-Secours] Cette application utilise les notifications pour pouvoir vous prévenir lorsque quelqu'un a besoin d'aide à proximité</string>
<key>NSUserNotificationUsageDescription</key>
<string>Alerte-Secours utilise les notifications pour vous informer immédiatement lorsqu'une personne à proximité nécessite une assistance urgente. Ces alertes sont essentielles pour une réponse rapide aux situations d'urgence.</string>
<key>TSLocationManagerLicense</key>
<string>eyJhbGciOiJFZERTQSIsImtpZCI6ImVkMjU1MTktbWFpbi12MSJ9.eyJvcyI6ImlvcyIsImFwcF9pZCI6ImNvbS5hbGVydGVzZWNvdXJzLmFsZXJ0ZXNlY291cnMiLCJvcmRlcl9udW1iZXIiOjg2NDAsInJlbmV3YWxfdXJsIjoiaHR0cHM6Ly9zaG9wLnRyYW5zaXN0b3Jzb2Z0LmNvbS9jYXJ0LzE2NTA3ODYxNTA1OjE_bm90ZT02MTkzIiwiY3VzdG9tZXJfaWQiOjU3NDIsInByb2R1Y3QiOiJyZWFjdC1uYXRpdmUtYmFja2dyb3VuZC1nZW9sb2NhdGlvbiIsImtleV92ZXJzaW9uIjoxLCJhbGxvd2VkX3N1ZmZpeGVzIjpbIi5kZXYiLCIuZGV2ZWxvcG1lbnQiLCIuc3RhZ2luZyIsIi5zdGFnZSIsIi5xYSIsIi51YXQiLCIudGVzdCIsIi5kZWJ1ZyJdLCJtYXhfYnVpbGRfc3RhbXAiOjIwMjcwMjIyLCJncmFjZV9idWlsZHMiOjAsImVudGl0bGVtZW50cyI6WyJjb3JlIl0sImlhdCI6MTc2OTEyNjI4OX0.bOJmTwxkRlRVAMHuNeXUVlSFTQyai3wal2TEz6jlOvEHFadbP6WG0E_KUMatinSRgKEIxs0L_SmSn6G5eWAYDQ</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>

View file

@ -178,7 +178,7 @@
"react-native-animatable": "^1.3.3",
"react-native-app-link": "^1.0.1",
"react-native-background-fetch": "^4.2.8",
"react-native-background-geolocation": "^4.19.0",
"react-native-background-geolocation": "5.0.1",
"react-native-battery-optimization-check": "^1.0.8",
"react-native-contact-pick": "^0.1.2",
"react-native-country-picker-modal": "^2.0.0",
@ -282,4 +282,4 @@
}
},
"packageManager": "yarn@4.5.3"
}
}

View file

@ -19,142 +19,166 @@ import env from "~/env";
// - ACTIVE (open alert): first location should reach server within seconds, then continuous distance-based updates.
//
// Notes:
// - We avoid `reset: true` in production because it can unintentionally wipe persisted / configured state.
// In dev, `reset: true` is useful to avoid config drift while iterating.
// - `maxRecordsToPersist` must be > 1 to support offline catch-up.
// - We keep config deterministic across launches to avoid stale persisted settings creating
// unexpected periodic wakeups/uploads.
// - `maxRecordsToPersist: 1` matches product requirement (only latest geopoint).
export const BASE_GEOLOCATION_CONFIG = {
// Android Headless Mode
// We do not require JS execution while terminated. Native tracking + native HTTP upload
// are sufficient for our needs (stopOnTerminate:false).
enableHeadless: false,
// Default to low-power (idle) profile; will be overridden when needed.
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_LOW,
// Default to the IDLE profile behaviour: we still want distance-based updates
// even with no open alert (see TRACKING_PROFILES.idle).
distanceFilter: 200,
// Activity-recognition stop-detection.
// NOTE: Transistorsoft defaults `stopTimeout` to 5 minutes (see
// [`node_modules/react-native-background-geolocation/src/declarations/interfaces/Config.d.ts:79`](node_modules/react-native-background-geolocation/src/declarations/interfaces/Config.d.ts:79)).
stopTimeout: 5,
// debug: true,
// Logging can become large and also adds overhead; keep verbose logs to dev/staging.
logLevel:
__DEV__ || env.IS_STAGING
? BackgroundGeolocation.LOG_LEVEL_VERBOSE
: BackgroundGeolocation.LOG_LEVEL_ERROR,
// Permission request strategy
locationAuthorizationRequest: "Always",
// Lifecycle
stopOnTerminate: false,
startOnBoot: true,
// Background scheduling
// Disable heartbeats by default to avoid periodic background wakeups while stationary.
// ACTIVE profile will explicitly enable a fast heartbeat when needed.
heartbeatInterval: 0,
// Android foreground service
foregroundService: true,
notification: {
title: "Alerte Secours",
text: "Suivi de localisation actif",
channelName: "Location tracking",
priority: BackgroundGeolocation.NOTIFICATION_PRIORITY_HIGH,
},
// Android 10+ rationale dialog
backgroundPermissionRationale: {
title:
"Autoriser Alerte-Secours à accéder à la localisation en arrière-plan",
message:
"Alerte-Secours nécessite la localisation en arrière-plan pour vous alerter en temps réel lorsqu'une personne à proximité a besoin d'aide urgente. Cette fonction est essentielle pour permettre une intervention rapide et efficace en cas d'urgence.",
positiveAction: "Autoriser",
negativeAction: "Désactiver",
},
// HTTP configuration
// IMPORTANT: Default to uploads disabled until we have an auth token.
// Authenticated mode will set `url` + `Authorization` header and enable `autoSync`.
url: "",
method: "POST",
httpRootProperty: "location",
// Keep uploads simple: 1 location record -> 1 HTTP request.
// (We intentionally keep only the latest record; batching provides no benefit.)
autoSync: false,
// Ensure no persisted config can keep batching/threshold behavior.
batchSync: false,
autoSyncThreshold: 0,
// Persistence
// Product requirement: keep only the latest geopoint. This reduces on-device storage
// and avoids building up a queue.
// NOTE: This means we intentionally do not support offline catch-up of multiple points.
maxRecordsToPersist: 1,
maxDaysToPersist: 1,
// IMPORTANT: Keep config deterministic across upgrades.
// `reset: false` causes the SDK to ignore changes in `ready(config)` after first install.
// This can leave old values (eg heartbeatInterval) lingering and creating periodic uploads.
reset: true,
// Behavior tweaks
disableProviderChangeRecord: true,
// Logger config
logger: {
// debug: true,
// Logging can become large and also adds overhead; keep verbose logs to dev/staging.
logLevel:
__DEV__ || env.IS_STAGING
? BackgroundGeolocation.LogLevel.Verbose
: BackgroundGeolocation.LogLevel.Error,
},
// Geolocation config
geolocation: {
// Default profile is IDLE.
//
// Important: `DesiredAccuracy.Low` (wifi/cell) can yield very large errors (km-level).
// Those poor fixes can create false motion/geofence transitions on some Android devices,
// resulting in periodic uploads while the user is stationary.
//
// We default to a GPS-capable accuracy but rely on motion + distance thresholds to
// protect battery.
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
// Default to the IDLE profile behaviour: we still want distance-based updates
// even with no open alert (see TRACKING_PROFILES.idle).
distanceFilter: 200,
// Stop-detection.
// NOTE: historically we set this at top-level. In v5 the knob is under `geolocation`.
stopTimeout: 5,
// Permission request strategy
locationAuthorizationRequest: "Always",
},
// Application / lifecycle config
app: {
// Android Headless Mode
// We do not require JS execution while terminated. Native tracking + native HTTP upload
// are sufficient for our needs (stopOnTerminate:false).
enableHeadless: false,
stopOnTerminate: false,
startOnBoot: true,
// Background scheduling
// Disable heartbeats to avoid periodic background wakeups while stationary.
heartbeatInterval: 0,
// Android foreground service notification
notification: {
title: "Alerte Secours",
text: "Suivi de localisation actif",
channelName: "Location tracking",
priority: BackgroundGeolocation.NotificationPriority.High,
},
// Android 10+ rationale dialog
backgroundPermissionRationale: {
title:
"Autoriser Alerte-Secours à accéder à la localisation en arrière-plan",
message:
"Alerte-Secours nécessite la localisation en arrière-plan pour vous alerter en temps réel lorsqu'une personne à proximité a besoin d'aide urgente. Cette fonction est essentielle pour permettre une intervention rapide et efficace en cas d'urgence.",
positiveAction: "Autoriser",
negativeAction: "Désactiver",
},
},
// HTTP config
// IMPORTANT: Default to uploads disabled until we have an auth token.
// Authenticated mode will set `http.url` + `Authorization` header and enable `autoSync`.
http: {
url: "",
method: BackgroundGeolocation.HttpMethod.POST,
rootProperty: "location",
// Keep uploads simple: 1 location record -> 1 HTTP request.
// (We intentionally keep only the latest record; batching provides no benefit.)
autoSync: false,
// Ensure no persisted config can keep batching/threshold behavior.
batchSync: false,
autoSyncThreshold: 0,
},
// Persistence config
persistence: {
// Product requirement: keep only the latest geopoint.
maxRecordsToPersist: 1,
maxDaysToPersist: 1,
// Behavior tweaks
disableProviderChangeRecord: true,
},
};
// Options we want to be stable across launches even when the plugin loads a persisted config.
// NOTE: We intentionally do *not* include HTTP auth headers here.
export const BASE_GEOLOCATION_INVARIANTS = {
enableHeadless: false,
stopOnTerminate: false,
startOnBoot: true,
foregroundService: true,
disableProviderChangeRecord: true,
// Never allow background heartbeats by default (prevents time-based wakeups/uploads).
heartbeatInterval: 0,
// Filter extreme GPS teleports that can create false uploads while stationary.
// Units: meters/second. 100 m/s ~= 360 km/h.
speedJumpFilter: 100,
method: "POST",
httpRootProperty: "location",
autoSync: false,
batchSync: false,
autoSyncThreshold: 0,
maxRecordsToPersist: 1,
maxDaysToPersist: 1,
app: {
enableHeadless: false,
stopOnTerminate: false,
startOnBoot: true,
// Never allow background heartbeats by default (prevents time-based wakeups/uploads).
heartbeatInterval: 0,
},
http: {
method: BackgroundGeolocation.HttpMethod.POST,
rootProperty: "location",
autoSync: false,
batchSync: false,
autoSyncThreshold: 0,
},
persistence: {
maxRecordsToPersist: 1,
maxDaysToPersist: 1,
disableProviderChangeRecord: true,
},
// NOTE: `speedJumpFilter` was a legacy Config knob; it is not part of v5 shared types.
// If we still want jump filtering, we'll need to implement a server-side filter or
// re-introduce a supported SDK filter (eg `geolocation.filter`) when available.
};
export const TRACKING_PROFILES = {
idle: {
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_LOW,
// Defensive: keep the distanceFilter conservative to avoid battery drain.
distanceFilter: 200,
// Never use heartbeat-driven updates; only movement-driven.
heartbeatInterval: 0,
// Keep the plugin's speed-based distanceFilter scaling enabled (default).
// This yields fewer updates as speed increases (highway speeds) and helps battery.
// We intentionally do NOT set `disableElasticity: true`.
// Android-only: reduce false-positive motion triggers due to screen-on/unlock.
// (This is ignored on iOS.)
motionTriggerDelay: 30000,
geolocation: {
// Same rationale as BASE: prefer GPS-capable accuracy to avoid km-level coarse fixes
// that can trigger false motion/geofence transitions on Android.
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
// Defensive: keep the distanceFilter conservative to avoid battery drain.
distanceFilter: 200,
},
app: {
// Never use heartbeat-driven updates; only movement-driven.
heartbeatInterval: 0,
},
activity: {
// Android-only: reduce false-positive motion triggers due to screen-on/unlock.
// We keep Motion API enabled (battery-optimized) but add a large delay so brief
// activity-jitter cannot repeatedly toggle moving/stationary while the user is idle.
// (This is ignored on iOS.)
motionTriggerDelay: 300000,
},
},
active: {
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
// ACTIVE target: frequent updates while moving.
distanceFilter: 25,
// Never use heartbeat-driven updates; only movement-driven.
heartbeatInterval: 0,
// Android-only: do not delay motion triggers while ACTIVE.
motionTriggerDelay: 0,
geolocation: {
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
// ACTIVE target: frequent updates while moving.
distanceFilter: 25,
},
app: {
// Never use heartbeat-driven updates; only movement-driven.
heartbeatInterval: 0,
},
activity: {
// Android-only: do not delay motion triggers while ACTIVE.
motionTriggerDelay: 0,
},
},
};

View file

@ -44,9 +44,9 @@ export async function getCurrentLocation() {
const isAuthorized =
authorizationStatus ===
BackgroundGeolocation.AUTHORIZATION_STATUS_ALWAYS ||
BackgroundGeolocation.AuthorizationStatus?.Always ||
authorizationStatus ===
BackgroundGeolocation.AUTHORIZATION_STATUS_WHEN_IN_USE;
BackgroundGeolocation.AuthorizationStatus?.WhenInUse;
if (!isAuthorized) {
// If unable to get permissions, provide a link to settings
@ -67,7 +67,7 @@ export async function getCurrentLocation() {
timeout: 30,
persist: false,
maximumAge: 5000,
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
samples: 1,
});
const coords = camelCaseKeys(location.coords);

View file

@ -64,14 +64,16 @@ export default function trackLocation() {
try {
const { userId } = getSessionState();
await BackgroundGeolocation.setConfig({
extras: {
tracking_ctx: {
reason,
app_state: appState,
profile: currentProfile,
auth_ready: authReady,
session_user_id: userId || null,
at: new Date().toISOString(),
persistence: {
extras: {
tracking_ctx: {
reason,
app_state: appState,
profile: currentProfile,
auth_ready: authReady,
session_user_id: userId || null,
at: new Date().toISOString(),
},
},
},
});
@ -249,7 +251,17 @@ export default function trackLocation() {
const AUTH_FIX_COOLDOWN_MS = 15 * 60 * 1000;
let lastAuthFixAt = 0;
// Avoid periodic UI-only getCurrentPosition while app is backgrounded, since
// this is a common source of "updates while stationary" (it can also influence
// motion state / generate provider churn on some Android devices).
const shouldAllowUiFixes = () => appState === "active";
const scheduleAuthFreshFix = () => {
// Do not perform UI refresh fixes while backgrounded.
if (!shouldAllowUiFixes()) {
return authFixInFlight;
}
// Avoid generating persisted + auto-synced locations as a side-effect of frequent
// auth refreshes (eg app resume / screen unlock).
if (Date.now() - lastAuthFixAt < AUTH_FIX_COOLDOWN_MS) {
@ -365,9 +377,9 @@ export default function trackLocation() {
locationLogger.info("Applying tracking profile", {
profileName,
desiredAccuracy: profile.desiredAccuracy,
distanceFilter: profile.distanceFilter,
heartbeatInterval: profile.heartbeatInterval,
desiredAccuracy: profile?.geolocation?.desiredAccuracy,
distanceFilter: profile?.geolocation?.distanceFilter,
heartbeatInterval: profile?.app?.heartbeatInterval,
});
try {
@ -485,9 +497,11 @@ export default function trackLocation() {
try {
await BackgroundGeolocation.setConfig({
url: "",
autoSync: false,
headers: {},
http: {
url: "",
autoSync: false,
headers: {},
},
});
didDisableUploadsForAnonymous = true;
didSyncAfterAuth = false;
@ -547,14 +561,17 @@ export default function trackLocation() {
// unsub();
locationLogger.debug("Updating background geolocation config");
await BackgroundGeolocation.setConfig({
url: env.GEOLOC_SYNC_URL, // Update the sync URL for when it's changed for staging
// IMPORTANT: enable native uploading when authenticated.
// This ensures uploads continue even if JS is suspended in background.
autoSync: true,
batchSync: false,
autoSyncThreshold: 0,
headers: {
Authorization: `Bearer ${userToken}`,
http: {
// Update the sync URL for when it's changed for staging
url: env.GEOLOC_SYNC_URL,
// IMPORTANT: enable native uploading when authenticated.
// This ensures uploads continue even if JS is suspended in background.
autoSync: true,
batchSync: false,
autoSyncThreshold: 0,
headers: {
Authorization: `Bearer ${userToken}`,
},
},
});
@ -756,11 +773,50 @@ export default function trackLocation() {
});
},
onMotionChange: (event) => {
// Diagnostic snapshot to understand periodic motion-change loops (eg Android ~5min).
// Keep it cheap: avoid heavy calls unless motion-change fires.
// NOTE: This is safe to run in background because it does not request a new location.
locationLogger.info("Motion change", {
isMoving: event?.isMoving,
location: event?.location?.coords,
});
// Async snapshot of BGGeo internal state/config at the time of motion-change.
// This helps correlate native behavior with our current profile + config.
(async () => {
try {
const state = await BackgroundGeolocation.getState();
locationLogger.info("Motion change diagnostic", {
isMoving: event?.isMoving,
appState: appState,
profile: currentProfile,
authReady,
// Time correlation
at: new Date().toISOString(),
// Core BGGeo runtime state
enabled: state?.enabled,
trackingMode: state?.trackingMode,
isMovingState: state?.isMoving,
schedulerEnabled: state?.schedulerEnabled,
// Critical config knobs related to periodic updates
distanceFilter: state?.geolocation?.distanceFilter,
heartbeatInterval: state?.app?.heartbeatInterval,
motionTriggerDelay: state?.activity?.motionTriggerDelay,
disableMotionActivityUpdates:
state?.activity?.disableMotionActivityUpdates,
stopTimeout: state?.geolocation?.stopTimeout,
// Location quality signal
accuracy: event?.location?.coords?.accuracy,
speed: event?.location?.coords?.speed,
});
} catch (e) {
locationLogger.warn("Motion change diagnostic failed", {
error: e?.message,
});
}
})();
// Moving-edge strategy: when we enter moving state, force one persisted high-quality
// point + sync so the server gets a quick update.
//

View file

@ -3237,6 +3237,13 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.26.0":
version: 7.28.6
resolution: "@babel/runtime@npm:7.28.6"
checksum: 10/fbcd439cb74d4a681958eb064c509829e3f46d8a4bfaaf441baa81bb6733d1e680bccc676c813883d7741bcaada1d0d04b15aa320ef280b5734e2192b50decf9
languageName: node
linkType: hard
"@babel/template@npm:^7.0.0, @babel/template@npm:^7.22.5":
version: 7.22.5
resolution: "@babel/template@npm:7.22.5"
@ -6110,6 +6117,13 @@ __metadata:
languageName: node
linkType: hard
"@transistorsoft/background-geolocation-types@npm:^5.0.1":
version: 5.0.1
resolution: "@transistorsoft/background-geolocation-types@npm:5.0.1"
checksum: 10/14e8a1e64f653f59c8ff22ecb576fc79b632278acdd1bb70a84d938d943f98bd51c620827a0036c4e486e2eaac255101686c40487904b1c866e7e261867faac0
languageName: node
linkType: hard
"@turf/along@npm:^7.1.0":
version: 7.1.0
resolution: "@turf/along@npm:7.1.0"
@ -7127,7 +7141,7 @@ __metadata:
react-native-animatable: "npm:^1.3.3"
react-native-app-link: "npm:^1.0.1"
react-native-background-fetch: "npm:^4.2.8"
react-native-background-geolocation: "npm:^4.19.0"
react-native-background-geolocation: "npm:^5.0.1"
react-native-battery-optimization-check: "npm:^1.0.8"
react-native-clean-project: "npm:^4.0.3"
react-native-contact-pick: "npm:^0.1.2"
@ -16713,19 +16727,14 @@ __metadata:
languageName: node
linkType: hard
"react-native-background-fetch@npm:~4.2.6":
version: 4.2.7
resolution: "react-native-background-fetch@npm:4.2.7"
checksum: 10/f0b3d008379dbd992cc1d9708ba1dae86848226a3f4644a8e5955d805841e3122ad56be28bea79af3fa6a03fa5e4f4b8ebf593eacee4dc5ef1ec8cd2dfd5f379
languageName: node
linkType: hard
"react-native-background-geolocation@npm:^4.19.0":
version: 4.19.0
resolution: "react-native-background-geolocation@npm:4.19.0"
"react-native-background-geolocation@npm:^5.0.1":
version: 5.0.1
resolution: "react-native-background-geolocation@npm:5.0.1"
dependencies:
react-native-background-fetch: "npm:~4.2.6"
checksum: 10/0ef8a52737ff77b2f04b4c6b1f806abc576b85570c4eba0274e9d2ea0b8393ccd96c7c24c1c5b067f4111b317a958b6d3760dc2a63b733ebecd6af63306a7191
"@babel/runtime": "npm:^7.26.0"
"@transistorsoft/background-geolocation-types": "npm:^5.0.1"
tslib: "npm:^2.6.3"
checksum: 10/7d994a2403228669140ffed55019041d0b2aab08b9de3dd4cbbfb26ea6378488a3c4d5fc0779f6138ddacb796df21363cafab996c0d83a86cd213975c1abc56c
languageName: node
linkType: hard
@ -19092,6 +19101,13 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^2.6.3":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7
languageName: node
linkType: hard
"tween-functions@npm:^1.0.1":
version: 1.2.0
resolution: "tween-functions@npm:1.2.0"