import { getApiUrl } from "./apiConfig"; // Assuming this correctly returns the server's base URL // --- Constants --- const VISITOR_ID_KEY = "gda.visitorId"; const SESSION_MARK_KEY = "gda.visitRecorded"; const LOCATION_CACHE_KEY = "gda.coarseLocation"; // --- Core Helper Functions (These are well-written and retained) --- function generateVisitorId() { if (typeof crypto !== "undefined" && crypto.randomUUID) { return crypto.randomUUID(); } const random = Math.random().toString(36).slice(2); const time = Date.now().toString(36); return `v_${time}_${random}`; } function getOrCreateVisitorId() { if (typeof window === "undefined") return null; try { const existing = localStorage.getItem(VISITOR_ID_KEY); if (existing) return existing; const id = generateVisitorId(); localStorage.setItem(VISITOR_ID_KEY, id); return id; } catch { return generateVisitorId(); // Fallback for environments where localStorage might fail } } /** * A generic function to send any tracking event to the server. * This aligns with the server's `/api/track` endpoint and Pydantic model. * @param {string} eventType - The type of event (e.g., 'page_view', 'file_upload_start'). * @param {object} eventData - A dictionary of additional data related to the event. */ export async function trackEvent(eventType, eventData = {}) { if (typeof window === "undefined") return; const clientId = getOrCreateVisitorId(); if (!clientId) return; const payload = { client_id: clientId, event_type: eventType, event_data: eventData, }; // This is a "fire-and-forget" request. We don't wait for the response, // and we use `keepalive` to ensure the request is sent even if the user // is navigating away from the page. try { fetch(getApiUrl("/api/track"), { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), keepalive: true, }); } catch (error) { console.warn("Could not send tracking event to server:", error); } } /** * Records a single page visit per session to the server. * This now uses the generic `trackEvent` function. */ export async function recordVisit() { if (typeof window === "undefined") return; // Use sessionStorage to ensure this only runs once per browser session. try { if (sessionStorage.getItem(SESSION_MARK_KEY)) { return; } sessionStorage.setItem(SESSION_MARK_KEY, "1"); } catch { // If sessionStorage fails, proceed anyway but might record multiple visits. } // Get location data to include in the event const location = await getCoarseLocation(); trackEvent('page_view', { path: `${window.location.pathname}${window.location.search}${window.location.hash}`, referrer: document.referrer, location: location, }); } /** * Fetches the user's coarse location using the Geolocation API. * This is well-written and retained. * @returns {Promise} */ export function getCoarseLocation() { if (typeof window === "undefined") return Promise.resolve(null); try { const cached = sessionStorage.getItem(LOCATION_CACHE_KEY); if (cached) return Promise.resolve(JSON.parse(cached)); } catch { /* Ignore cache errors */ } if (!("geolocation" in navigator)) return Promise.resolve(null); return new Promise((resolve) => { navigator.geolocation.getCurrentPosition( (position) => { const location = { lat: position.coords.latitude, lon: position.coords.longitude, accuracyMeters: position.coords.accuracy, }; try { sessionStorage.setItem(LOCATION_CACHE_KEY, JSON.stringify(location)); } catch { /* Ignore cache errors */ } resolve(location); }, () => resolve(null), { enableHighAccuracy: false, maximumAge: 10 * 60 * 1000, // 10 minutes timeout: 8000, } ); }); }