fix(track-location): try 8

This commit is contained in:
devthejo 2026-01-23 01:44:59 +01:00
parent f4f7708e71
commit 7ba78c7334
No known key found for this signature in database
GPG key ID: 00CCA7A92B1D5351
3 changed files with 185 additions and 53 deletions

View file

@ -24,6 +24,11 @@ Applies to the BackgroundGeolocation integration:
- When authenticated, each persisted location should upload immediately via native HTTP (works while JS is suspended).
- Pre-auth: tracking may persist locally but `http.url` is empty so nothing is uploaded until auth is ready.
- Stationary noise suppression:
- Native accuracy gate for persisted/uploaded locations: `geolocation.filter.trackingAccuracyThreshold: 100`.
- Identical location suppression: `geolocation.allowIdenticalLocations: false`.
- Extra safety: any JS-triggered persisted fix requests are tagged and ignored if accuracy > 100m.
## Basic preconditions
- Location permissions: foreground + background granted.
@ -103,6 +108,14 @@ Applies to the BackgroundGeolocation integration:
- 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).
- Attribution for `getCurrentPosition`:
- `Location update received` logs include `extras.req_reason` and `extras.req_persist`.
- Persisted-fix reasons to expect: `active_profile_enter`, `moving_edge`, `startup_fix`, `identity_fix:*`.
- Accuracy gate signals:
- A persisted-fix request can be logged but later ignored due to accuracy > 100m.
- If the server still receives periodic updates while stationary, check that the uploaded record has acceptable accuracy and that the device isn't flapping between moving/stationary.
## Debugging tips
- Observe logs in app:

View file

@ -1,6 +1,18 @@
import BackgroundGeolocation from "react-native-background-geolocation";
import env from "~/env";
const LOCATION_ACCURACY_GATE_M = 100;
// Native filter to reduce GPS drift and suppress stationary jitter.
// This is the primary mechanism to prevent unwanted persisted/uploaded points while the device
// is not moving (works even when JS is suspended).
const DEFAULT_LOCATION_FILTER = {
policy: BackgroundGeolocation.LocationFilterPolicy.Conservative,
useKalman: true,
kalmanProfile: BackgroundGeolocation.KalmanProfile.Conservative,
trackingAccuracyThreshold: LOCATION_ACCURACY_GATE_M,
};
// Common config: keep always-on tracking enabled, but default to an IDLE low-power profile.
// High-accuracy and tighter distance thresholds are enabled only when an active alert is open.
//
@ -55,6 +67,13 @@ export const BASE_GEOLOCATION_CONFIG = {
// NOTE: historically we set this at top-level. In v5 the knob is under `geolocation`.
stopTimeout: 5,
// Prevent identical/noise locations from being persisted.
// This reduces DB churn and avoids triggering native HTTP uploads with redundant points.
allowIdenticalLocations: false,
// Native-side filter (see DEFAULT_LOCATION_FILTER above).
filter: DEFAULT_LOCATION_FILTER,
// Permission request strategy
locationAuthorizationRequest: "Always",
},
@ -153,6 +172,10 @@ export const TRACKING_PROFILES = {
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
// Defensive: keep the distanceFilter conservative to avoid battery drain.
distanceFilter: 200,
// Keep filtering enabled across profile transitions.
filter: DEFAULT_LOCATION_FILTER,
allowIdenticalLocations: false,
},
app: {
// Never use heartbeat-driven updates; only movement-driven.
@ -171,6 +194,10 @@ export const TRACKING_PROFILES = {
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
// ACTIVE target: frequent updates while moving.
distanceFilter: 25,
// Apply the same native filter while ACTIVE.
filter: DEFAULT_LOCATION_FILTER,
allowIdenticalLocations: false,
},
app: {
// Never use heartbeat-driven updates; only movement-driven.

View file

@ -56,6 +56,49 @@ export default function trackLocation() {
const MOVING_EDGE_COOLDOWN_MS = 5 * 60 * 1000;
const BAD_ACCURACY_THRESHOLD_M = 200;
const PERSISTED_ACCURACY_GATE_M = 100;
const shouldUseLocationForUi = (location) => {
const acc = location?.coords?.accuracy;
return !(typeof acc === "number" && acc > BAD_ACCURACY_THRESHOLD_M);
};
// Gate persisted/uploaded points (native layer also filters, but this protects any
// JS-triggered persisted-fix code-paths).
const shouldAllowPersistedFix = (location) => {
const acc = location?.coords?.accuracy;
return !(typeof acc === "number" && acc > PERSISTED_ACCURACY_GATE_M);
};
const getCurrentPositionWithDiagnostics = async (
options,
{ reason, persist },
) => {
const opts = {
...options,
persist,
extras: {
...(options?.extras || {}),
// Attribution for log correlation.
req_reason: reason,
req_persist: !!persist,
req_at: new Date().toISOString(),
req_app_state: appState,
req_profile: currentProfile,
},
};
locationLogger.debug("Requesting getCurrentPosition", {
reason,
persist: !!persist,
desiredAccuracy: opts?.desiredAccuracy,
samples: opts?.samples,
maximumAge: opts?.maximumAge,
timeout: opts?.timeout,
});
return BackgroundGeolocation.getCurrentPosition(opts);
};
// Track identity so we can force a first geopoint when the effective user changes.
let lastSessionUserId = null;
@ -145,9 +188,9 @@ export default function trackLocation() {
const requestIdentityPersistedFixAndSync = async ({ reason, userId }) => {
try {
const t0 = Date.now();
const location = await BackgroundGeolocation.getCurrentPosition({
const location = await getCurrentPositionWithDiagnostics(
{
samples: 1,
persist: true,
timeout: 30,
maximumAge: 0,
desiredAccuracy: 50,
@ -156,7 +199,20 @@ export default function trackLocation() {
identity_reason: reason,
session_user_id: userId,
},
});
},
{ reason: `identity_fix:${reason}`, persist: true },
);
if (!shouldAllowPersistedFix(location)) {
locationLogger.info(
"Identity persisted fix ignored due to poor accuracy",
{
reason,
userId,
accuracy: location?.coords?.accuracy,
},
);
}
locationLogger.info("Identity persisted fix acquired", {
reason,
userId,
@ -206,16 +262,29 @@ export default function trackLocation() {
}
const t0 = Date.now();
const location = await BackgroundGeolocation.getCurrentPosition({
const location = await getCurrentPositionWithDiagnostics(
{
samples: 1,
persist: true,
timeout: 30,
maximumAge: 10000,
desiredAccuracy: 100,
extras: {
startup_fix: true,
},
});
},
{ reason: "startup_fix", persist: true },
);
if (!shouldAllowPersistedFix(location)) {
locationLogger.info(
"Startup persisted fix ignored due to poor accuracy",
{
accuracy: location?.coords?.accuracy,
timestamp: location?.timestamp,
},
);
return;
}
locationLogger.info("Startup persisted fix acquired", {
ms: Date.now() - t0,
@ -290,19 +359,18 @@ export default function trackLocation() {
return;
}
const location = await BackgroundGeolocation.getCurrentPosition({
const location = await getCurrentPositionWithDiagnostics(
{
samples: 1,
// IMPORTANT: do not persist by default.
// Persisting will create a DB record and the SDK may upload it on resume,
// which is the source of "updates while not moved" on some devices.
persist: false,
timeout: 20,
maximumAge: 10000,
desiredAccuracy: 100,
extras: {
auth_token_update: true,
},
});
},
{ reason: "auth_change_ui_fix", persist: false },
);
// If the fix is very poor accuracy, treat it as noise and do nothing.
// (We intentionally do not persist in this path.)
@ -403,16 +471,28 @@ export default function trackLocation() {
// motion-detection / distanceFilter to produce the first point.
try {
const beforeFix = Date.now();
const fix = await BackgroundGeolocation.getCurrentPosition({
const fix = await getCurrentPositionWithDiagnostics(
{
samples: 3,
persist: true,
timeout: 30,
maximumAge: 0,
desiredAccuracy: 10,
extras: {
active_profile_enter: true,
},
});
},
{ reason: "active_profile_enter", persist: true },
);
if (!shouldAllowPersistedFix(fix)) {
locationLogger.info(
"ACTIVE immediate persisted fix ignored due to poor accuracy",
{
accuracy: fix?.coords?.accuracy,
},
);
return;
}
locationLogger.info("ACTIVE immediate fix acquired", {
ms: Date.now() - beforeFix,
accuracy: fix?.coords?.accuracy,
@ -697,6 +777,7 @@ export default function trackLocation() {
activity: location.activity,
battery: location.battery,
sample: location.sample,
extras: location.extras,
});
// Ignore sampling locations (eg, emitted during getCurrentPosition) to avoid UI/storage churn.
@ -705,10 +786,9 @@ export default function trackLocation() {
// Quality gate (UI-only): if accuracy is very poor, ignore for UI/state.
// Do NOT delete the record here; native uploads may happen while JS is suspended.
const acc = location?.coords?.accuracy;
if (typeof acc === "number" && acc > BAD_ACCURACY_THRESHOLD_M) {
if (!shouldUseLocationForUi(location)) {
locationLogger.info("Ignoring poor-accuracy location", {
accuracy: acc,
accuracy: location?.coords?.accuracy,
uuid: location?.uuid,
});
return;
@ -830,16 +910,28 @@ export default function trackLocation() {
lastMovingEdgeAt = now;
(async () => {
try {
const fix = await BackgroundGeolocation.getCurrentPosition({
const fix = await getCurrentPositionWithDiagnostics(
{
samples: 1,
persist: true,
timeout: 30,
maximumAge: 0,
desiredAccuracy: 50,
extras: {
moving_edge: true,
},
});
},
{ reason: "moving_edge", persist: true },
);
if (!shouldAllowPersistedFix(fix)) {
locationLogger.info(
"Moving-edge persisted fix ignored due to poor accuracy",
{
accuracy: fix?.coords?.accuracy,
},
);
return;
}
locationLogger.info("Moving-edge fix acquired", {
accuracy: fix?.coords?.accuracy,
latitude: fix?.coords?.latitude,