/** * AttractOS Tracking Snippet * Tracks LLM referrals and conversions * ~2KB minified */ (function(w, d) { 'use strict'; // Get site key from script tag var scripts = d.getElementsByTagName('script'); var currentScript = scripts[scripts.length - 1]; var siteKey = currentScript.getAttribute('data-key'); if (!siteKey) { console.warn('[AttractOS] Missing data-key attribute'); return; } var ENDPOINT = 'https://attractos.com/api/t'; var STORAGE_KEY = 'attractos_v'; var SESSION_KEY = 'attractos_s'; // LLM referrer patterns - domain to source mapping var LLM_REFERRERS = { 'chat.openai.com': 'chatgpt', 'chatgpt.com': 'chatgpt', 'openai.com': 'chatgpt', 'claude.ai': 'claude', 'anthropic.com': 'claude', 'perplexity.ai': 'perplexity', 'gemini.google.com': 'gemini', 'bard.google.com': 'gemini', 'copilot.microsoft.com': 'copilot', 'bing.com': 'copilot', 'meta.ai': 'meta-ai', 'grok.x.ai': 'grok', 'you.com': 'you', 'phind.com': 'phind', 'kagi.com': 'kagi', 'search.brave.com': 'brave-leo', 'duckduckgo.com': 'duckduckgo-ai' }; // Known LLM source values for UTM/param detection var LLM_SOURCES = [ 'chatgpt', 'openai', 'claude', 'anthropic', 'perplexity', 'gemini', 'bard', 'copilot', 'bing-chat', 'meta-ai', 'llama', 'grok', 'xai', 'you', 'phind', 'kagi', 'brave-leo', 'duckduckgo-ai' ]; /** * Detect LLM source from referrer */ function detectFromReferrer() { var ref = d.referrer; if (!ref) return null; try { var hostname = new URL(ref).hostname.toLowerCase(); for (var pattern in LLM_REFERRERS) { if (hostname.indexOf(pattern) !== -1) { return { source: LLM_REFERRERS[pattern], method: 'referrer' }; } } } catch (e) {} return null; } /** * Detect LLM source from URL parameters */ function detectFromParams() { var params = new URLSearchParams(w.location.search); var checkParams = ['utm_source', 'via', 'ref', 'source']; for (var i = 0; i < checkParams.length; i++) { var val = params.get(checkParams[i]); if (val) { val = val.toLowerCase(); for (var j = 0; j < LLM_SOURCES.length; j++) { if (val === LLM_SOURCES[j] || val.indexOf(LLM_SOURCES[j]) !== -1) { return { source: LLM_SOURCES[j], method: 'param' }; } } } } return null; } /** * Get or create visitor ID */ function getVisitorId() { var vid = null; try { vid = localStorage.getItem(STORAGE_KEY); if (!vid) { vid = Math.random().toString(36).substr(2, 9) + Date.now().toString(36); localStorage.setItem(STORAGE_KEY, vid); } } catch (e) { // localStorage not available, generate ephemeral ID vid = Math.random().toString(36).substr(2, 9); } return vid; } /** * Get or set session LLM source (persist across page views) */ function getSessionSource(newSource) { try { if (newSource) { sessionStorage.setItem(SESSION_KEY, JSON.stringify(newSource)); return newSource; } var stored = sessionStorage.getItem(SESSION_KEY); return stored ? JSON.parse(stored) : null; } catch (e) { return newSource || null; } } /** * Send tracking data */ function send(data) { data.k = siteKey; data.p = w.location.pathname; data.ts = Date.now(); // Use sendBeacon if available, fallback to fetch var payload = JSON.stringify(data); if (navigator.sendBeacon) { navigator.sendBeacon(ENDPOINT, payload); } else { fetch(ENDPOINT, { method: 'POST', body: payload, keepalive: true }).catch(function() {}); } } // Initialize var visitorId = getVisitorId(); // Detect LLM source (check params first, then referrer) var llmSource = detectFromParams() || detectFromReferrer(); // Persist LLM source in session (so it survives navigation) if (llmSource) { getSessionSource(llmSource); } else { llmSource = getSessionSource(); } // Track referral if LLM source detected if (llmSource) { send({ t: 'referral', v: visitorId, s: llmSource.source, m: llmSource.method }); } // Expose public API w.attractos = { /** * Track a conversion event * @param {string} eventName - Name of the event (e.g., 'signup', 'purchase') * @param {object} props - Optional properties (e.g., { value: 99.99 }) */ track: function(eventName, props) { props = props || {}; send({ t: 'conversion', v: visitorId, e: eventName, val: props.value, s: llmSource ? llmSource.source : null }); }, /** * Get the detected LLM source (if any) */ getSource: function() { return llmSource; }, /** * Get the visitor ID */ getVisitorId: function() { return visitorId; } }; // Debug mode if (w.location.search.indexOf('attractos_debug=1') !== -1) { console.log('[AttractOS] Initialized', { siteKey: siteKey, visitorId: visitorId, llmSource: llmSource }); } })(window, document);