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. // // Expected behavior (both Android + iOS): // - Foreground: locations recorded only after moving beyond `distanceFilter`. // - Background: same rule; native service continues even if JS is suspended. // - Terminated: // - Android: native service continues (`stopOnTerminate:false`); JS headless is NOT required. // - iOS: OS will relaunch app on significant movement / stationary-geofence exit. // // NOTE: We avoid creating persisted records from UI-only lookups (eg map refresh), since // persisted records can trigger native HTTP uploads even while stationary. // // Product goals: // - IDLE (no open alert): minimize battery; server updates are acceptable only on OS-level significant movement. // - ACTIVE (open alert): first location should reach server within seconds, then continuous distance-based updates. // // Notes: // - 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 = { reset: 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, // 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", }, // 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 = { 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: { 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, // Keep filtering enabled across profile transitions. filter: DEFAULT_LOCATION_FILTER, allowIdenticalLocations: false, }, 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: { geolocation: { 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. heartbeatInterval: 0, }, activity: { // Android-only: do not delay motion triggers while ACTIVE. motionTriggerDelay: 0, }, }, };