import { App } from '@capacitor/app'; import { Capacitor, registerPlugin } from '@capacitor/core'; import { getToken } from './auth.js'; import { apiGet, apiPostUrlEncoded, getApiBase } from './api.js'; const TRACKING_CONTEXT_PATH = '/cartography/tracking-context'; const TRACKING_UPDATE_PATH = '/cartography/update-location'; const TRACKING_SYNC_INTERVAL_MS = 120000; const MIN_SEND_INTERVAL_MS = 60000; const MIN_MOVE_METERS = 10; const BackgroundTracking = registerPlugin('BackgroundTracking'); let trackingBootstrapped = false; let trackingWatchId = null; let trackingSyncTimer = null; let appStateListener = null; let currentContext = null; let lastSent = null; function isNativeAndroid() { return Capacitor.getPlatform() === 'android'; } function buildAbsoluteUrl(path) { if (path.startsWith('http')) { return path; } return `${getApiBase()}${path}`; } async function configureNativeTracking() { if (!isNativeAndroid()) { return; } try { await BackgroundTracking.configure({ token: getToken(), updateUrl: buildAbsoluteUrl(TRACKING_UPDATE_PATH), intervalMs: TRACKING_SYNC_INTERVAL_MS, }); } catch { // Le fallback JS reste actif si le plugin n'est pas disponible. } } async function startNativeTracking() { if (!isNativeAndroid()) { return; } try { await configureNativeTracking(); await BackgroundTracking.start(); await BackgroundTracking.syncOnce(); } catch { // On garde le watch JS comme filet de sécurité. } } async function stopNativeTracking() { if (!isNativeAndroid()) { return; } try { await BackgroundTracking.stop(); } catch { // Rien à remonter à l'utilisateur. } } function hasGeolocationSupport() { return typeof navigator !== 'undefined' && !!navigator.geolocation; } function toRadians(value) { return (value * Math.PI) / 180; } function distanceMeters(startLat, startLng, endLat, endLng) { const earthRadius = 6371000; const deltaLat = toRadians(endLat - startLat); const deltaLng = toRadians(endLng - startLng); const a = Math.sin(deltaLat / 2) ** 2 + Math.cos(toRadians(startLat)) * Math.cos(toRadians(endLat)) * Math.sin(deltaLng / 2) ** 2; return earthRadius * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function shouldSendPosition(coords) { if (!lastSent) return true; const now = Date.now(); const moved = distanceMeters(lastSent.lat, lastSent.lng, coords.latitude, coords.longitude); const elapsed = now - lastSent.sentAt; return moved >= MIN_MOVE_METERS || elapsed >= MIN_SEND_INTERVAL_MS; } async function pushPosition(position) { const coords = position?.coords; if (!coords || !shouldSendPosition(coords)) { return; } try { await apiPostUrlEncoded(TRACKING_UPDATE_PATH, { lat: coords.latitude, lng: coords.longitude, accuracy: coords.accuracy ?? '', heading: coords.heading ?? '', speed: coords.speed ?? '', source: 'mobile_auto_tracking', }); lastSent = { lat: Number(coords.latitude), lng: Number(coords.longitude), sentAt: Date.now(), }; } catch { // Ignore réseau/transitoire: une nouvelle tentative partira au prochain point GPS. } } function clearWatch() { if (trackingWatchId !== null && hasGeolocationSupport()) { navigator.geolocation.clearWatch(trackingWatchId); } trackingWatchId = null; } function startWatch() { if (trackingWatchId !== null || !hasGeolocationSupport()) { return; } navigator.geolocation.getCurrentPosition((position) => { pushPosition(position); }, () => { // Permission refusée ou GPS indisponible: on reste silencieux. }, { enableHighAccuracy: true, maximumAge: 15000, timeout: 20000, }); trackingWatchId = navigator.geolocation.watchPosition((position) => { pushPosition(position); }, () => { // Rien à afficher dans l'UI: le service réessaiera au prochain sync. }, { enableHighAccuracy: true, maximumAge: 15000, timeout: 20000, }); } function startPeriodicSync() { if (trackingSyncTimer !== null) { return; } trackingSyncTimer = window.setInterval(() => { syncTrackingService().catch(() => {}); }, TRACKING_SYNC_INTERVAL_MS); } function stopPeriodicSync() { if (trackingSyncTimer !== null) { window.clearInterval(trackingSyncTimer); } trackingSyncTimer = null; } async function fetchTrackingContext() { try { const payload = await apiGet(TRACKING_CONTEXT_PATH); return payload?.ok ? payload : null; } catch { return null; } } export async function syncTrackingService() { if (!getToken()) { stopTrackingService(); return null; } const context = await fetchTrackingContext(); currentContext = context; if (!context?.should_track) { clearWatch(); await stopNativeTracking(); return context; } await startNativeTracking(); startWatch(); startPeriodicSync(); return context; } export function stopTrackingService() { clearWatch(); stopPeriodicSync(); stopNativeTracking(); currentContext = null; lastSent = null; } export function initializeTrackingService() { if (trackingBootstrapped) { return; } trackingBootstrapped = true; startPeriodicSync(); document.addEventListener('visibilitychange', () => { if (!document.hidden) { syncTrackingService().catch(() => {}); } }); window.addEventListener('focus', () => { syncTrackingService().catch(() => {}); }); if (App && typeof App.addListener === 'function') { appStateListener = App.addListener('appStateChange', ({ isActive }) => { if (isActive) { syncTrackingService().catch(() => {}); } }); } }