Spaces:
Running
Running
File size: 3,966 Bytes
fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f 93a912e fc288c8 93a912e fc288c8 f0abe95 fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f 93a912e f0abe95 93a912e fc288c8 93a912e f0abe95 93a912e be9292f fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f fc288c8 be9292f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 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<object|null>}
*/
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,
}
);
});
}
|