fix(track-location): try 4

This commit is contained in:
devthejo 2026-01-22 12:42:05 +01:00
parent 6c8153bdb1
commit c7d0b36f1b
No known key found for this signature in database
GPG key ID: 00CCA7A92B1D5351
2 changed files with 111 additions and 7 deletions

View file

@ -74,7 +74,10 @@ export const BASE_GEOLOCATION_CONFIG = {
method: "POST",
httpRootProperty: "location",
batchSync: false,
autoSync: true,
// We intentionally disable autoSync and perform controlled uploads from explicit triggers
// (startup, identity-change, moving-edge, active-alert). This prevents stationary "ghost"
// uploads from low-quality locations produced by some Android devices.
autoSync: false,
// Persistence: keep enough records for offline catch-up.
// (The SDK already constrains with maxDaysToPersist; records are deleted after successful upload.)
@ -101,6 +104,7 @@ export const BASE_GEOLOCATION_INVARIANTS = {
speedJumpFilter: 100,
method: "POST",
httpRootProperty: "location",
autoSync: false,
maxRecordsToPersist: 1000,
maxDaysToPersist: 7,
};
@ -113,10 +117,9 @@ export const TRACKING_PROFILES = {
// only periodic fixes (several times/hour). Note many config options like
// `distanceFilter` / `stationaryRadius` are documented as having little/no
// effect in this mode.
// Some iOS devices / user settings can result in unreliable significant-change wakeups.
// We keep SLC for Android (battery), but fall back to standard motion tracking on iOS
// with a conservative distanceFilter.
useSignificantChangesOnly: Platform.OS !== "ios",
// Some devices / OEMs can be unreliable with significant-change only.
// Use standard motion tracking for reliability, with conservative distanceFilter.
useSignificantChangesOnly: false,
// Defensive: if some devices/platform conditions fall back to standard tracking,
// keep the distanceFilter conservative to avoid battery drain.

View file

@ -50,12 +50,46 @@ export default function trackLocation() {
let didDisableUploadsForAnonymous = false;
let didSyncAfterAuth = false;
let didSyncAfterStartupFix = false;
let lastMovingEdgeAt = 0;
const MOVING_EDGE_COOLDOWN_MS = 5 * 60 * 1000;
const BAD_ACCURACY_THRESHOLD_M = 200;
// Track identity so we can force a first geopoint when the effective user changes.
let lastSessionUserId = null;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const pruneBadLocations = async () => {
try {
const locations = await BackgroundGeolocation.getLocations();
if (!Array.isArray(locations) || !locations.length) return 0;
let deleted = 0;
// Defensive: only scan a bounded amount to avoid heavy work.
const toScan = locations.slice(-200);
for (const loc of toScan) {
const acc = loc?.coords?.accuracy;
const uuid = loc?.uuid;
if (
typeof acc === "number" &&
acc > BAD_ACCURACY_THRESHOLD_M &&
uuid
) {
try {
await BackgroundGeolocation.destroyLocation(uuid);
deleted++;
} catch (e) {
// ignore
}
}
}
return deleted;
} catch (e) {
return 0;
}
};
const safeSync = async (reason) => {
// Sync can fail transiently (SDK busy, network warming up, etc). Retry a few times.
for (let attempt = 1; attempt <= 3; attempt++) {
@ -65,6 +99,8 @@ export default function trackLocation() {
BackgroundGeolocation.getCount(),
]);
const pruned = await pruneBadLocations();
locationLogger.info("Attempting BGGeo sync", {
reason,
attempt,
@ -72,6 +108,7 @@ export default function trackLocation() {
isMoving: state?.isMoving,
trackingMode: state?.trackingMode,
pendingBefore,
pruned,
});
const records = await BackgroundGeolocation.sync();
@ -421,7 +458,7 @@ export default function trackLocation() {
} catch (e) {
currentSessionUserId = null;
}
if (!userToken) {
if (!userToken && !currentSessionUserId) {
// Pre-login mode: keep tracking enabled but disable uploads.
// Also applies to logout: keep tracking on (per product requirement: track all the time),
// but stop sending anything to server without auth.
@ -491,7 +528,7 @@ export default function trackLocation() {
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
autoSync: true,
autoSync: false,
headers: {
Authorization: `Bearer ${userToken}`,
},
@ -618,6 +655,30 @@ export default function trackLocation() {
// The final persisted location will arrive with sample=false.
if (location.sample) return;
// Quality gate: delete very poor-accuracy locations to prevent them from being synced
// and ignore them for UI/state.
const acc = location?.coords?.accuracy;
if (typeof acc === "number" && acc > BAD_ACCURACY_THRESHOLD_M) {
locationLogger.info("Ignoring poor-accuracy location", {
accuracy: acc,
uuid: location?.uuid,
});
if (location?.uuid) {
try {
await BackgroundGeolocation.destroyLocation(location.uuid);
locationLogger.debug("Destroyed poor-accuracy location", {
uuid: location.uuid,
});
} catch (e) {
locationLogger.warn("Failed to destroy poor-accuracy location", {
uuid: location?.uuid,
error: e?.message,
});
}
}
return;
}
if (
location.coords &&
location.coords.latitude &&
@ -681,6 +742,46 @@ export default function trackLocation() {
isMoving: event?.isMoving,
location: event?.location?.coords,
});
// Moving-edge strategy: when we enter moving state, force one persisted high-quality
// point + sync so the server gets a quick update.
//
// IMPORTANT: Restrict this to ACTIVE tracking only. On Android, motion detection can
// produce false-positive moving transitions while the device is stationary (screen-off),
// which would otherwise trigger unwanted background uploads.
// Cooldown to avoid repeated work due to motion jitter.
if (event?.isMoving && authReady && currentProfile === "active") {
const now = Date.now();
if (now - lastMovingEdgeAt >= MOVING_EDGE_COOLDOWN_MS) {
lastMovingEdgeAt = now;
(async () => {
try {
const fix = await BackgroundGeolocation.getCurrentPosition({
samples: 1,
persist: true,
timeout: 30,
maximumAge: 0,
desiredAccuracy: 50,
extras: {
moving_edge: true,
},
});
locationLogger.info("Moving-edge fix acquired", {
accuracy: fix?.coords?.accuracy,
latitude: fix?.coords?.latitude,
longitude: fix?.coords?.longitude,
timestamp: fix?.timestamp,
});
} catch (e) {
locationLogger.warn("Moving-edge fix failed", {
error: e?.message,
stack: e?.stack,
});
}
await safeSync("moving-edge");
})();
}
}
},
onActivityChange: (event) => {
locationLogger.info("Activity change", {