diff --git a/.env.default b/.env.default index 1c4e02a..f614c22 100644 --- a/.env.default +++ b/.env.default @@ -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 \ No newline at end of file +BACKGROUND_GEOLOCATION_LICENSE_ANDROID=your_license_key_here +BACKGROUND_GEOLOCATION_LICENSE_IOS=your_license_key_here \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dff6951..197d612 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -37,8 +37,7 @@ - - + diff --git a/android/build.gradle b/android/build.gradle index 6d14997..e811038 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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')) } diff --git a/app.config.js b/app.config.js index dc4bbe4..f77d0e1 100644 --- a/app.config.js +++ b/app.config.js @@ -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", { diff --git a/docs/config-files-requirements.md b/docs/config-files-requirements.md index e5c3e90..b4fb27d 100644 --- a/docs/config-files-requirements.md +++ b/docs/config-files-requirements.md @@ -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 diff --git a/docs/location-tracking-qa.md b/docs/location-tracking-qa.md index 8bbdc89..805cec7 100644 --- a/docs/location-tracking-qa.md +++ b/docs/location-tracking-qa.md @@ -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: diff --git a/ios/AlerteSecours/Info.plist b/ios/AlerteSecours/Info.plist index 987066e..61a2bc7 100644 --- a/ios/AlerteSecours/Info.plist +++ b/ios/AlerteSecours/Info.plist @@ -146,6 +146,8 @@ [Alerte-Secours] Cette application utilise les notifications pour pouvoir vous prévenir lorsque quelqu'un a besoin d'aide à proximité NSUserNotificationUsageDescription 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. + TSLocationManagerLicense + eyJhbGciOiJFZERTQSIsImtpZCI6ImVkMjU1MTktbWFpbi12MSJ9.eyJvcyI6ImlvcyIsImFwcF9pZCI6ImNvbS5hbGVydGVzZWNvdXJzLmFsZXJ0ZXNlY291cnMiLCJvcmRlcl9udW1iZXIiOjg2NDAsInJlbmV3YWxfdXJsIjoiaHR0cHM6Ly9zaG9wLnRyYW5zaXN0b3Jzb2Z0LmNvbS9jYXJ0LzE2NTA3ODYxNTA1OjE_bm90ZT02MTkzIiwiY3VzdG9tZXJfaWQiOjU3NDIsInByb2R1Y3QiOiJyZWFjdC1uYXRpdmUtYmFja2dyb3VuZC1nZW9sb2NhdGlvbiIsImtleV92ZXJzaW9uIjoxLCJhbGxvd2VkX3N1ZmZpeGVzIjpbIi5kZXYiLCIuZGV2ZWxvcG1lbnQiLCIuc3RhZ2luZyIsIi5zdGFnZSIsIi5xYSIsIi51YXQiLCIudGVzdCIsIi5kZWJ1ZyJdLCJtYXhfYnVpbGRfc3RhbXAiOjIwMjcwMjIyLCJncmFjZV9idWlsZHMiOjAsImVudGl0bGVtZW50cyI6WyJjb3JlIl0sImlhdCI6MTc2OTEyNjI4OX0.bOJmTwxkRlRVAMHuNeXUVlSFTQyai3wal2TEz6jlOvEHFadbP6WG0E_KUMatinSRgKEIxs0L_SmSn6G5eWAYDQ UIBackgroundModes fetch diff --git a/package.json b/package.json index 64f0cbb..e7acb0b 100644 --- a/package.json +++ b/package.json @@ -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" -} \ No newline at end of file +} diff --git a/src/location/backgroundGeolocationConfig.js b/src/location/backgroundGeolocationConfig.js index f89f714..648a0c3 100644 --- a/src/location/backgroundGeolocationConfig.js +++ b/src/location/backgroundGeolocationConfig.js @@ -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, + }, }, }; diff --git a/src/location/index.js b/src/location/index.js index 1760c33..8ca9952 100644 --- a/src/location/index.js +++ b/src/location/index.js @@ -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); diff --git a/src/location/trackLocation.js b/src/location/trackLocation.js index f077308..64a3774 100644 --- a/src/location/trackLocation.js +++ b/src/location/trackLocation.js @@ -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. // diff --git a/yarn.lock b/yarn.lock index c825b64..be03201 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"