const ANONYMOUS_ID_STORAGE_KEY = "agent-native.anonymous_id"; const SESSION_ID_STORAGE_KEY = "agent-native.session_id"; const SESSION_LAST_ACTIVITY_STORAGE_KEY = "agent-native.session_last_activity"; // 30-minute idle timeout matches GA4 / Mixpanel defaults: a tab left open // overnight starts a new session in the morning instead of stretching one visit. const SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000; function generateVisitorId(): string { try { if ( typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ) { return crypto.randomUUID(); } } catch { // fall through to Math.random } return ( Date.now().toString(36) + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2) ); } function safeStorageGet(key: string): string | null { try { return window.localStorage.getItem(key); } catch { return null; } } function safeStorageSet(key: string, value: string): void { try { window.localStorage.setItem(key, value); } catch { // private browsing / storage disabled -- best-effort } } export function getOrCreateAnalyticsAnonymousId(): string | undefined { if (typeof window === "undefined") return undefined; let id = safeStorageGet(ANONYMOUS_ID_STORAGE_KEY); if (!id) { id = generateVisitorId(); safeStorageSet(ANONYMOUS_ID_STORAGE_KEY, id); } return id; } export function getOrCreateAnalyticsSessionId(): string | undefined { if (typeof window === "undefined") return undefined; const now = Date.now(); const lastActivityRaw = safeStorageGet(SESSION_LAST_ACTIVITY_STORAGE_KEY); const lastActivity = lastActivityRaw ? Number.parseInt(lastActivityRaw, 10) : 0; let id = safeStorageGet(SESSION_ID_STORAGE_KEY); const expired = !lastActivity || Number.isNaN(lastActivity) || now - lastActivity > SESSION_IDLE_TIMEOUT_MS; if (!id || expired) { id = generateVisitorId(); safeStorageSet(SESSION_ID_STORAGE_KEY, id); } safeStorageSet(SESSION_LAST_ACTIVITY_STORAGE_KEY, String(now)); return id; }