const WORLD_ANALYTICS_ENDPOINT = '/api/worlds/analytics/events' const VISITOR_STORAGE_KEY = 'skinbase:world-analytics-visitor' const SOURCE_PARAM = 'world_source' const SOURCE_DETAIL_PARAM = 'world_source_detail' const IMPRESSION_KEYS = new Set() const ALLOWED_SOURCES = new Set([ 'homepage_spotlight', 'homepage_worlds_rail', 'worlds_index', 'navigation', 'upload_flow', 'challenge_page', 'news_article', 'profile', 'direct', 'unknown', ]) function csrfToken() { return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' } function randomToken() { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID() } return `w-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}` } function normalizeSourceSurface(value) { const normalized = String(value || '').trim() return ALLOWED_SOURCES.has(normalized) ? normalized : '' } function sanitizeDetail(value) { return String(value || '').trim().slice(0, 80) } function impressionKey({ worldId, sourceSurface, sourceDetail = '', sectionKey = '' }) { return [worldId, sourceSurface, sanitizeDetail(sourceDetail), String(sectionKey || '').trim()].join(':') } export function worldAnalyticsVisitorToken() { try { const existing = window.localStorage?.getItem(VISITOR_STORAGE_KEY) if (existing) { return existing } const next = randomToken() window.localStorage?.setItem(VISITOR_STORAGE_KEY, next) return next } catch { return randomToken() } } export function withWorldSource(url, sourceSurface, sourceDetail = '') { if (!url) { return url } try { const parsed = new URL(url, window.location.origin) if (parsed.origin !== window.location.origin) { return url } const normalizedSource = normalizeSourceSurface(sourceSurface) if (normalizedSource) { parsed.searchParams.set(SOURCE_PARAM, normalizedSource) } const normalizedDetail = sanitizeDetail(sourceDetail) if (normalizedDetail) { parsed.searchParams.set(SOURCE_DETAIL_PARAM, normalizedDetail) } return `${parsed.pathname}${parsed.search}${parsed.hash}` } catch { return url } } export function resolveWorldLandingSource() { try { const locationUrl = new URL(window.location.href) const explicitSource = normalizeSourceSurface(locationUrl.searchParams.get(SOURCE_PARAM)) const explicitDetail = sanitizeDetail(locationUrl.searchParams.get(SOURCE_DETAIL_PARAM)) if (explicitSource) { return { sourceSurface: explicitSource, sourceDetail: explicitDetail, } } if (!document.referrer) { return { sourceSurface: 'direct', sourceDetail: '' } } const referrer = new URL(document.referrer) if (referrer.origin !== window.location.origin) { return { sourceSurface: 'unknown', sourceDetail: 'external_referrer' } } const path = referrer.pathname || '/' if (path === '/') { return { sourceSurface: 'homepage_spotlight', sourceDetail: 'referrer' } } if (path === '/worlds') { return { sourceSurface: 'worlds_index', sourceDetail: 'referrer' } } if (/^\/groups\/[^/]+\/challenges\//.test(path)) { return { sourceSurface: 'challenge_page', sourceDetail: 'referrer' } } if (path.startsWith('/upload') || path.startsWith('/studio/artworks')) { return { sourceSurface: 'upload_flow', sourceDetail: 'referrer' } } if (path.startsWith('/news') || path.startsWith('/stories')) { return { sourceSurface: 'news_article', sourceDetail: 'referrer' } } if (path.startsWith('/@') || path.startsWith('/profile')) { return { sourceSurface: 'profile', sourceDetail: 'referrer' } } } catch { return { sourceSurface: 'unknown', sourceDetail: '' } } return { sourceSurface: 'unknown', sourceDetail: '' } } export async function trackWorldAnalytics(eventType, payload = {}) { try { if (!eventType || !payload.world_id) { return } await fetch(WORLD_ANALYTICS_ENDPOINT, { method: 'POST', credentials: 'same-origin', keepalive: true, headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'X-Requested-With': 'XMLHttpRequest', }, body: JSON.stringify({ event_type: eventType, visitor_token: worldAnalyticsVisitorToken(), ...payload, }), }) } catch { // Best-effort analytics only. } } export function trackWorldSourceClick({ worldId, worldTitle = '', sourceSurface = '', sourceDetail = '' }) { const normalizedSource = normalizeSourceSurface(sourceSurface) if (!worldId || !normalizedSource) { return } trackWorldAnalytics('world_source_clicked', { world_id: worldId, source_surface: normalizedSource, source_detail: sanitizeDetail(sourceDetail), entity_type: 'world', entity_id: worldId, entity_title: worldTitle, }) } export function trackWorldSourceImpression({ worldId, worldTitle = '', sourceSurface = '', sourceDetail = '', sectionKey = '', }) { const normalizedSource = normalizeSourceSurface(sourceSurface) if (!worldId || !normalizedSource) { return } const key = impressionKey({ worldId, sourceSurface: normalizedSource, sourceDetail, sectionKey }) if (IMPRESSION_KEYS.has(key)) { return } IMPRESSION_KEYS.add(key) trackWorldAnalytics('world_source_impression', { world_id: worldId, source_surface: normalizedSource, source_detail: sanitizeDetail(sourceDetail), section_key: String(sectionKey || '').trim().slice(0, 80), entity_type: 'world', entity_id: worldId, entity_title: worldTitle, }) }