@loamly/tracker 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +411 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.mjs +411 -1
- package/dist/index.mjs.map +1 -1
- package/dist/loamly.iife.global.js +411 -1
- package/dist/loamly.iife.global.js.map +1 -1
- package/dist/loamly.iife.min.global.js +1 -1
- package/dist/loamly.iife.min.global.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +1 -1
- package/src/core.ts +135 -1
- package/src/detection/behavioral-classifier.ts +489 -0
- package/src/detection/index.ts +6 -2
- package/src/types.ts +21 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/config.ts","../src/detection/navigation-timing.ts","../src/detection/referrer.ts","../src/utils.ts","../src/core.ts"],"sourcesContent":["/**\n * Loamly Tracker - Browser Bundle (IIFE)\n * \n * This file is the entry point for the browser script tag version.\n * It auto-initializes from data attributes on the script tag.\n * \n * Usage:\n * <script src=\"https://unpkg.com/@loamly/tracker\" data-api-key=\"your-key\"></script>\n * \n * @module @loamly/tracker/browser\n */\n\nimport { loamly } from './core'\nimport type { LoamlyConfig } from './types'\n\n// Auto-initialize from script tag data attributes\nfunction autoInit(): void {\n // Find the script tag that loaded us\n const scripts = document.getElementsByTagName('script')\n let scriptTag: HTMLScriptElement | null = null\n \n for (const script of scripts) {\n if (script.src.includes('loamly') || script.dataset.loamly !== undefined) {\n scriptTag = script\n break\n }\n }\n\n if (!scriptTag) {\n // No matching script tag found, don't auto-init\n return\n }\n\n // Extract configuration from data attributes\n const config: LoamlyConfig = {}\n \n if (scriptTag.dataset.apiKey) {\n config.apiKey = scriptTag.dataset.apiKey\n }\n \n if (scriptTag.dataset.apiHost) {\n config.apiHost = scriptTag.dataset.apiHost\n }\n \n if (scriptTag.dataset.debug === 'true') {\n config.debug = true\n }\n \n if (scriptTag.dataset.disableAutoPageview === 'true') {\n config.disableAutoPageview = true\n }\n \n if (scriptTag.dataset.disableBehavioral === 'true') {\n config.disableBehavioral = true\n }\n\n // Initialize if we have configuration\n if (config.apiKey || scriptTag.dataset.loamly !== undefined) {\n loamly.init(config)\n }\n}\n\n// Run auto-init when DOM is ready\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoInit)\n } else {\n // Use requestIdleCallback if available for non-blocking init\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(autoInit)\n } else {\n setTimeout(autoInit, 0)\n }\n }\n}\n\n// Export for manual usage\nexport { loamly }\nexport default loamly\n\n\n","/**\n * Loamly Tracker Configuration\n * @module @loamly/tracker\n */\n\nexport const VERSION = '1.6.0'\n\nexport const DEFAULT_CONFIG = {\n apiHost: 'https://app.loamly.ai',\n endpoints: {\n visit: '/api/ingest/visit',\n behavioral: '/api/ingest/behavioral',\n session: '/api/ingest/session',\n resolve: '/api/tracker/resolve',\n health: '/api/tracker/health',\n ping: '/api/tracker/ping',\n },\n pingInterval: 30000, // 30 seconds\n batchSize: 10,\n batchTimeout: 5000,\n sessionTimeout: 1800000, // 30 minutes\n maxTextLength: 100,\n timeSpentThresholdMs: 5000, // Only send time_spent when delta >= 5 seconds\n} as const\n\n/**\n * Known AI platforms for referrer detection\n */\nexport const AI_PLATFORMS: Record<string, string> = {\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n 'claude.ai': 'claude',\n 'perplexity.ai': 'perplexity',\n 'bard.google.com': 'bard',\n 'gemini.google.com': 'gemini',\n 'copilot.microsoft.com': 'copilot',\n 'github.com/copilot': 'github-copilot',\n 'you.com': 'you',\n 'phind.com': 'phind',\n 'poe.com': 'poe',\n}\n\n/**\n * User agents of known AI crawlers\n */\nexport const AI_BOT_PATTERNS = [\n 'GPTBot',\n 'ChatGPT-User',\n 'ClaudeBot',\n 'Claude-Web',\n 'PerplexityBot',\n 'Amazonbot',\n 'Google-Extended',\n 'CCBot',\n 'anthropic-ai',\n 'cohere-ai',\n]\n\n\n","/**\n * Navigation Timing API Detection\n * \n * Detects whether the user arrived via paste (from AI chat) vs click\n * by analyzing Navigation Timing API patterns.\n * \n * @module @loamly/tracker/detection\n */\n\nimport type { NavigationTiming } from '../types'\n\n/**\n * Analyze Navigation Timing API to detect paste vs click navigation\n * \n * When users paste a URL (common after copying from AI chat), \n * the timing patterns are distinctive:\n * 1. fetchStart is virtually immediate after navigationStart\n * 2. DNS/connect times are often 0 (cached or direct)\n * 3. No redirect chain\n * 4. Uniform timing patterns\n * \n * @returns NavigationTiming result with type and confidence\n */\nexport function detectNavigationType(): NavigationTiming {\n try {\n const entries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[]\n \n if (!entries || entries.length === 0) {\n return { nav_type: 'unknown', confidence: 0, signals: ['no_timing_data'] }\n }\n\n const nav = entries[0]\n const signals: string[] = []\n let pasteScore = 0\n\n // Signal 1: fetchStart delta from navigationStart\n // Paste navigation typically has very small delta (< 5ms)\n const fetchStartDelta = nav.fetchStart - nav.startTime\n if (fetchStartDelta < 5) {\n pasteScore += 0.25\n signals.push('instant_fetch_start')\n } else if (fetchStartDelta < 20) {\n pasteScore += 0.15\n signals.push('fast_fetch_start')\n }\n\n // Signal 2: DNS lookup time\n // Paste = likely 0 (direct URL entry, no link warmup)\n const dnsTime = nav.domainLookupEnd - nav.domainLookupStart\n if (dnsTime === 0) {\n pasteScore += 0.15\n signals.push('no_dns_lookup')\n }\n\n // Signal 3: TCP connect time\n // Paste = likely 0 (no preconnect from previous page)\n const connectTime = nav.connectEnd - nav.connectStart\n if (connectTime === 0) {\n pasteScore += 0.15\n signals.push('no_tcp_connect')\n }\n\n // Signal 4: No redirects\n // Paste URLs are typically direct, no redirects\n if (nav.redirectCount === 0) {\n pasteScore += 0.1\n signals.push('no_redirects')\n }\n\n // Signal 5: Timing uniformity check\n // Paste navigation tends to have more uniform patterns\n const timingVariance = calculateTimingVariance(nav)\n if (timingVariance < 10) {\n pasteScore += 0.15\n signals.push('uniform_timing')\n }\n\n // Signal 6: No referrer (common for paste)\n if (!document.referrer || document.referrer === '') {\n pasteScore += 0.1\n signals.push('no_referrer')\n }\n\n // Determine navigation type based on score\n const confidence = Math.min(pasteScore, 1)\n const nav_type = pasteScore >= 0.5 ? 'likely_paste' : 'likely_click'\n\n return {\n nav_type,\n confidence: Math.round(confidence * 1000) / 1000,\n signals,\n }\n } catch {\n return { nav_type: 'unknown', confidence: 0, signals: ['detection_error'] }\n }\n}\n\n/**\n * Calculate timing variance to detect paste patterns\n */\nfunction calculateTimingVariance(nav: PerformanceNavigationTiming): number {\n const timings = [\n nav.fetchStart - nav.startTime,\n nav.domainLookupEnd - nav.domainLookupStart,\n nav.connectEnd - nav.connectStart,\n nav.responseStart - nav.requestStart,\n ].filter((t) => t >= 0)\n\n if (timings.length === 0) return 100\n\n const mean = timings.reduce((a, b) => a + b, 0) / timings.length\n const variance = timings.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / timings.length\n \n return Math.sqrt(variance)\n}\n\n\n","/**\n * Referrer-based AI Detection\n * \n * Detects when users arrive from known AI platforms\n * based on the document.referrer header.\n * \n * @module @loamly/tracker/detection\n */\n\nimport { AI_PLATFORMS } from '../config'\nimport type { AIDetectionResult } from '../types'\n\n/**\n * Detect AI platform from referrer URL\n * \n * @param referrer - The document.referrer value\n * @returns AI detection result or null if no AI detected\n */\nexport function detectAIFromReferrer(referrer: string): AIDetectionResult | null {\n if (!referrer) {\n return null\n }\n\n try {\n const url = new URL(referrer)\n const hostname = url.hostname.toLowerCase()\n\n // Check against known AI platforms\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (hostname.includes(pattern) || referrer.includes(pattern)) {\n return {\n isAI: true,\n platform,\n confidence: 0.95, // High confidence when referrer matches\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n // Invalid URL, try pattern matching on raw string\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (referrer.toLowerCase().includes(pattern.toLowerCase())) {\n return {\n isAI: true,\n platform,\n confidence: 0.85,\n method: 'referrer',\n }\n }\n }\n return null\n }\n}\n\n/**\n * Extract AI platform from UTM parameters\n * \n * @param url - The current page URL\n * @returns AI platform name or null\n */\nexport function detectAIFromUTM(url: string): AIDetectionResult | null {\n try {\n const params = new URL(url).searchParams\n \n // Check utm_source for AI platforms\n const utmSource = params.get('utm_source')?.toLowerCase()\n if (utmSource) {\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (utmSource.includes(pattern.split('.')[0])) {\n return {\n isAI: true,\n platform,\n confidence: 0.99, // Very high confidence from explicit UTM\n method: 'referrer',\n }\n }\n }\n \n // Generic AI source patterns\n if (utmSource.includes('ai') || utmSource.includes('llm') || utmSource.includes('chatbot')) {\n return {\n isAI: true,\n platform: utmSource,\n confidence: 0.9,\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n return null\n }\n}\n\n\n","/**\n * Utility functions for Loamly Tracker\n * @module @loamly/tracker\n */\n\n/**\n * Generate a UUID v4\n */\nexport function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID()\n }\n \n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Get or create a persistent visitor ID\n * (Privacy-respecting, no cookies)\n */\nexport function getVisitorId(): string {\n // Try to get from localStorage first\n try {\n const stored = localStorage.getItem('_loamly_vid')\n if (stored) return stored\n \n const newId = generateUUID()\n localStorage.setItem('_loamly_vid', newId)\n return newId\n } catch {\n // localStorage not available, generate ephemeral ID\n return generateUUID()\n }\n}\n\n/**\n * Get or create a session ID using sessionStorage\n * (Cookie-free session tracking)\n */\nexport function getSessionId(): { sessionId: string; isNew: boolean } {\n try {\n const storedSession = sessionStorage.getItem('loamly_session')\n const storedStart = sessionStorage.getItem('loamly_start')\n \n if (storedSession && storedStart) {\n return { sessionId: storedSession, isNew: false }\n }\n \n const newSession = generateUUID()\n const startTime = Date.now().toString()\n \n sessionStorage.setItem('loamly_session', newSession)\n sessionStorage.setItem('loamly_start', startTime)\n \n return { sessionId: newSession, isNew: true }\n } catch {\n // sessionStorage not available\n return { sessionId: generateUUID(), isNew: true }\n }\n}\n\n/**\n * Extract UTM parameters from URL\n */\nexport function extractUTMParams(url: string): Record<string, string> {\n const params: Record<string, string> = {}\n \n try {\n const searchParams = new URL(url).searchParams\n const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']\n \n for (const key of utmKeys) {\n const value = searchParams.get(key)\n if (value) params[key] = value\n }\n } catch {\n // Invalid URL\n }\n \n return params\n}\n\n/**\n * Truncate text to max length\n */\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.substring(0, maxLength - 3) + '...'\n}\n\n/**\n * Safe fetch with timeout\n */\nexport async function safeFetch(\n url: string,\n options: RequestInit,\n timeout = 10000\n): Promise<Response | null> {\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n \n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n })\n \n clearTimeout(timeoutId)\n return response\n } catch {\n return null\n }\n}\n\n/**\n * Send beacon (for unload events)\n */\nexport function sendBeacon(url: string, data: unknown): boolean {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n return navigator.sendBeacon(url, JSON.stringify(data))\n }\n return false\n}\n\n\n","/**\n * Loamly Tracker Core\n * \n * Cookie-free, privacy-first analytics with AI traffic detection.\n * \n * @module @loamly/tracker\n */\n\nimport { VERSION, DEFAULT_CONFIG } from './config'\nimport { detectNavigationType } from './detection/navigation-timing'\nimport { detectAIFromReferrer, detectAIFromUTM } from './detection/referrer'\nimport { \n getVisitorId, \n getSessionId, \n extractUTMParams, \n truncateText,\n safeFetch,\n sendBeacon \n} from './utils'\nimport type { \n LoamlyConfig, \n LoamlyTracker, \n TrackEventOptions, \n NavigationTiming,\n AIDetectionResult \n} from './types'\n\n// State\nlet config: LoamlyConfig & { apiHost: string } = { apiHost: DEFAULT_CONFIG.apiHost }\nlet initialized = false\nlet debugMode = false\nlet visitorId: string | null = null\nlet sessionId: string | null = null\nlet sessionStartTime: number | null = null\nlet navigationTiming: NavigationTiming | null = null\nlet aiDetection: AIDetectionResult | null = null\n\n/**\n * Debug logger\n */\nfunction log(...args: unknown[]): void {\n if (debugMode) {\n console.log('[Loamly]', ...args)\n }\n}\n\n/**\n * Build API endpoint URL\n */\nfunction endpoint(path: string): string {\n return `${config.apiHost}${path}`\n}\n\n/**\n * Initialize the tracker\n */\nfunction init(userConfig: LoamlyConfig = {}): void {\n if (initialized) {\n log('Already initialized')\n return\n }\n\n config = {\n ...config,\n ...userConfig,\n apiHost: userConfig.apiHost || DEFAULT_CONFIG.apiHost,\n }\n \n debugMode = userConfig.debug ?? false\n \n log('Initializing Loamly Tracker v' + VERSION)\n \n // Get/create visitor ID\n visitorId = getVisitorId()\n log('Visitor ID:', visitorId)\n \n // Get/create session\n const session = getSessionId()\n sessionId = session.sessionId\n sessionStartTime = Date.now()\n log('Session ID:', sessionId, session.isNew ? '(new)' : '(existing)')\n \n // Detect navigation timing (paste vs click)\n navigationTiming = detectNavigationType()\n log('Navigation timing:', navigationTiming)\n \n // Detect AI from referrer\n aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href)\n if (aiDetection) {\n log('AI detected:', aiDetection)\n }\n \n initialized = true\n \n // Auto pageview unless disabled\n if (!userConfig.disableAutoPageview) {\n pageview()\n }\n \n // Set up behavioral tracking unless disabled\n if (!userConfig.disableBehavioral) {\n setupBehavioralTracking()\n }\n \n log('Initialization complete')\n}\n\n/**\n * Track a page view\n */\nfunction pageview(customUrl?: string): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const url = customUrl || window.location.href\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n url,\n referrer: document.referrer || null,\n title: document.title || null,\n utm_source: extractUTMParams(url).utm_source || null,\n utm_medium: extractUTMParams(url).utm_medium || null,\n utm_campaign: extractUTMParams(url).utm_campaign || null,\n user_agent: navigator.userAgent,\n screen_width: window.screen?.width,\n screen_height: window.screen?.height,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n tracker_version: VERSION,\n navigation_timing: navigationTiming,\n ai_platform: aiDetection?.platform || null,\n is_ai_referrer: aiDetection?.isAI || false,\n }\n\n log('Pageview:', payload)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.visit), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a custom event\n */\nfunction track(eventName: string, options: TrackEventOptions = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_name: eventName,\n event_type: 'custom',\n properties: options.properties || {},\n revenue: options.revenue,\n currency: options.currency || 'USD',\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Event:', eventName, payload)\n\n safeFetch(endpoint('/api/ingest/event'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a conversion/revenue event\n */\nfunction conversion(eventName: string, revenue: number, currency = 'USD'): void {\n track(eventName, { revenue, currency, properties: { type: 'conversion' } })\n}\n\n/**\n * Identify a user\n */\nfunction identify(userId: string, traits: Record<string, unknown> = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n log('Identify:', userId, traits)\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n user_id: userId,\n traits,\n timestamp: new Date().toISOString(),\n }\n\n safeFetch(endpoint('/api/ingest/identify'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Set up behavioral tracking (scroll, time spent, etc.)\n */\nfunction setupBehavioralTracking(): void {\n let maxScrollDepth = 0\n let lastScrollUpdate = 0\n let lastTimeUpdate = Date.now()\n\n // Scroll tracking with requestAnimationFrame throttling\n let scrollTicking = false\n \n window.addEventListener('scroll', () => {\n if (!scrollTicking) {\n requestAnimationFrame(() => {\n const scrollPercent = Math.round(\n ((window.scrollY + window.innerHeight) / document.documentElement.scrollHeight) * 100\n )\n \n if (scrollPercent > maxScrollDepth) {\n maxScrollDepth = scrollPercent\n \n // Report at milestones (25%, 50%, 75%, 100%)\n const milestones = [25, 50, 75, 100]\n for (const milestone of milestones) {\n if (scrollPercent >= milestone && lastScrollUpdate < milestone) {\n lastScrollUpdate = milestone\n sendBehavioralEvent('scroll_depth', { depth: milestone })\n }\n }\n }\n \n scrollTicking = false\n })\n scrollTicking = true\n }\n })\n\n // Time spent tracking (every 5 seconds minimum)\n const trackTimeSpent = (): void => {\n const now = Date.now()\n const delta = now - lastTimeUpdate\n \n if (delta >= DEFAULT_CONFIG.timeSpentThresholdMs) {\n lastTimeUpdate = now\n sendBehavioralEvent('time_spent', { \n seconds: Math.round(delta / 1000),\n total_seconds: Math.round((now - (sessionStartTime || now)) / 1000)\n })\n }\n }\n\n // Track on visibility change\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n trackTimeSpent()\n }\n })\n\n // Track on page unload\n window.addEventListener('beforeunload', () => {\n trackTimeSpent()\n \n // Send final scroll depth\n if (maxScrollDepth > 0) {\n sendBeacon(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: 'scroll_depth_final',\n data: { depth: maxScrollDepth },\n url: window.location.href,\n })\n }\n })\n\n // Form interaction tracking\n document.addEventListener('focusin', (e) => {\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n sendBehavioralEvent('form_focus', {\n field_type: target.tagName.toLowerCase(),\n field_name: (target as HTMLInputElement).name || (target as HTMLInputElement).id || 'unknown',\n })\n }\n })\n\n // Form submit tracking\n document.addEventListener('submit', (e) => {\n const form = e.target as HTMLFormElement\n sendBehavioralEvent('form_submit', {\n form_id: form.id || form.name || 'unknown',\n form_action: form.action ? new URL(form.action).pathname : 'unknown',\n })\n })\n\n // Click tracking for links\n document.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n const link = target.closest('a')\n \n if (link && link.href) {\n const isExternal = link.hostname !== window.location.hostname\n sendBehavioralEvent('click', {\n element: 'link',\n href: truncateText(link.href, 200),\n text: truncateText(link.textContent || '', 100),\n is_external: isExternal,\n })\n }\n })\n}\n\n/**\n * Send a behavioral event\n */\nfunction sendBehavioralEvent(eventType: string, data: Record<string, unknown>): void {\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: eventType,\n data,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Behavioral:', eventType, data)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Get current session ID\n */\nfunction getCurrentSessionId(): string | null {\n return sessionId\n}\n\n/**\n * Get current visitor ID\n */\nfunction getCurrentVisitorId(): string | null {\n return visitorId\n}\n\n/**\n * Get AI detection result\n */\nfunction getAIDetectionResult(): AIDetectionResult | null {\n return aiDetection\n}\n\n/**\n * Get navigation timing result\n */\nfunction getNavigationTimingResult(): NavigationTiming | null {\n return navigationTiming\n}\n\n/**\n * Check if initialized\n */\nfunction isTrackerInitialized(): boolean {\n return initialized\n}\n\n/**\n * Reset the tracker\n */\nfunction reset(): void {\n log('Resetting tracker')\n initialized = false\n visitorId = null\n sessionId = null\n sessionStartTime = null\n navigationTiming = null\n aiDetection = null\n \n try {\n sessionStorage.removeItem('loamly_session')\n sessionStorage.removeItem('loamly_start')\n } catch {\n // Ignore\n }\n}\n\n/**\n * Enable/disable debug mode\n */\nfunction setDebug(enabled: boolean): void {\n debugMode = enabled\n log('Debug mode:', enabled ? 'enabled' : 'disabled')\n}\n\n/**\n * The Loamly Tracker instance\n */\nexport const loamly: LoamlyTracker = {\n init,\n pageview,\n track,\n conversion,\n identify,\n getSessionId: getCurrentSessionId,\n getVisitorId: getCurrentVisitorId,\n getAIDetection: getAIDetectionResult,\n getNavigationTiming: getNavigationTimingResult,\n isInitialized: isTrackerInitialized,\n reset,\n debug: setDebug,\n}\n\nexport default loamly\n\n\n"],"mappings":"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","names":["config"]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/config.ts","../src/detection/navigation-timing.ts","../src/detection/referrer.ts","../src/detection/behavioral-classifier.ts","../src/utils.ts","../src/core.ts"],"sourcesContent":["/**\n * Loamly Tracker - Browser Bundle (IIFE)\n * \n * This file is the entry point for the browser script tag version.\n * It auto-initializes from data attributes on the script tag.\n * \n * Usage:\n * <script src=\"https://unpkg.com/@loamly/tracker\" data-api-key=\"your-key\"></script>\n * \n * @module @loamly/tracker/browser\n */\n\nimport { loamly } from './core'\nimport type { LoamlyConfig } from './types'\n\n// Auto-initialize from script tag data attributes\nfunction autoInit(): void {\n // Find the script tag that loaded us\n const scripts = document.getElementsByTagName('script')\n let scriptTag: HTMLScriptElement | null = null\n \n for (const script of scripts) {\n if (script.src.includes('loamly') || script.dataset.loamly !== undefined) {\n scriptTag = script\n break\n }\n }\n\n if (!scriptTag) {\n // No matching script tag found, don't auto-init\n return\n }\n\n // Extract configuration from data attributes\n const config: LoamlyConfig = {}\n \n if (scriptTag.dataset.apiKey) {\n config.apiKey = scriptTag.dataset.apiKey\n }\n \n if (scriptTag.dataset.apiHost) {\n config.apiHost = scriptTag.dataset.apiHost\n }\n \n if (scriptTag.dataset.debug === 'true') {\n config.debug = true\n }\n \n if (scriptTag.dataset.disableAutoPageview === 'true') {\n config.disableAutoPageview = true\n }\n \n if (scriptTag.dataset.disableBehavioral === 'true') {\n config.disableBehavioral = true\n }\n\n // Initialize if we have configuration\n if (config.apiKey || scriptTag.dataset.loamly !== undefined) {\n loamly.init(config)\n }\n}\n\n// Run auto-init when DOM is ready\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoInit)\n } else {\n // Use requestIdleCallback if available for non-blocking init\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(autoInit)\n } else {\n setTimeout(autoInit, 0)\n }\n }\n}\n\n// Export for manual usage\nexport { loamly }\nexport default loamly\n\n\n","/**\n * Loamly Tracker Configuration\n * @module @loamly/tracker\n */\n\nexport const VERSION = '1.7.0'\n\nexport const DEFAULT_CONFIG = {\n apiHost: 'https://app.loamly.ai',\n endpoints: {\n visit: '/api/ingest/visit',\n behavioral: '/api/ingest/behavioral',\n session: '/api/ingest/session',\n resolve: '/api/tracker/resolve',\n health: '/api/tracker/health',\n ping: '/api/tracker/ping',\n },\n pingInterval: 30000, // 30 seconds\n batchSize: 10,\n batchTimeout: 5000,\n sessionTimeout: 1800000, // 30 minutes\n maxTextLength: 100,\n timeSpentThresholdMs: 5000, // Only send time_spent when delta >= 5 seconds\n} as const\n\n/**\n * Known AI platforms for referrer detection\n */\nexport const AI_PLATFORMS: Record<string, string> = {\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n 'claude.ai': 'claude',\n 'perplexity.ai': 'perplexity',\n 'bard.google.com': 'bard',\n 'gemini.google.com': 'gemini',\n 'copilot.microsoft.com': 'copilot',\n 'github.com/copilot': 'github-copilot',\n 'you.com': 'you',\n 'phind.com': 'phind',\n 'poe.com': 'poe',\n}\n\n/**\n * User agents of known AI crawlers\n */\nexport const AI_BOT_PATTERNS = [\n 'GPTBot',\n 'ChatGPT-User',\n 'ClaudeBot',\n 'Claude-Web',\n 'PerplexityBot',\n 'Amazonbot',\n 'Google-Extended',\n 'CCBot',\n 'anthropic-ai',\n 'cohere-ai',\n]\n\n\n","/**\n * Navigation Timing API Detection\n * \n * Detects whether the user arrived via paste (from AI chat) vs click\n * by analyzing Navigation Timing API patterns.\n * \n * @module @loamly/tracker/detection\n */\n\nimport type { NavigationTiming } from '../types'\n\n/**\n * Analyze Navigation Timing API to detect paste vs click navigation\n * \n * When users paste a URL (common after copying from AI chat), \n * the timing patterns are distinctive:\n * 1. fetchStart is virtually immediate after navigationStart\n * 2. DNS/connect times are often 0 (cached or direct)\n * 3. No redirect chain\n * 4. Uniform timing patterns\n * \n * @returns NavigationTiming result with type and confidence\n */\nexport function detectNavigationType(): NavigationTiming {\n try {\n const entries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[]\n \n if (!entries || entries.length === 0) {\n return { nav_type: 'unknown', confidence: 0, signals: ['no_timing_data'] }\n }\n\n const nav = entries[0]\n const signals: string[] = []\n let pasteScore = 0\n\n // Signal 1: fetchStart delta from navigationStart\n // Paste navigation typically has very small delta (< 5ms)\n const fetchStartDelta = nav.fetchStart - nav.startTime\n if (fetchStartDelta < 5) {\n pasteScore += 0.25\n signals.push('instant_fetch_start')\n } else if (fetchStartDelta < 20) {\n pasteScore += 0.15\n signals.push('fast_fetch_start')\n }\n\n // Signal 2: DNS lookup time\n // Paste = likely 0 (direct URL entry, no link warmup)\n const dnsTime = nav.domainLookupEnd - nav.domainLookupStart\n if (dnsTime === 0) {\n pasteScore += 0.15\n signals.push('no_dns_lookup')\n }\n\n // Signal 3: TCP connect time\n // Paste = likely 0 (no preconnect from previous page)\n const connectTime = nav.connectEnd - nav.connectStart\n if (connectTime === 0) {\n pasteScore += 0.15\n signals.push('no_tcp_connect')\n }\n\n // Signal 4: No redirects\n // Paste URLs are typically direct, no redirects\n if (nav.redirectCount === 0) {\n pasteScore += 0.1\n signals.push('no_redirects')\n }\n\n // Signal 5: Timing uniformity check\n // Paste navigation tends to have more uniform patterns\n const timingVariance = calculateTimingVariance(nav)\n if (timingVariance < 10) {\n pasteScore += 0.15\n signals.push('uniform_timing')\n }\n\n // Signal 6: No referrer (common for paste)\n if (!document.referrer || document.referrer === '') {\n pasteScore += 0.1\n signals.push('no_referrer')\n }\n\n // Determine navigation type based on score\n const confidence = Math.min(pasteScore, 1)\n const nav_type = pasteScore >= 0.5 ? 'likely_paste' : 'likely_click'\n\n return {\n nav_type,\n confidence: Math.round(confidence * 1000) / 1000,\n signals,\n }\n } catch {\n return { nav_type: 'unknown', confidence: 0, signals: ['detection_error'] }\n }\n}\n\n/**\n * Calculate timing variance to detect paste patterns\n */\nfunction calculateTimingVariance(nav: PerformanceNavigationTiming): number {\n const timings = [\n nav.fetchStart - nav.startTime,\n nav.domainLookupEnd - nav.domainLookupStart,\n nav.connectEnd - nav.connectStart,\n nav.responseStart - nav.requestStart,\n ].filter((t) => t >= 0)\n\n if (timings.length === 0) return 100\n\n const mean = timings.reduce((a, b) => a + b, 0) / timings.length\n const variance = timings.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / timings.length\n \n return Math.sqrt(variance)\n}\n\n\n","/**\n * Referrer-based AI Detection\n * \n * Detects when users arrive from known AI platforms\n * based on the document.referrer header.\n * \n * @module @loamly/tracker/detection\n */\n\nimport { AI_PLATFORMS } from '../config'\nimport type { AIDetectionResult } from '../types'\n\n/**\n * Detect AI platform from referrer URL\n * \n * @param referrer - The document.referrer value\n * @returns AI detection result or null if no AI detected\n */\nexport function detectAIFromReferrer(referrer: string): AIDetectionResult | null {\n if (!referrer) {\n return null\n }\n\n try {\n const url = new URL(referrer)\n const hostname = url.hostname.toLowerCase()\n\n // Check against known AI platforms\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (hostname.includes(pattern) || referrer.includes(pattern)) {\n return {\n isAI: true,\n platform,\n confidence: 0.95, // High confidence when referrer matches\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n // Invalid URL, try pattern matching on raw string\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (referrer.toLowerCase().includes(pattern.toLowerCase())) {\n return {\n isAI: true,\n platform,\n confidence: 0.85,\n method: 'referrer',\n }\n }\n }\n return null\n }\n}\n\n/**\n * Extract AI platform from UTM parameters\n * \n * @param url - The current page URL\n * @returns AI platform name or null\n */\nexport function detectAIFromUTM(url: string): AIDetectionResult | null {\n try {\n const params = new URL(url).searchParams\n \n // Check utm_source for AI platforms\n const utmSource = params.get('utm_source')?.toLowerCase()\n if (utmSource) {\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (utmSource.includes(pattern.split('.')[0])) {\n return {\n isAI: true,\n platform,\n confidence: 0.99, // Very high confidence from explicit UTM\n method: 'referrer',\n }\n }\n }\n \n // Generic AI source patterns\n if (utmSource.includes('ai') || utmSource.includes('llm') || utmSource.includes('chatbot')) {\n return {\n isAI: true,\n platform: utmSource,\n confidence: 0.9,\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n return null\n }\n}\n\n\n","/**\n * Lightweight Behavioral ML Classifier\n * \n * LOA-180: Client-side Naive Bayes classifier for AI traffic detection.\n * Research: 75-90% accuracy with 5-8 behavioral signals (Perplexity Dec 2025)\n * \n * @module @loamly/tracker/detection/behavioral-classifier\n */\n\n/**\n * Behavioral signal types for classification\n */\nexport type BehavioralSignal = \n // Time-based signals\n | 'time_to_first_click_immediate' // < 500ms\n | 'time_to_first_click_fast' // 500ms - 2s\n | 'time_to_first_click_normal' // 2s - 10s\n | 'time_to_first_click_delayed' // > 10s\n // Scroll signals\n | 'scroll_speed_none' // No scroll\n | 'scroll_speed_uniform' // Bot-like uniform scrolling\n | 'scroll_speed_variable' // Human-like variable\n | 'scroll_speed_erratic' // Very erratic\n // Navigation signals\n | 'nav_timing_paste'\n | 'nav_timing_click'\n | 'nav_timing_unknown'\n // Context signals\n | 'no_referrer'\n | 'has_referrer'\n | 'deep_landing' // Non-homepage first page\n | 'homepage_landing'\n // Mouse signals\n | 'mouse_movement_none'\n | 'mouse_movement_linear' // Bot-like straight lines\n | 'mouse_movement_curved' // Human-like curves\n // Form signals\n | 'form_fill_instant' // < 100ms per field\n | 'form_fill_fast' // 100-500ms per field\n | 'form_fill_normal' // > 500ms per field\n // Focus signals\n | 'focus_blur_rapid' // Rapid tab switching\n | 'focus_blur_normal'\n\n/**\n * Classification result\n */\nexport interface BehavioralClassificationResult {\n /** Overall classification */\n classification: 'human' | 'ai_influenced' | 'uncertain'\n /** Human probability (0-1) */\n humanProbability: number\n /** AI-influenced probability (0-1) */\n aiProbability: number\n /** Confidence in classification (0-1) */\n confidence: number\n /** Signals detected */\n signals: BehavioralSignal[]\n /** Time when classification was made */\n timestamp: number\n /** Session duration when classified */\n sessionDurationMs: number\n}\n\n/**\n * Behavioral data collector\n */\ninterface BehavioralData {\n firstClickTime: number | null\n scrollEvents: { time: number; position: number }[]\n mouseEvents: { time: number; x: number; y: number }[]\n formEvents: { fieldId: string; startTime: number; endTime: number }[]\n focusBlurEvents: { type: 'focus' | 'blur'; time: number }[]\n startTime: number\n}\n\n/**\n * Pre-trained Naive Bayes weights\n * Research-validated weights from Perplexity Dec 2025 research\n */\nconst NAIVE_BAYES_WEIGHTS = {\n human: {\n time_to_first_click_delayed: 0.85,\n time_to_first_click_normal: 0.75,\n time_to_first_click_fast: 0.50,\n time_to_first_click_immediate: 0.25,\n scroll_speed_variable: 0.80,\n scroll_speed_erratic: 0.70,\n scroll_speed_uniform: 0.35,\n scroll_speed_none: 0.45,\n nav_timing_click: 0.75,\n nav_timing_unknown: 0.55,\n nav_timing_paste: 0.35,\n has_referrer: 0.70,\n no_referrer: 0.45,\n homepage_landing: 0.65,\n deep_landing: 0.50,\n mouse_movement_curved: 0.90,\n mouse_movement_linear: 0.30,\n mouse_movement_none: 0.40,\n form_fill_normal: 0.85,\n form_fill_fast: 0.60,\n form_fill_instant: 0.20,\n focus_blur_normal: 0.75,\n focus_blur_rapid: 0.45,\n },\n ai_influenced: {\n time_to_first_click_immediate: 0.75,\n time_to_first_click_fast: 0.55,\n time_to_first_click_normal: 0.40,\n time_to_first_click_delayed: 0.35,\n scroll_speed_none: 0.55,\n scroll_speed_uniform: 0.70,\n scroll_speed_variable: 0.35,\n scroll_speed_erratic: 0.40,\n nav_timing_paste: 0.75,\n nav_timing_unknown: 0.50,\n nav_timing_click: 0.35,\n no_referrer: 0.65,\n has_referrer: 0.40,\n deep_landing: 0.60,\n homepage_landing: 0.45,\n mouse_movement_none: 0.60,\n mouse_movement_linear: 0.75,\n mouse_movement_curved: 0.25,\n form_fill_instant: 0.80,\n form_fill_fast: 0.55,\n form_fill_normal: 0.30,\n focus_blur_rapid: 0.60,\n focus_blur_normal: 0.40,\n },\n} as const\n\n// Prior probabilities (base rates)\nconst PRIORS = {\n human: 0.85,\n ai_influenced: 0.15,\n}\n\n// Default weight for unknown signals\nconst DEFAULT_WEIGHT = 0.5\n\n/**\n * Behavioral Classifier\n * \n * Lightweight Naive Bayes classifier (~2KB) for client-side AI traffic detection.\n * Collects behavioral signals and classifies after configurable session time.\n */\nexport class BehavioralClassifier {\n private data: BehavioralData\n private classified = false\n private result: BehavioralClassificationResult | null = null\n private minSessionTime: number\n private onClassify: ((result: BehavioralClassificationResult) => void) | null = null\n\n /**\n * Create a new classifier\n * @param minSessionTimeMs Minimum session time before classification (default: 10s)\n */\n constructor(minSessionTimeMs = 10000) {\n this.minSessionTime = minSessionTimeMs\n this.data = {\n firstClickTime: null,\n scrollEvents: [],\n mouseEvents: [],\n formEvents: [],\n focusBlurEvents: [],\n startTime: Date.now(),\n }\n }\n\n /**\n * Set callback for when classification completes\n */\n setOnClassify(callback: (result: BehavioralClassificationResult) => void): void {\n this.onClassify = callback\n }\n\n /**\n * Record a click event\n */\n recordClick(): void {\n if (this.data.firstClickTime === null) {\n this.data.firstClickTime = Date.now()\n }\n this.checkAndClassify()\n }\n\n /**\n * Record a scroll event\n */\n recordScroll(position: number): void {\n this.data.scrollEvents.push({ time: Date.now(), position })\n // Keep only last 50 events to limit memory\n if (this.data.scrollEvents.length > 50) {\n this.data.scrollEvents = this.data.scrollEvents.slice(-50)\n }\n this.checkAndClassify()\n }\n\n /**\n * Record mouse movement\n */\n recordMouse(x: number, y: number): void {\n this.data.mouseEvents.push({ time: Date.now(), x, y })\n // Keep only last 100 events\n if (this.data.mouseEvents.length > 100) {\n this.data.mouseEvents = this.data.mouseEvents.slice(-100)\n }\n this.checkAndClassify()\n }\n\n /**\n * Record form field interaction start\n */\n recordFormStart(fieldId: string): void {\n const existing = this.data.formEvents.find(e => e.fieldId === fieldId && e.endTime === 0)\n if (!existing) {\n this.data.formEvents.push({ fieldId, startTime: Date.now(), endTime: 0 })\n }\n }\n\n /**\n * Record form field interaction end\n */\n recordFormEnd(fieldId: string): void {\n const event = this.data.formEvents.find(e => e.fieldId === fieldId && e.endTime === 0)\n if (event) {\n event.endTime = Date.now()\n }\n this.checkAndClassify()\n }\n\n /**\n * Record focus/blur event\n */\n recordFocusBlur(type: 'focus' | 'blur'): void {\n this.data.focusBlurEvents.push({ type, time: Date.now() })\n // Keep only last 20 events\n if (this.data.focusBlurEvents.length > 20) {\n this.data.focusBlurEvents = this.data.focusBlurEvents.slice(-20)\n }\n }\n\n /**\n * Check if we have enough data and classify\n */\n private checkAndClassify(): void {\n if (this.classified) return\n \n const sessionDuration = Date.now() - this.data.startTime\n if (sessionDuration < this.minSessionTime) return\n \n // Need at least some behavioral data\n const hasData = \n this.data.scrollEvents.length >= 2 ||\n this.data.mouseEvents.length >= 5 ||\n this.data.firstClickTime !== null\n \n if (!hasData) return\n \n this.classify()\n }\n\n /**\n * Force classification (for beforeunload)\n */\n forceClassify(): BehavioralClassificationResult | null {\n if (this.classified) return this.result\n return this.classify()\n }\n\n /**\n * Perform classification\n */\n private classify(): BehavioralClassificationResult {\n const sessionDuration = Date.now() - this.data.startTime\n const signals = this.extractSignals()\n \n // Naive Bayes log-probability calculation\n let humanLogProb = Math.log(PRIORS.human)\n let aiLogProb = Math.log(PRIORS.ai_influenced)\n \n for (const signal of signals) {\n const humanWeight = NAIVE_BAYES_WEIGHTS.human[signal as keyof typeof NAIVE_BAYES_WEIGHTS.human] ?? DEFAULT_WEIGHT\n const aiWeight = NAIVE_BAYES_WEIGHTS.ai_influenced[signal as keyof typeof NAIVE_BAYES_WEIGHTS.ai_influenced] ?? DEFAULT_WEIGHT\n \n humanLogProb += Math.log(humanWeight)\n aiLogProb += Math.log(aiWeight)\n }\n \n // Convert to probabilities using log-sum-exp trick\n const maxLog = Math.max(humanLogProb, aiLogProb)\n const humanExp = Math.exp(humanLogProb - maxLog)\n const aiExp = Math.exp(aiLogProb - maxLog)\n const total = humanExp + aiExp\n \n const humanProbability = humanExp / total\n const aiProbability = aiExp / total\n \n // Determine classification\n let classification: 'human' | 'ai_influenced' | 'uncertain'\n let confidence: number\n \n if (humanProbability > 0.6) {\n classification = 'human'\n confidence = humanProbability\n } else if (aiProbability > 0.6) {\n classification = 'ai_influenced'\n confidence = aiProbability\n } else {\n classification = 'uncertain'\n confidence = Math.max(humanProbability, aiProbability)\n }\n \n this.result = {\n classification,\n humanProbability,\n aiProbability,\n confidence,\n signals,\n timestamp: Date.now(),\n sessionDurationMs: sessionDuration,\n }\n \n this.classified = true\n \n // Call callback if set\n if (this.onClassify) {\n this.onClassify(this.result)\n }\n \n return this.result\n }\n\n /**\n * Extract behavioral signals from collected data\n */\n private extractSignals(): BehavioralSignal[] {\n const signals: BehavioralSignal[] = []\n // Session duration available for future enhancements\n // const sessionDuration = Date.now() - this.data.startTime\n \n // Time to first click\n if (this.data.firstClickTime !== null) {\n const timeToClick = this.data.firstClickTime - this.data.startTime\n if (timeToClick < 500) {\n signals.push('time_to_first_click_immediate')\n } else if (timeToClick < 2000) {\n signals.push('time_to_first_click_fast')\n } else if (timeToClick < 10000) {\n signals.push('time_to_first_click_normal')\n } else {\n signals.push('time_to_first_click_delayed')\n }\n }\n \n // Scroll behavior\n if (this.data.scrollEvents.length === 0) {\n signals.push('scroll_speed_none')\n } else if (this.data.scrollEvents.length >= 3) {\n const scrollDeltas: number[] = []\n for (let i = 1; i < this.data.scrollEvents.length; i++) {\n const delta = this.data.scrollEvents[i].time - this.data.scrollEvents[i - 1].time\n scrollDeltas.push(delta)\n }\n \n // Calculate coefficient of variation for scroll timing\n const mean = scrollDeltas.reduce((a, b) => a + b, 0) / scrollDeltas.length\n const variance = scrollDeltas.reduce((sum, d) => sum + Math.pow(d - mean, 2), 0) / scrollDeltas.length\n const stdDev = Math.sqrt(variance)\n const cv = mean > 0 ? stdDev / mean : 0\n \n if (cv < 0.2) {\n signals.push('scroll_speed_uniform') // Very consistent = bot-like\n } else if (cv < 0.6) {\n signals.push('scroll_speed_variable') // Natural variation\n } else {\n signals.push('scroll_speed_erratic')\n }\n }\n \n // Mouse movement analysis\n if (this.data.mouseEvents.length === 0) {\n signals.push('mouse_movement_none')\n } else if (this.data.mouseEvents.length >= 10) {\n // Calculate linearity using R² of best-fit line\n const n = Math.min(this.data.mouseEvents.length, 20)\n const recentMouse = this.data.mouseEvents.slice(-n)\n \n let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0\n for (const event of recentMouse) {\n sumX += event.x\n sumY += event.y\n sumXY += event.x * event.y\n sumX2 += event.x * event.x\n }\n \n const denominator = (n * sumX2 - sumX * sumX)\n const slope = denominator !== 0 ? (n * sumXY - sumX * sumY) / denominator : 0\n const intercept = (sumY - slope * sumX) / n\n \n // Calculate R²\n let ssRes = 0, ssTot = 0\n const yMean = sumY / n\n for (const event of recentMouse) {\n const yPred = slope * event.x + intercept\n ssRes += Math.pow(event.y - yPred, 2)\n ssTot += Math.pow(event.y - yMean, 2)\n }\n \n const r2 = ssTot !== 0 ? 1 - (ssRes / ssTot) : 0\n \n if (r2 > 0.95) {\n signals.push('mouse_movement_linear') // Too straight = bot-like\n } else {\n signals.push('mouse_movement_curved') // Natural curves\n }\n }\n \n // Form fill timing\n const completedForms = this.data.formEvents.filter(e => e.endTime > 0)\n if (completedForms.length > 0) {\n const avgFillTime = completedForms.reduce((sum, e) => sum + (e.endTime - e.startTime), 0) / completedForms.length\n \n if (avgFillTime < 100) {\n signals.push('form_fill_instant')\n } else if (avgFillTime < 500) {\n signals.push('form_fill_fast')\n } else {\n signals.push('form_fill_normal')\n }\n }\n \n // Focus/blur patterns\n if (this.data.focusBlurEvents.length >= 4) {\n const recentFB = this.data.focusBlurEvents.slice(-10)\n const intervals: number[] = []\n \n for (let i = 1; i < recentFB.length; i++) {\n intervals.push(recentFB[i].time - recentFB[i - 1].time)\n }\n \n const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length\n \n if (avgInterval < 1000) {\n signals.push('focus_blur_rapid')\n } else {\n signals.push('focus_blur_normal')\n }\n }\n \n // Context signals (will be set from outside)\n // These are typically added by the tracker itself\n \n return signals\n }\n\n /**\n * Add context signals (set by tracker from external data)\n */\n addContextSignal(_signal: BehavioralSignal): void {\n // These will be picked up on next classification\n // For now, we'll handle them in the classify method\n // TODO: Store signals for next classification run\n }\n\n /**\n * Get current result (null if not yet classified)\n */\n getResult(): BehavioralClassificationResult | null {\n return this.result\n }\n\n /**\n * Check if classification has been performed\n */\n hasClassified(): boolean {\n return this.classified\n }\n}\n\n/**\n * Create a classifier instance with standard configuration\n */\nexport function createBehavioralClassifier(minSessionTimeMs = 10000): BehavioralClassifier {\n return new BehavioralClassifier(minSessionTimeMs)\n}\n\n","/**\n * Utility functions for Loamly Tracker\n * @module @loamly/tracker\n */\n\n/**\n * Generate a UUID v4\n */\nexport function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID()\n }\n \n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Get or create a persistent visitor ID\n * (Privacy-respecting, no cookies)\n */\nexport function getVisitorId(): string {\n // Try to get from localStorage first\n try {\n const stored = localStorage.getItem('_loamly_vid')\n if (stored) return stored\n \n const newId = generateUUID()\n localStorage.setItem('_loamly_vid', newId)\n return newId\n } catch {\n // localStorage not available, generate ephemeral ID\n return generateUUID()\n }\n}\n\n/**\n * Get or create a session ID using sessionStorage\n * (Cookie-free session tracking)\n */\nexport function getSessionId(): { sessionId: string; isNew: boolean } {\n try {\n const storedSession = sessionStorage.getItem('loamly_session')\n const storedStart = sessionStorage.getItem('loamly_start')\n \n if (storedSession && storedStart) {\n return { sessionId: storedSession, isNew: false }\n }\n \n const newSession = generateUUID()\n const startTime = Date.now().toString()\n \n sessionStorage.setItem('loamly_session', newSession)\n sessionStorage.setItem('loamly_start', startTime)\n \n return { sessionId: newSession, isNew: true }\n } catch {\n // sessionStorage not available\n return { sessionId: generateUUID(), isNew: true }\n }\n}\n\n/**\n * Extract UTM parameters from URL\n */\nexport function extractUTMParams(url: string): Record<string, string> {\n const params: Record<string, string> = {}\n \n try {\n const searchParams = new URL(url).searchParams\n const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']\n \n for (const key of utmKeys) {\n const value = searchParams.get(key)\n if (value) params[key] = value\n }\n } catch {\n // Invalid URL\n }\n \n return params\n}\n\n/**\n * Truncate text to max length\n */\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.substring(0, maxLength - 3) + '...'\n}\n\n/**\n * Safe fetch with timeout\n */\nexport async function safeFetch(\n url: string,\n options: RequestInit,\n timeout = 10000\n): Promise<Response | null> {\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n \n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n })\n \n clearTimeout(timeoutId)\n return response\n } catch {\n return null\n }\n}\n\n/**\n * Send beacon (for unload events)\n */\nexport function sendBeacon(url: string, data: unknown): boolean {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n return navigator.sendBeacon(url, JSON.stringify(data))\n }\n return false\n}\n\n\n","/**\n * Loamly Tracker Core\n * \n * Cookie-free, privacy-first analytics with AI traffic detection.\n * \n * @module @loamly/tracker\n */\n\nimport { VERSION, DEFAULT_CONFIG } from './config'\nimport { detectNavigationType } from './detection/navigation-timing'\nimport { detectAIFromReferrer, detectAIFromUTM } from './detection/referrer'\nimport { \n BehavioralClassifier, \n type BehavioralClassificationResult \n} from './detection/behavioral-classifier'\nimport { \n getVisitorId, \n getSessionId, \n extractUTMParams, \n truncateText,\n safeFetch,\n sendBeacon \n} from './utils'\nimport type { \n LoamlyConfig, \n LoamlyTracker, \n TrackEventOptions, \n NavigationTiming,\n AIDetectionResult,\n BehavioralMLResult\n} from './types'\n\n// State\nlet config: LoamlyConfig & { apiHost: string } = { apiHost: DEFAULT_CONFIG.apiHost }\nlet initialized = false\nlet debugMode = false\nlet visitorId: string | null = null\nlet sessionId: string | null = null\nlet sessionStartTime: number | null = null\nlet navigationTiming: NavigationTiming | null = null\nlet aiDetection: AIDetectionResult | null = null\nlet behavioralClassifier: BehavioralClassifier | null = null\nlet behavioralMLResult: BehavioralMLResult | null = null\n\n/**\n * Debug logger\n */\nfunction log(...args: unknown[]): void {\n if (debugMode) {\n console.log('[Loamly]', ...args)\n }\n}\n\n/**\n * Build API endpoint URL\n */\nfunction endpoint(path: string): string {\n return `${config.apiHost}${path}`\n}\n\n/**\n * Initialize the tracker\n */\nfunction init(userConfig: LoamlyConfig = {}): void {\n if (initialized) {\n log('Already initialized')\n return\n }\n\n config = {\n ...config,\n ...userConfig,\n apiHost: userConfig.apiHost || DEFAULT_CONFIG.apiHost,\n }\n \n debugMode = userConfig.debug ?? false\n \n log('Initializing Loamly Tracker v' + VERSION)\n \n // Get/create visitor ID\n visitorId = getVisitorId()\n log('Visitor ID:', visitorId)\n \n // Get/create session\n const session = getSessionId()\n sessionId = session.sessionId\n sessionStartTime = Date.now()\n log('Session ID:', sessionId, session.isNew ? '(new)' : '(existing)')\n \n // Detect navigation timing (paste vs click)\n navigationTiming = detectNavigationType()\n log('Navigation timing:', navigationTiming)\n \n // Detect AI from referrer\n aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href)\n if (aiDetection) {\n log('AI detected:', aiDetection)\n }\n \n initialized = true\n \n // Auto pageview unless disabled\n if (!userConfig.disableAutoPageview) {\n pageview()\n }\n \n // Set up behavioral tracking unless disabled\n if (!userConfig.disableBehavioral) {\n setupBehavioralTracking()\n }\n \n // Initialize behavioral ML classifier (LOA-180)\n behavioralClassifier = new BehavioralClassifier(10000) // 10s min session\n behavioralClassifier.setOnClassify(handleBehavioralClassification)\n setupBehavioralMLTracking()\n \n log('Initialization complete')\n}\n\n/**\n * Track a page view\n */\nfunction pageview(customUrl?: string): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const url = customUrl || window.location.href\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n url,\n referrer: document.referrer || null,\n title: document.title || null,\n utm_source: extractUTMParams(url).utm_source || null,\n utm_medium: extractUTMParams(url).utm_medium || null,\n utm_campaign: extractUTMParams(url).utm_campaign || null,\n user_agent: navigator.userAgent,\n screen_width: window.screen?.width,\n screen_height: window.screen?.height,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n tracker_version: VERSION,\n navigation_timing: navigationTiming,\n ai_platform: aiDetection?.platform || null,\n is_ai_referrer: aiDetection?.isAI || false,\n }\n\n log('Pageview:', payload)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.visit), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a custom event\n */\nfunction track(eventName: string, options: TrackEventOptions = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_name: eventName,\n event_type: 'custom',\n properties: options.properties || {},\n revenue: options.revenue,\n currency: options.currency || 'USD',\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Event:', eventName, payload)\n\n safeFetch(endpoint('/api/ingest/event'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a conversion/revenue event\n */\nfunction conversion(eventName: string, revenue: number, currency = 'USD'): void {\n track(eventName, { revenue, currency, properties: { type: 'conversion' } })\n}\n\n/**\n * Identify a user\n */\nfunction identify(userId: string, traits: Record<string, unknown> = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n log('Identify:', userId, traits)\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n user_id: userId,\n traits,\n timestamp: new Date().toISOString(),\n }\n\n safeFetch(endpoint('/api/ingest/identify'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Set up behavioral tracking (scroll, time spent, etc.)\n */\nfunction setupBehavioralTracking(): void {\n let maxScrollDepth = 0\n let lastScrollUpdate = 0\n let lastTimeUpdate = Date.now()\n\n // Scroll tracking with requestAnimationFrame throttling\n let scrollTicking = false\n \n window.addEventListener('scroll', () => {\n if (!scrollTicking) {\n requestAnimationFrame(() => {\n const scrollPercent = Math.round(\n ((window.scrollY + window.innerHeight) / document.documentElement.scrollHeight) * 100\n )\n \n if (scrollPercent > maxScrollDepth) {\n maxScrollDepth = scrollPercent\n \n // Report at milestones (25%, 50%, 75%, 100%)\n const milestones = [25, 50, 75, 100]\n for (const milestone of milestones) {\n if (scrollPercent >= milestone && lastScrollUpdate < milestone) {\n lastScrollUpdate = milestone\n sendBehavioralEvent('scroll_depth', { depth: milestone })\n }\n }\n }\n \n scrollTicking = false\n })\n scrollTicking = true\n }\n })\n\n // Time spent tracking (every 5 seconds minimum)\n const trackTimeSpent = (): void => {\n const now = Date.now()\n const delta = now - lastTimeUpdate\n \n if (delta >= DEFAULT_CONFIG.timeSpentThresholdMs) {\n lastTimeUpdate = now\n sendBehavioralEvent('time_spent', { \n seconds: Math.round(delta / 1000),\n total_seconds: Math.round((now - (sessionStartTime || now)) / 1000)\n })\n }\n }\n\n // Track on visibility change\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n trackTimeSpent()\n }\n })\n\n // Track on page unload\n window.addEventListener('beforeunload', () => {\n trackTimeSpent()\n \n // Send final scroll depth\n if (maxScrollDepth > 0) {\n sendBeacon(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: 'scroll_depth_final',\n data: { depth: maxScrollDepth },\n url: window.location.href,\n })\n }\n })\n\n // Form interaction tracking\n document.addEventListener('focusin', (e) => {\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n sendBehavioralEvent('form_focus', {\n field_type: target.tagName.toLowerCase(),\n field_name: (target as HTMLInputElement).name || (target as HTMLInputElement).id || 'unknown',\n })\n }\n })\n\n // Form submit tracking\n document.addEventListener('submit', (e) => {\n const form = e.target as HTMLFormElement\n sendBehavioralEvent('form_submit', {\n form_id: form.id || form.name || 'unknown',\n form_action: form.action ? new URL(form.action).pathname : 'unknown',\n })\n })\n\n // Click tracking for links\n document.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n const link = target.closest('a')\n \n if (link && link.href) {\n const isExternal = link.hostname !== window.location.hostname\n sendBehavioralEvent('click', {\n element: 'link',\n href: truncateText(link.href, 200),\n text: truncateText(link.textContent || '', 100),\n is_external: isExternal,\n })\n }\n })\n}\n\n/**\n * Send a behavioral event\n */\nfunction sendBehavioralEvent(eventType: string, data: Record<string, unknown>): void {\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: eventType,\n data,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Behavioral:', eventType, data)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Set up behavioral ML signal collection (LOA-180)\n * Collects mouse, scroll, and interaction signals for Naive Bayes classification\n */\nfunction setupBehavioralMLTracking(): void {\n if (!behavioralClassifier) return\n \n // Mouse movement tracking (sampled for performance)\n let mouseSampleCount = 0\n document.addEventListener('mousemove', (e) => {\n mouseSampleCount++\n // Sample every 10th event for performance\n if (mouseSampleCount % 10 === 0 && behavioralClassifier) {\n behavioralClassifier.recordMouse(e.clientX, e.clientY)\n }\n }, { passive: true })\n \n // Click tracking\n document.addEventListener('click', () => {\n if (behavioralClassifier) {\n behavioralClassifier.recordClick()\n }\n }, { passive: true })\n \n // Scroll tracking for ML (separate from milestone-based)\n let lastScrollY = 0\n document.addEventListener('scroll', () => {\n const currentY = window.scrollY\n if (Math.abs(currentY - lastScrollY) > 50 && behavioralClassifier) {\n lastScrollY = currentY\n behavioralClassifier.recordScroll(currentY)\n }\n }, { passive: true })\n \n // Focus/blur tracking\n document.addEventListener('focusin', (e) => {\n if (behavioralClassifier) {\n behavioralClassifier.recordFocusBlur('focus')\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {\n behavioralClassifier.recordFormStart(target.id || target.getAttribute('name') || 'unknown')\n }\n }\n }, { passive: true })\n \n document.addEventListener('focusout', (e) => {\n if (behavioralClassifier) {\n behavioralClassifier.recordFocusBlur('blur')\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {\n behavioralClassifier.recordFormEnd(target.id || target.getAttribute('name') || 'unknown')\n }\n }\n }, { passive: true })\n \n // Force classification on page unload\n window.addEventListener('beforeunload', () => {\n if (behavioralClassifier && !behavioralClassifier.hasClassified()) {\n const result = behavioralClassifier.forceClassify()\n if (result) {\n handleBehavioralClassification(result)\n }\n }\n })\n \n // Also try to classify after 30 seconds as backup\n setTimeout(() => {\n if (behavioralClassifier && !behavioralClassifier.hasClassified()) {\n behavioralClassifier.forceClassify()\n }\n }, 30000)\n}\n\n/**\n * Handle behavioral ML classification result\n */\nfunction handleBehavioralClassification(result: BehavioralClassificationResult): void {\n log('Behavioral ML classification:', result)\n \n // Store result\n behavioralMLResult = {\n classification: result.classification,\n humanProbability: result.humanProbability,\n aiProbability: result.aiProbability,\n confidence: result.confidence,\n signals: result.signals,\n sessionDurationMs: result.sessionDurationMs,\n }\n \n // Send to backend\n sendBehavioralEvent('ml_classification', {\n classification: result.classification,\n human_probability: result.humanProbability,\n ai_probability: result.aiProbability,\n confidence: result.confidence,\n signals: result.signals,\n session_duration_ms: result.sessionDurationMs,\n navigation_timing: navigationTiming,\n ai_detection: aiDetection,\n })\n \n // If AI-influenced detected with high confidence, update AI detection\n if (result.classification === 'ai_influenced' && result.confidence >= 0.7) {\n aiDetection = {\n isAI: true,\n confidence: result.confidence,\n method: 'behavioral',\n }\n log('AI detection updated from behavioral ML:', aiDetection)\n }\n}\n\n/**\n * Get current session ID\n */\nfunction getCurrentSessionId(): string | null {\n return sessionId\n}\n\n/**\n * Get current visitor ID\n */\nfunction getCurrentVisitorId(): string | null {\n return visitorId\n}\n\n/**\n * Get AI detection result\n */\nfunction getAIDetectionResult(): AIDetectionResult | null {\n return aiDetection\n}\n\n/**\n * Get navigation timing result\n */\nfunction getNavigationTimingResult(): NavigationTiming | null {\n return navigationTiming\n}\n\n/**\n * Get behavioral ML classification result\n */\nfunction getBehavioralMLResult(): BehavioralMLResult | null {\n return behavioralMLResult\n}\n\n/**\n * Check if initialized\n */\nfunction isTrackerInitialized(): boolean {\n return initialized\n}\n\n/**\n * Reset the tracker\n */\nfunction reset(): void {\n log('Resetting tracker')\n initialized = false\n visitorId = null\n sessionId = null\n sessionStartTime = null\n navigationTiming = null\n aiDetection = null\n behavioralClassifier = null\n behavioralMLResult = null\n \n try {\n sessionStorage.removeItem('loamly_session')\n sessionStorage.removeItem('loamly_start')\n } catch {\n // Ignore\n }\n}\n\n/**\n * Enable/disable debug mode\n */\nfunction setDebug(enabled: boolean): void {\n debugMode = enabled\n log('Debug mode:', enabled ? 'enabled' : 'disabled')\n}\n\n/**\n * The Loamly Tracker instance\n */\nexport const loamly: LoamlyTracker = {\n init,\n pageview,\n track,\n conversion,\n identify,\n getSessionId: getCurrentSessionId,\n getVisitorId: getCurrentVisitorId,\n getAIDetection: getAIDetectionResult,\n getNavigationTiming: getNavigationTimingResult,\n getBehavioralML: getBehavioralMLResult,\n isInitialized: isTrackerInitialized,\n reset,\n debug: setDebug,\n}\n\nexport default loamly\n\n\n"],"mappings":"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","names":["config"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var Loamly=(()=>{var S=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var z=(e,t)=>{for(var n in t)S(e,n,{get:t[n],enumerable:!0})},V=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of B(t))!F.call(e,o)&&o!==n&&S(e,o,{get:()=>t[o],enumerable:!(i=H(t,o))||i.enumerable});return e};var j=e=>V(S({},"__esModule",{value:!0}),e);var it={};z(it,{default:()=>nt,loamly:()=>w});var p="1.6.0",d={apiHost:"https://app.loamly.ai",endpoints:{visit:"/api/ingest/visit",behavioral:"/api/ingest/behavioral",session:"/api/ingest/session",resolve:"/api/tracker/resolve",health:"/api/tracker/health",ping:"/api/tracker/ping"},pingInterval:3e4,batchSize:10,batchTimeout:5e3,sessionTimeout:18e5,maxTextLength:100,timeSpentThresholdMs:5e3},_={"chatgpt.com":"chatgpt","chat.openai.com":"chatgpt","claude.ai":"claude","perplexity.ai":"perplexity","bard.google.com":"bard","gemini.google.com":"gemini","copilot.microsoft.com":"copilot","github.com/copilot":"github-copilot","you.com":"you","phind.com":"phind","poe.com":"poe"};function N(){try{let e=performance.getEntriesByType("navigation");if(!e||e.length===0)return{nav_type:"unknown",confidence:0,signals:["no_timing_data"]};let t=e[0],n=[],i=0,o=t.fetchStart-t.startTime;o<5?(i+=.25,n.push("instant_fetch_start")):o<20&&(i+=.15,n.push("fast_fetch_start")),t.domainLookupEnd-t.domainLookupStart===0&&(i+=.15,n.push("no_dns_lookup")),t.connectEnd-t.connectStart===0&&(i+=.15,n.push("no_tcp_connect")),t.redirectCount===0&&(i+=.1,n.push("no_redirects")),q(t)<10&&(i+=.15,n.push("uniform_timing")),(!document.referrer||document.referrer==="")&&(i+=.1,n.push("no_referrer"));let x=Math.min(i,1);return{nav_type:i>=.5?"likely_paste":"likely_click",confidence:Math.round(x*1e3)/1e3,signals:n}}catch{return{nav_type:"unknown",confidence:0,signals:["detection_error"]}}}function q(e){let t=[e.fetchStart-e.startTime,e.domainLookupEnd-e.domainLookupStart,e.connectEnd-e.connectStart,e.responseStart-e.requestStart].filter(o=>o>=0);if(t.length===0)return 100;let n=t.reduce((o,r)=>o+r,0)/t.length,i=t.reduce((o,r)=>o+Math.pow(r-n,2),0)/t.length;return Math.sqrt(i)}function D(e){if(!e)return null;try{let n=new URL(e).hostname.toLowerCase();for(let[i,o]of Object.entries(_))if(n.includes(i)||e.includes(i))return{isAI:!0,platform:o,confidence:.95,method:"referrer"};return null}catch{for(let[t,n]of Object.entries(_))if(e.toLowerCase().includes(t.toLowerCase()))return{isAI:!0,platform:n,confidence:.85,method:"referrer"};return null}}function R(e){try{let n=new URL(e).searchParams.get("utm_source")?.toLowerCase();if(n){for(let[i,o]of Object.entries(_))if(n.includes(i.split(".")[0]))return{isAI:!0,platform:o,confidence:.99,method:"referrer"};if(n.includes("ai")||n.includes("llm")||n.includes("chatbot"))return{isAI:!0,platform:n,confidence:.9,method:"referrer"}}return null}catch{return null}}function T(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function C(){try{let e=localStorage.getItem("_loamly_vid");if(e)return e;let t=T();return localStorage.setItem("_loamly_vid",t),t}catch{return T()}}function O(){try{let e=sessionStorage.getItem("loamly_session"),t=sessionStorage.getItem("loamly_start");if(e&&t)return{sessionId:e,isNew:!1};let n=T(),i=Date.now().toString();return sessionStorage.setItem("loamly_session",n),sessionStorage.setItem("loamly_start",i),{sessionId:n,isNew:!0}}catch{return{sessionId:T(),isNew:!0}}}function I(e){let t={};try{let n=new URL(e).searchParams,i=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"];for(let o of i){let r=n.get(o);r&&(t[o]=r)}}catch{}return t}function b(e,t){return e.length<=t?e:e.substring(0,t-3)+"..."}async function g(e,t,n=1e4){try{let i=new AbortController,o=setTimeout(()=>i.abort(),n),r=await fetch(e,{...t,signal:i.signal});return clearTimeout(o),r}catch{return null}}function M(e,t){return typeof navigator<"u"&&navigator.sendBeacon?navigator.sendBeacon(e,JSON.stringify(t)):!1}var k={apiHost:d.apiHost},f=!1,L=!1,l=null,u=null,A=null,y=null,m=null;function a(...e){L&&console.log("[Loamly]",...e)}function v(e){return`${k.apiHost}${e}`}function G(e={}){if(f){a("Already initialized");return}k={...k,...e,apiHost:e.apiHost||d.apiHost},L=e.debug??!1,a("Initializing Loamly Tracker v"+p),l=C(),a("Visitor ID:",l);let t=O();u=t.sessionId,A=Date.now(),a("Session ID:",u,t.isNew?"(new)":"(existing)"),y=N(),a("Navigation timing:",y),m=D(document.referrer)||R(window.location.href),m&&a("AI detected:",m),f=!0,e.disableAutoPageview||P(),e.disableBehavioral||$(),a("Initialization complete")}function P(e){if(!f){a("Not initialized, call init() first");return}let t=e||window.location.href,n={visitor_id:l,session_id:u,url:t,referrer:document.referrer||null,title:document.title||null,utm_source:I(t).utm_source||null,utm_medium:I(t).utm_medium||null,utm_campaign:I(t).utm_campaign||null,user_agent:navigator.userAgent,screen_width:window.screen?.width,screen_height:window.screen?.height,language:navigator.language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,tracker_version:p,navigation_timing:y,ai_platform:m?.platform||null,is_ai_referrer:m?.isAI||!1};a("Pageview:",n),g(v(d.endpoints.visit),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}function U(e,t={}){if(!f){a("Not initialized, call init() first");return}let n={visitor_id:l,session_id:u,event_name:e,event_type:"custom",properties:t.properties||{},revenue:t.revenue,currency:t.currency||"USD",url:window.location.href,timestamp:new Date().toISOString(),tracker_version:p};a("Event:",e,n),g(v("/api/ingest/event"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}function J(e,t,n="USD"){U(e,{revenue:t,currency:n,properties:{type:"conversion"}})}function K(e,t={}){if(!f){a("Not initialized, call init() first");return}a("Identify:",e,t);let n={visitor_id:l,session_id:u,user_id:e,traits:t,timestamp:new Date().toISOString()};g(v("/api/ingest/identify"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}function $(){let e=0,t=0,n=Date.now(),i=!1;window.addEventListener("scroll",()=>{i||(requestAnimationFrame(()=>{let r=Math.round((window.scrollY+window.innerHeight)/document.documentElement.scrollHeight*100);if(r>e){e=r;let s=[25,50,75,100];for(let c of s)r>=c&&t<c&&(t=c,h("scroll_depth",{depth:c}))}i=!1}),i=!0)});let o=()=>{let r=Date.now(),s=r-n;s>=d.timeSpentThresholdMs&&(n=r,h("time_spent",{seconds:Math.round(s/1e3),total_seconds:Math.round((r-(A||r))/1e3)}))};document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&o()}),window.addEventListener("beforeunload",()=>{o(),e>0&&M(v(d.endpoints.behavioral),{visitor_id:l,session_id:u,event_type:"scroll_depth_final",data:{depth:e},url:window.location.href})}),document.addEventListener("focusin",r=>{let s=r.target;(s.tagName==="INPUT"||s.tagName==="TEXTAREA"||s.tagName==="SELECT")&&h("form_focus",{field_type:s.tagName.toLowerCase(),field_name:s.name||s.id||"unknown"})}),document.addEventListener("submit",r=>{let s=r.target;h("form_submit",{form_id:s.id||s.name||"unknown",form_action:s.action?new URL(s.action).pathname:"unknown"})}),document.addEventListener("click",r=>{let c=r.target.closest("a");if(c&&c.href){let x=c.hostname!==window.location.hostname;h("click",{element:"link",href:b(c.href,200),text:b(c.textContent||"",100),is_external:x})}})}function h(e,t){let n={visitor_id:l,session_id:u,event_type:e,data:t,url:window.location.href,timestamp:new Date().toISOString(),tracker_version:p};a("Behavioral:",e,t),g(v(d.endpoints.behavioral),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}function W(){return u}function X(){return l}function Y(){return m}function Z(){return y}function Q(){return f}function tt(){a("Resetting tracker"),f=!1,l=null,u=null,A=null,y=null,m=null;try{sessionStorage.removeItem("loamly_session"),sessionStorage.removeItem("loamly_start")}catch{}}function et(e){L=e,a("Debug mode:",e?"enabled":"disabled")}var w={init:G,pageview:P,track:U,conversion:J,identify:K,getSessionId:W,getVisitorId:X,getAIDetection:Y,getNavigationTiming:Z,isInitialized:Q,reset:tt,debug:et};function E(){let e=document.getElementsByTagName("script"),t=null;for(let i of e)if(i.src.includes("loamly")||i.dataset.loamly!==void 0){t=i;break}if(!t)return;let n={};t.dataset.apiKey&&(n.apiKey=t.dataset.apiKey),t.dataset.apiHost&&(n.apiHost=t.dataset.apiHost),t.dataset.debug==="true"&&(n.debug=!0),t.dataset.disableAutoPageview==="true"&&(n.disableAutoPageview=!0),t.dataset.disableBehavioral==="true"&&(n.disableBehavioral=!0),(n.apiKey||t.dataset.loamly!==void 0)&&w.init(n)}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",E):typeof requestIdleCallback<"u"?requestIdleCallback(E):setTimeout(E,0));var nt=w;return j(it);})();
|
|
1
|
+
"use strict";var Loamly=(()=>{var D=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var ie=Object.prototype.hasOwnProperty;var ne=(t,e)=>{for(var i in e)D(t,i,{get:e[i],enumerable:!0})},oe=(t,e,i,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of te(e))!ie.call(t,o)&&o!==i&&D(t,o,{get:()=>e[o],enumerable:!(n=ee(e,o))||n.enumerable});return t};var se=t=>oe(D({},"__esModule",{value:!0}),t);var Te={};ne(Te,{default:()=>be,loamly:()=>M});var x="1.7.0",v={apiHost:"https://app.loamly.ai",endpoints:{visit:"/api/ingest/visit",behavioral:"/api/ingest/behavioral",session:"/api/ingest/session",resolve:"/api/tracker/resolve",health:"/api/tracker/health",ping:"/api/tracker/ping"},pingInterval:3e4,batchSize:10,batchTimeout:5e3,sessionTimeout:18e5,maxTextLength:100,timeSpentThresholdMs:5e3},k={"chatgpt.com":"chatgpt","chat.openai.com":"chatgpt","claude.ai":"claude","perplexity.ai":"perplexity","bard.google.com":"bard","gemini.google.com":"gemini","copilot.microsoft.com":"copilot","github.com/copilot":"github-copilot","you.com":"you","phind.com":"phind","poe.com":"poe"};function V(){try{let t=performance.getEntriesByType("navigation");if(!t||t.length===0)return{nav_type:"unknown",confidence:0,signals:["no_timing_data"]};let e=t[0],i=[],n=0,o=e.fetchStart-e.startTime;o<5?(n+=.25,i.push("instant_fetch_start")):o<20&&(n+=.15,i.push("fast_fetch_start")),e.domainLookupEnd-e.domainLookupStart===0&&(n+=.15,i.push("no_dns_lookup")),e.connectEnd-e.connectStart===0&&(n+=.15,i.push("no_tcp_connect")),e.redirectCount===0&&(n+=.1,i.push("no_redirects")),ae(e)<10&&(n+=.15,i.push("uniform_timing")),(!document.referrer||document.referrer==="")&&(n+=.1,i.push("no_referrer"));let c=Math.min(n,1);return{nav_type:n>=.5?"likely_paste":"likely_click",confidence:Math.round(c*1e3)/1e3,signals:i}}catch{return{nav_type:"unknown",confidence:0,signals:["detection_error"]}}}function ae(t){let e=[t.fetchStart-t.startTime,t.domainLookupEnd-t.domainLookupStart,t.connectEnd-t.connectStart,t.responseStart-t.requestStart].filter(o=>o>=0);if(e.length===0)return 100;let i=e.reduce((o,s)=>o+s,0)/e.length,n=e.reduce((o,s)=>o+Math.pow(s-i,2),0)/e.length;return Math.sqrt(n)}function z(t){if(!t)return null;try{let i=new URL(t).hostname.toLowerCase();for(let[n,o]of Object.entries(k))if(i.includes(n)||t.includes(n))return{isAI:!0,platform:o,confidence:.95,method:"referrer"};return null}catch{for(let[e,i]of Object.entries(k))if(t.toLowerCase().includes(e.toLowerCase()))return{isAI:!0,platform:i,confidence:.85,method:"referrer"};return null}}function Y(t){try{let i=new URL(t).searchParams.get("utm_source")?.toLowerCase();if(i){for(let[n,o]of Object.entries(k))if(i.includes(n.split(".")[0]))return{isAI:!0,platform:o,confidence:.99,method:"referrer"};if(i.includes("ai")||i.includes("llm")||i.includes("chatbot"))return{isAI:!0,platform:i,confidence:.9,method:"referrer"}}return null}catch{return null}}var G={human:{time_to_first_click_delayed:.85,time_to_first_click_normal:.75,time_to_first_click_fast:.5,time_to_first_click_immediate:.25,scroll_speed_variable:.8,scroll_speed_erratic:.7,scroll_speed_uniform:.35,scroll_speed_none:.45,nav_timing_click:.75,nav_timing_unknown:.55,nav_timing_paste:.35,has_referrer:.7,no_referrer:.45,homepage_landing:.65,deep_landing:.5,mouse_movement_curved:.9,mouse_movement_linear:.3,mouse_movement_none:.4,form_fill_normal:.85,form_fill_fast:.6,form_fill_instant:.2,focus_blur_normal:.75,focus_blur_rapid:.45},ai_influenced:{time_to_first_click_immediate:.75,time_to_first_click_fast:.55,time_to_first_click_normal:.4,time_to_first_click_delayed:.35,scroll_speed_none:.55,scroll_speed_uniform:.7,scroll_speed_variable:.35,scroll_speed_erratic:.4,nav_timing_paste:.75,nav_timing_unknown:.5,nav_timing_click:.35,no_referrer:.65,has_referrer:.4,deep_landing:.6,homepage_landing:.45,mouse_movement_none:.6,mouse_movement_linear:.75,mouse_movement_curved:.25,form_fill_instant:.8,form_fill_fast:.55,form_fill_normal:.3,focus_blur_rapid:.6,focus_blur_normal:.4}},j={human:.85,ai_influenced:.15},q=.5,C=class{constructor(e=1e4){this.classified=!1;this.result=null;this.onClassify=null;this.minSessionTime=e,this.data={firstClickTime:null,scrollEvents:[],mouseEvents:[],formEvents:[],focusBlurEvents:[],startTime:Date.now()}}setOnClassify(e){this.onClassify=e}recordClick(){this.data.firstClickTime===null&&(this.data.firstClickTime=Date.now()),this.checkAndClassify()}recordScroll(e){this.data.scrollEvents.push({time:Date.now(),position:e}),this.data.scrollEvents.length>50&&(this.data.scrollEvents=this.data.scrollEvents.slice(-50)),this.checkAndClassify()}recordMouse(e,i){this.data.mouseEvents.push({time:Date.now(),x:e,y:i}),this.data.mouseEvents.length>100&&(this.data.mouseEvents=this.data.mouseEvents.slice(-100)),this.checkAndClassify()}recordFormStart(e){this.data.formEvents.find(n=>n.fieldId===e&&n.endTime===0)||this.data.formEvents.push({fieldId:e,startTime:Date.now(),endTime:0})}recordFormEnd(e){let i=this.data.formEvents.find(n=>n.fieldId===e&&n.endTime===0);i&&(i.endTime=Date.now()),this.checkAndClassify()}recordFocusBlur(e){this.data.focusBlurEvents.push({type:e,time:Date.now()}),this.data.focusBlurEvents.length>20&&(this.data.focusBlurEvents=this.data.focusBlurEvents.slice(-20))}checkAndClassify(){this.classified||Date.now()-this.data.startTime<this.minSessionTime||!(this.data.scrollEvents.length>=2||this.data.mouseEvents.length>=5||this.data.firstClickTime!==null)||this.classify()}forceClassify(){return this.classified?this.result:this.classify()}classify(){let e=Date.now()-this.data.startTime,i=this.extractSignals(),n=Math.log(j.human),o=Math.log(j.ai_influenced);for(let T of i){let A=G.human[T]??q,U=G.ai_influenced[T]??q;n+=Math.log(A),o+=Math.log(U)}let s=Math.max(n,o),a=Math.exp(n-s),r=Math.exp(o-s),c=a+r,m=a/c,p=r/c,b,g;return m>.6?(b="human",g=m):p>.6?(b="ai_influenced",g=p):(b="uncertain",g=Math.max(m,p)),this.result={classification:b,humanProbability:m,aiProbability:p,confidence:g,signals:i,timestamp:Date.now(),sessionDurationMs:e},this.classified=!0,this.onClassify&&this.onClassify(this.result),this.result}extractSignals(){let e=[];if(this.data.firstClickTime!==null){let n=this.data.firstClickTime-this.data.startTime;n<500?e.push("time_to_first_click_immediate"):n<2e3?e.push("time_to_first_click_fast"):n<1e4?e.push("time_to_first_click_normal"):e.push("time_to_first_click_delayed")}if(this.data.scrollEvents.length===0)e.push("scroll_speed_none");else if(this.data.scrollEvents.length>=3){let n=[];for(let c=1;c<this.data.scrollEvents.length;c++){let m=this.data.scrollEvents[c].time-this.data.scrollEvents[c-1].time;n.push(m)}let o=n.reduce((c,m)=>c+m,0)/n.length,s=n.reduce((c,m)=>c+Math.pow(m-o,2),0)/n.length,a=Math.sqrt(s),r=o>0?a/o:0;r<.2?e.push("scroll_speed_uniform"):r<.6?e.push("scroll_speed_variable"):e.push("scroll_speed_erratic")}if(this.data.mouseEvents.length===0)e.push("mouse_movement_none");else if(this.data.mouseEvents.length>=10){let n=Math.min(this.data.mouseEvents.length,20),o=this.data.mouseEvents.slice(-n),s=0,a=0,r=0,c=0;for(let d of o)s+=d.x,a+=d.y,r+=d.x*d.y,c+=d.x*d.x;let m=n*c-s*s,p=m!==0?(n*r-s*a)/m:0,b=(a-p*s)/n,g=0,T=0,A=a/n;for(let d of o){let Q=p*d.x+b;g+=Math.pow(d.y-Q,2),T+=Math.pow(d.y-A,2)}(T!==0?1-g/T:0)>.95?e.push("mouse_movement_linear"):e.push("mouse_movement_curved")}let i=this.data.formEvents.filter(n=>n.endTime>0);if(i.length>0){let n=i.reduce((o,s)=>o+(s.endTime-s.startTime),0)/i.length;n<100?e.push("form_fill_instant"):n<500?e.push("form_fill_fast"):e.push("form_fill_normal")}if(this.data.focusBlurEvents.length>=4){let n=this.data.focusBlurEvents.slice(-10),o=[];for(let a=1;a<n.length;a++)o.push(n[a].time-n[a-1].time);o.reduce((a,r)=>a+r,0)/o.length<1e3?e.push("focus_blur_rapid"):e.push("focus_blur_normal")}return e}addContextSignal(e){}getResult(){return this.result}hasClassified(){return this.classified}};function L(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}function W(){try{let t=localStorage.getItem("_loamly_vid");if(t)return t;let e=L();return localStorage.setItem("_loamly_vid",e),e}catch{return L()}}function X(){try{let t=sessionStorage.getItem("loamly_session"),e=sessionStorage.getItem("loamly_start");if(t&&e)return{sessionId:t,isNew:!1};let i=L(),n=Date.now().toString();return sessionStorage.setItem("loamly_session",i),sessionStorage.setItem("loamly_start",n),{sessionId:i,isNew:!0}}catch{return{sessionId:L(),isNew:!0}}}function B(t){let e={};try{let i=new URL(t).searchParams,n=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"];for(let o of n){let s=i.get(o);s&&(e[o]=s)}}catch{}return e}function R(t,e){return t.length<=e?t:t.substring(0,e-3)+"..."}async function I(t,e,i=1e4){try{let n=new AbortController,o=setTimeout(()=>n.abort(),i),s=await fetch(t,{...e,signal:n.signal});return clearTimeout(o),s}catch{return null}}function J(t,e){return typeof navigator<"u"&&navigator.sendBeacon?navigator.sendBeacon(t,JSON.stringify(e)):!1}var N={apiHost:v.apiHost},y=!1,P=!1,_=null,h=null,O=null,w=null,f=null,l=null,F=null;function u(...t){P&&console.log("[Loamly]",...t)}function S(t){return`${N.apiHost}${t}`}function re(t={}){if(y){u("Already initialized");return}N={...N,...t,apiHost:t.apiHost||v.apiHost},P=t.debug??!1,u("Initializing Loamly Tracker v"+x),_=W(),u("Visitor ID:",_);let e=X();h=e.sessionId,O=Date.now(),u("Session ID:",h,e.isNew?"(new)":"(existing)"),w=V(),u("Navigation timing:",w),f=z(document.referrer)||Y(window.location.href),f&&u("AI detected:",f),y=!0,t.disableAutoPageview||K(),t.disableBehavioral||ue(),l=new C(1e4),l.setOnClassify(Z),me(),u("Initialization complete")}function K(t){if(!y){u("Not initialized, call init() first");return}let e=t||window.location.href,i={visitor_id:_,session_id:h,url:e,referrer:document.referrer||null,title:document.title||null,utm_source:B(e).utm_source||null,utm_medium:B(e).utm_medium||null,utm_campaign:B(e).utm_campaign||null,user_agent:navigator.userAgent,screen_width:window.screen?.width,screen_height:window.screen?.height,language:navigator.language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,tracker_version:x,navigation_timing:w,ai_platform:f?.platform||null,is_ai_referrer:f?.isAI||!1};u("Pageview:",i),I(S(v.endpoints.visit),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}function $(t,e={}){if(!y){u("Not initialized, call init() first");return}let i={visitor_id:_,session_id:h,event_name:t,event_type:"custom",properties:e.properties||{},revenue:e.revenue,currency:e.currency||"USD",url:window.location.href,timestamp:new Date().toISOString(),tracker_version:x};u("Event:",t,i),I(S("/api/ingest/event"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}function le(t,e,i="USD"){$(t,{revenue:e,currency:i,properties:{type:"conversion"}})}function ce(t,e={}){if(!y){u("Not initialized, call init() first");return}u("Identify:",t,e);let i={visitor_id:_,session_id:h,user_id:t,traits:e,timestamp:new Date().toISOString()};I(S("/api/ingest/identify"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}function ue(){let t=0,e=0,i=Date.now(),n=!1;window.addEventListener("scroll",()=>{n||(requestAnimationFrame(()=>{let s=Math.round((window.scrollY+window.innerHeight)/document.documentElement.scrollHeight*100);if(s>t){t=s;let a=[25,50,75,100];for(let r of a)s>=r&&e<r&&(e=r,E("scroll_depth",{depth:r}))}n=!1}),n=!0)});let o=()=>{let s=Date.now(),a=s-i;a>=v.timeSpentThresholdMs&&(i=s,E("time_spent",{seconds:Math.round(a/1e3),total_seconds:Math.round((s-(O||s))/1e3)}))};document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&o()}),window.addEventListener("beforeunload",()=>{o(),t>0&&J(S(v.endpoints.behavioral),{visitor_id:_,session_id:h,event_type:"scroll_depth_final",data:{depth:t},url:window.location.href})}),document.addEventListener("focusin",s=>{let a=s.target;(a.tagName==="INPUT"||a.tagName==="TEXTAREA"||a.tagName==="SELECT")&&E("form_focus",{field_type:a.tagName.toLowerCase(),field_name:a.name||a.id||"unknown"})}),document.addEventListener("submit",s=>{let a=s.target;E("form_submit",{form_id:a.id||a.name||"unknown",form_action:a.action?new URL(a.action).pathname:"unknown"})}),document.addEventListener("click",s=>{let r=s.target.closest("a");if(r&&r.href){let c=r.hostname!==window.location.hostname;E("click",{element:"link",href:R(r.href,200),text:R(r.textContent||"",100),is_external:c})}})}function E(t,e){let i={visitor_id:_,session_id:h,event_type:t,data:e,url:window.location.href,timestamp:new Date().toISOString(),tracker_version:x};u("Behavioral:",t,e),I(S(v.endpoints.behavioral),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}function me(){if(!l)return;let t=0;document.addEventListener("mousemove",i=>{t++,t%10===0&&l&&l.recordMouse(i.clientX,i.clientY)},{passive:!0}),document.addEventListener("click",()=>{l&&l.recordClick()},{passive:!0});let e=0;document.addEventListener("scroll",()=>{let i=window.scrollY;Math.abs(i-e)>50&&l&&(e=i,l.recordScroll(i))},{passive:!0}),document.addEventListener("focusin",i=>{if(l){l.recordFocusBlur("focus");let n=i.target;(n.tagName==="INPUT"||n.tagName==="TEXTAREA")&&l.recordFormStart(n.id||n.getAttribute("name")||"unknown")}},{passive:!0}),document.addEventListener("focusout",i=>{if(l){l.recordFocusBlur("blur");let n=i.target;(n.tagName==="INPUT"||n.tagName==="TEXTAREA")&&l.recordFormEnd(n.id||n.getAttribute("name")||"unknown")}},{passive:!0}),window.addEventListener("beforeunload",()=>{if(l&&!l.hasClassified()){let i=l.forceClassify();i&&Z(i)}}),setTimeout(()=>{l&&!l.hasClassified()&&l.forceClassify()},3e4)}function Z(t){u("Behavioral ML classification:",t),F={classification:t.classification,humanProbability:t.humanProbability,aiProbability:t.aiProbability,confidence:t.confidence,signals:t.signals,sessionDurationMs:t.sessionDurationMs},E("ml_classification",{classification:t.classification,human_probability:t.humanProbability,ai_probability:t.aiProbability,confidence:t.confidence,signals:t.signals,session_duration_ms:t.sessionDurationMs,navigation_timing:w,ai_detection:f}),t.classification==="ai_influenced"&&t.confidence>=.7&&(f={isAI:!0,confidence:t.confidence,method:"behavioral"},u("AI detection updated from behavioral ML:",f))}function de(){return h}function fe(){return _}function _e(){return f}function he(){return w}function pe(){return F}function ge(){return y}function ve(){u("Resetting tracker"),y=!1,_=null,h=null,O=null,w=null,f=null,l=null,F=null;try{sessionStorage.removeItem("loamly_session"),sessionStorage.removeItem("loamly_start")}catch{}}function ye(t){P=t,u("Debug mode:",t?"enabled":"disabled")}var M={init:re,pageview:K,track:$,conversion:le,identify:ce,getSessionId:de,getVisitorId:fe,getAIDetection:_e,getNavigationTiming:he,getBehavioralML:pe,isInitialized:ge,reset:ve,debug:ye};function H(){let t=document.getElementsByTagName("script"),e=null;for(let n of t)if(n.src.includes("loamly")||n.dataset.loamly!==void 0){e=n;break}if(!e)return;let i={};e.dataset.apiKey&&(i.apiKey=e.dataset.apiKey),e.dataset.apiHost&&(i.apiHost=e.dataset.apiHost),e.dataset.debug==="true"&&(i.debug=!0),e.dataset.disableAutoPageview==="true"&&(i.disableAutoPageview=!0),e.dataset.disableBehavioral==="true"&&(i.disableBehavioral=!0),(i.apiKey||e.dataset.loamly!==void 0)&&M.init(i)}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",H):typeof requestIdleCallback<"u"?requestIdleCallback(H):setTimeout(H,0));var be=M;return se(Te);})();
|
|
2
2
|
//# sourceMappingURL=loamly.iife.min.global.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/config.ts","../src/detection/navigation-timing.ts","../src/detection/referrer.ts","../src/utils.ts","../src/core.ts"],"sourcesContent":["/**\n * Loamly Tracker - Browser Bundle (IIFE)\n * \n * This file is the entry point for the browser script tag version.\n * It auto-initializes from data attributes on the script tag.\n * \n * Usage:\n * <script src=\"https://unpkg.com/@loamly/tracker\" data-api-key=\"your-key\"></script>\n * \n * @module @loamly/tracker/browser\n */\n\nimport { loamly } from './core'\nimport type { LoamlyConfig } from './types'\n\n// Auto-initialize from script tag data attributes\nfunction autoInit(): void {\n // Find the script tag that loaded us\n const scripts = document.getElementsByTagName('script')\n let scriptTag: HTMLScriptElement | null = null\n \n for (const script of scripts) {\n if (script.src.includes('loamly') || script.dataset.loamly !== undefined) {\n scriptTag = script\n break\n }\n }\n\n if (!scriptTag) {\n // No matching script tag found, don't auto-init\n return\n }\n\n // Extract configuration from data attributes\n const config: LoamlyConfig = {}\n \n if (scriptTag.dataset.apiKey) {\n config.apiKey = scriptTag.dataset.apiKey\n }\n \n if (scriptTag.dataset.apiHost) {\n config.apiHost = scriptTag.dataset.apiHost\n }\n \n if (scriptTag.dataset.debug === 'true') {\n config.debug = true\n }\n \n if (scriptTag.dataset.disableAutoPageview === 'true') {\n config.disableAutoPageview = true\n }\n \n if (scriptTag.dataset.disableBehavioral === 'true') {\n config.disableBehavioral = true\n }\n\n // Initialize if we have configuration\n if (config.apiKey || scriptTag.dataset.loamly !== undefined) {\n loamly.init(config)\n }\n}\n\n// Run auto-init when DOM is ready\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoInit)\n } else {\n // Use requestIdleCallback if available for non-blocking init\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(autoInit)\n } else {\n setTimeout(autoInit, 0)\n }\n }\n}\n\n// Export for manual usage\nexport { loamly }\nexport default loamly\n\n\n","/**\n * Loamly Tracker Configuration\n * @module @loamly/tracker\n */\n\nexport const VERSION = '1.6.0'\n\nexport const DEFAULT_CONFIG = {\n apiHost: 'https://app.loamly.ai',\n endpoints: {\n visit: '/api/ingest/visit',\n behavioral: '/api/ingest/behavioral',\n session: '/api/ingest/session',\n resolve: '/api/tracker/resolve',\n health: '/api/tracker/health',\n ping: '/api/tracker/ping',\n },\n pingInterval: 30000, // 30 seconds\n batchSize: 10,\n batchTimeout: 5000,\n sessionTimeout: 1800000, // 30 minutes\n maxTextLength: 100,\n timeSpentThresholdMs: 5000, // Only send time_spent when delta >= 5 seconds\n} as const\n\n/**\n * Known AI platforms for referrer detection\n */\nexport const AI_PLATFORMS: Record<string, string> = {\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n 'claude.ai': 'claude',\n 'perplexity.ai': 'perplexity',\n 'bard.google.com': 'bard',\n 'gemini.google.com': 'gemini',\n 'copilot.microsoft.com': 'copilot',\n 'github.com/copilot': 'github-copilot',\n 'you.com': 'you',\n 'phind.com': 'phind',\n 'poe.com': 'poe',\n}\n\n/**\n * User agents of known AI crawlers\n */\nexport const AI_BOT_PATTERNS = [\n 'GPTBot',\n 'ChatGPT-User',\n 'ClaudeBot',\n 'Claude-Web',\n 'PerplexityBot',\n 'Amazonbot',\n 'Google-Extended',\n 'CCBot',\n 'anthropic-ai',\n 'cohere-ai',\n]\n\n\n","/**\n * Navigation Timing API Detection\n * \n * Detects whether the user arrived via paste (from AI chat) vs click\n * by analyzing Navigation Timing API patterns.\n * \n * @module @loamly/tracker/detection\n */\n\nimport type { NavigationTiming } from '../types'\n\n/**\n * Analyze Navigation Timing API to detect paste vs click navigation\n * \n * When users paste a URL (common after copying from AI chat), \n * the timing patterns are distinctive:\n * 1. fetchStart is virtually immediate after navigationStart\n * 2. DNS/connect times are often 0 (cached or direct)\n * 3. No redirect chain\n * 4. Uniform timing patterns\n * \n * @returns NavigationTiming result with type and confidence\n */\nexport function detectNavigationType(): NavigationTiming {\n try {\n const entries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[]\n \n if (!entries || entries.length === 0) {\n return { nav_type: 'unknown', confidence: 0, signals: ['no_timing_data'] }\n }\n\n const nav = entries[0]\n const signals: string[] = []\n let pasteScore = 0\n\n // Signal 1: fetchStart delta from navigationStart\n // Paste navigation typically has very small delta (< 5ms)\n const fetchStartDelta = nav.fetchStart - nav.startTime\n if (fetchStartDelta < 5) {\n pasteScore += 0.25\n signals.push('instant_fetch_start')\n } else if (fetchStartDelta < 20) {\n pasteScore += 0.15\n signals.push('fast_fetch_start')\n }\n\n // Signal 2: DNS lookup time\n // Paste = likely 0 (direct URL entry, no link warmup)\n const dnsTime = nav.domainLookupEnd - nav.domainLookupStart\n if (dnsTime === 0) {\n pasteScore += 0.15\n signals.push('no_dns_lookup')\n }\n\n // Signal 3: TCP connect time\n // Paste = likely 0 (no preconnect from previous page)\n const connectTime = nav.connectEnd - nav.connectStart\n if (connectTime === 0) {\n pasteScore += 0.15\n signals.push('no_tcp_connect')\n }\n\n // Signal 4: No redirects\n // Paste URLs are typically direct, no redirects\n if (nav.redirectCount === 0) {\n pasteScore += 0.1\n signals.push('no_redirects')\n }\n\n // Signal 5: Timing uniformity check\n // Paste navigation tends to have more uniform patterns\n const timingVariance = calculateTimingVariance(nav)\n if (timingVariance < 10) {\n pasteScore += 0.15\n signals.push('uniform_timing')\n }\n\n // Signal 6: No referrer (common for paste)\n if (!document.referrer || document.referrer === '') {\n pasteScore += 0.1\n signals.push('no_referrer')\n }\n\n // Determine navigation type based on score\n const confidence = Math.min(pasteScore, 1)\n const nav_type = pasteScore >= 0.5 ? 'likely_paste' : 'likely_click'\n\n return {\n nav_type,\n confidence: Math.round(confidence * 1000) / 1000,\n signals,\n }\n } catch {\n return { nav_type: 'unknown', confidence: 0, signals: ['detection_error'] }\n }\n}\n\n/**\n * Calculate timing variance to detect paste patterns\n */\nfunction calculateTimingVariance(nav: PerformanceNavigationTiming): number {\n const timings = [\n nav.fetchStart - nav.startTime,\n nav.domainLookupEnd - nav.domainLookupStart,\n nav.connectEnd - nav.connectStart,\n nav.responseStart - nav.requestStart,\n ].filter((t) => t >= 0)\n\n if (timings.length === 0) return 100\n\n const mean = timings.reduce((a, b) => a + b, 0) / timings.length\n const variance = timings.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / timings.length\n \n return Math.sqrt(variance)\n}\n\n\n","/**\n * Referrer-based AI Detection\n * \n * Detects when users arrive from known AI platforms\n * based on the document.referrer header.\n * \n * @module @loamly/tracker/detection\n */\n\nimport { AI_PLATFORMS } from '../config'\nimport type { AIDetectionResult } from '../types'\n\n/**\n * Detect AI platform from referrer URL\n * \n * @param referrer - The document.referrer value\n * @returns AI detection result or null if no AI detected\n */\nexport function detectAIFromReferrer(referrer: string): AIDetectionResult | null {\n if (!referrer) {\n return null\n }\n\n try {\n const url = new URL(referrer)\n const hostname = url.hostname.toLowerCase()\n\n // Check against known AI platforms\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (hostname.includes(pattern) || referrer.includes(pattern)) {\n return {\n isAI: true,\n platform,\n confidence: 0.95, // High confidence when referrer matches\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n // Invalid URL, try pattern matching on raw string\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (referrer.toLowerCase().includes(pattern.toLowerCase())) {\n return {\n isAI: true,\n platform,\n confidence: 0.85,\n method: 'referrer',\n }\n }\n }\n return null\n }\n}\n\n/**\n * Extract AI platform from UTM parameters\n * \n * @param url - The current page URL\n * @returns AI platform name or null\n */\nexport function detectAIFromUTM(url: string): AIDetectionResult | null {\n try {\n const params = new URL(url).searchParams\n \n // Check utm_source for AI platforms\n const utmSource = params.get('utm_source')?.toLowerCase()\n if (utmSource) {\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (utmSource.includes(pattern.split('.')[0])) {\n return {\n isAI: true,\n platform,\n confidence: 0.99, // Very high confidence from explicit UTM\n method: 'referrer',\n }\n }\n }\n \n // Generic AI source patterns\n if (utmSource.includes('ai') || utmSource.includes('llm') || utmSource.includes('chatbot')) {\n return {\n isAI: true,\n platform: utmSource,\n confidence: 0.9,\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n return null\n }\n}\n\n\n","/**\n * Utility functions for Loamly Tracker\n * @module @loamly/tracker\n */\n\n/**\n * Generate a UUID v4\n */\nexport function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID()\n }\n \n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Get or create a persistent visitor ID\n * (Privacy-respecting, no cookies)\n */\nexport function getVisitorId(): string {\n // Try to get from localStorage first\n try {\n const stored = localStorage.getItem('_loamly_vid')\n if (stored) return stored\n \n const newId = generateUUID()\n localStorage.setItem('_loamly_vid', newId)\n return newId\n } catch {\n // localStorage not available, generate ephemeral ID\n return generateUUID()\n }\n}\n\n/**\n * Get or create a session ID using sessionStorage\n * (Cookie-free session tracking)\n */\nexport function getSessionId(): { sessionId: string; isNew: boolean } {\n try {\n const storedSession = sessionStorage.getItem('loamly_session')\n const storedStart = sessionStorage.getItem('loamly_start')\n \n if (storedSession && storedStart) {\n return { sessionId: storedSession, isNew: false }\n }\n \n const newSession = generateUUID()\n const startTime = Date.now().toString()\n \n sessionStorage.setItem('loamly_session', newSession)\n sessionStorage.setItem('loamly_start', startTime)\n \n return { sessionId: newSession, isNew: true }\n } catch {\n // sessionStorage not available\n return { sessionId: generateUUID(), isNew: true }\n }\n}\n\n/**\n * Extract UTM parameters from URL\n */\nexport function extractUTMParams(url: string): Record<string, string> {\n const params: Record<string, string> = {}\n \n try {\n const searchParams = new URL(url).searchParams\n const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']\n \n for (const key of utmKeys) {\n const value = searchParams.get(key)\n if (value) params[key] = value\n }\n } catch {\n // Invalid URL\n }\n \n return params\n}\n\n/**\n * Truncate text to max length\n */\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.substring(0, maxLength - 3) + '...'\n}\n\n/**\n * Safe fetch with timeout\n */\nexport async function safeFetch(\n url: string,\n options: RequestInit,\n timeout = 10000\n): Promise<Response | null> {\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n \n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n })\n \n clearTimeout(timeoutId)\n return response\n } catch {\n return null\n }\n}\n\n/**\n * Send beacon (for unload events)\n */\nexport function sendBeacon(url: string, data: unknown): boolean {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n return navigator.sendBeacon(url, JSON.stringify(data))\n }\n return false\n}\n\n\n","/**\n * Loamly Tracker Core\n * \n * Cookie-free, privacy-first analytics with AI traffic detection.\n * \n * @module @loamly/tracker\n */\n\nimport { VERSION, DEFAULT_CONFIG } from './config'\nimport { detectNavigationType } from './detection/navigation-timing'\nimport { detectAIFromReferrer, detectAIFromUTM } from './detection/referrer'\nimport { \n getVisitorId, \n getSessionId, \n extractUTMParams, \n truncateText,\n safeFetch,\n sendBeacon \n} from './utils'\nimport type { \n LoamlyConfig, \n LoamlyTracker, \n TrackEventOptions, \n NavigationTiming,\n AIDetectionResult \n} from './types'\n\n// State\nlet config: LoamlyConfig & { apiHost: string } = { apiHost: DEFAULT_CONFIG.apiHost }\nlet initialized = false\nlet debugMode = false\nlet visitorId: string | null = null\nlet sessionId: string | null = null\nlet sessionStartTime: number | null = null\nlet navigationTiming: NavigationTiming | null = null\nlet aiDetection: AIDetectionResult | null = null\n\n/**\n * Debug logger\n */\nfunction log(...args: unknown[]): void {\n if (debugMode) {\n console.log('[Loamly]', ...args)\n }\n}\n\n/**\n * Build API endpoint URL\n */\nfunction endpoint(path: string): string {\n return `${config.apiHost}${path}`\n}\n\n/**\n * Initialize the tracker\n */\nfunction init(userConfig: LoamlyConfig = {}): void {\n if (initialized) {\n log('Already initialized')\n return\n }\n\n config = {\n ...config,\n ...userConfig,\n apiHost: userConfig.apiHost || DEFAULT_CONFIG.apiHost,\n }\n \n debugMode = userConfig.debug ?? false\n \n log('Initializing Loamly Tracker v' + VERSION)\n \n // Get/create visitor ID\n visitorId = getVisitorId()\n log('Visitor ID:', visitorId)\n \n // Get/create session\n const session = getSessionId()\n sessionId = session.sessionId\n sessionStartTime = Date.now()\n log('Session ID:', sessionId, session.isNew ? '(new)' : '(existing)')\n \n // Detect navigation timing (paste vs click)\n navigationTiming = detectNavigationType()\n log('Navigation timing:', navigationTiming)\n \n // Detect AI from referrer\n aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href)\n if (aiDetection) {\n log('AI detected:', aiDetection)\n }\n \n initialized = true\n \n // Auto pageview unless disabled\n if (!userConfig.disableAutoPageview) {\n pageview()\n }\n \n // Set up behavioral tracking unless disabled\n if (!userConfig.disableBehavioral) {\n setupBehavioralTracking()\n }\n \n log('Initialization complete')\n}\n\n/**\n * Track a page view\n */\nfunction pageview(customUrl?: string): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const url = customUrl || window.location.href\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n url,\n referrer: document.referrer || null,\n title: document.title || null,\n utm_source: extractUTMParams(url).utm_source || null,\n utm_medium: extractUTMParams(url).utm_medium || null,\n utm_campaign: extractUTMParams(url).utm_campaign || null,\n user_agent: navigator.userAgent,\n screen_width: window.screen?.width,\n screen_height: window.screen?.height,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n tracker_version: VERSION,\n navigation_timing: navigationTiming,\n ai_platform: aiDetection?.platform || null,\n is_ai_referrer: aiDetection?.isAI || false,\n }\n\n log('Pageview:', payload)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.visit), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a custom event\n */\nfunction track(eventName: string, options: TrackEventOptions = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_name: eventName,\n event_type: 'custom',\n properties: options.properties || {},\n revenue: options.revenue,\n currency: options.currency || 'USD',\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Event:', eventName, payload)\n\n safeFetch(endpoint('/api/ingest/event'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a conversion/revenue event\n */\nfunction conversion(eventName: string, revenue: number, currency = 'USD'): void {\n track(eventName, { revenue, currency, properties: { type: 'conversion' } })\n}\n\n/**\n * Identify a user\n */\nfunction identify(userId: string, traits: Record<string, unknown> = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n log('Identify:', userId, traits)\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n user_id: userId,\n traits,\n timestamp: new Date().toISOString(),\n }\n\n safeFetch(endpoint('/api/ingest/identify'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Set up behavioral tracking (scroll, time spent, etc.)\n */\nfunction setupBehavioralTracking(): void {\n let maxScrollDepth = 0\n let lastScrollUpdate = 0\n let lastTimeUpdate = Date.now()\n\n // Scroll tracking with requestAnimationFrame throttling\n let scrollTicking = false\n \n window.addEventListener('scroll', () => {\n if (!scrollTicking) {\n requestAnimationFrame(() => {\n const scrollPercent = Math.round(\n ((window.scrollY + window.innerHeight) / document.documentElement.scrollHeight) * 100\n )\n \n if (scrollPercent > maxScrollDepth) {\n maxScrollDepth = scrollPercent\n \n // Report at milestones (25%, 50%, 75%, 100%)\n const milestones = [25, 50, 75, 100]\n for (const milestone of milestones) {\n if (scrollPercent >= milestone && lastScrollUpdate < milestone) {\n lastScrollUpdate = milestone\n sendBehavioralEvent('scroll_depth', { depth: milestone })\n }\n }\n }\n \n scrollTicking = false\n })\n scrollTicking = true\n }\n })\n\n // Time spent tracking (every 5 seconds minimum)\n const trackTimeSpent = (): void => {\n const now = Date.now()\n const delta = now - lastTimeUpdate\n \n if (delta >= DEFAULT_CONFIG.timeSpentThresholdMs) {\n lastTimeUpdate = now\n sendBehavioralEvent('time_spent', { \n seconds: Math.round(delta / 1000),\n total_seconds: Math.round((now - (sessionStartTime || now)) / 1000)\n })\n }\n }\n\n // Track on visibility change\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n trackTimeSpent()\n }\n })\n\n // Track on page unload\n window.addEventListener('beforeunload', () => {\n trackTimeSpent()\n \n // Send final scroll depth\n if (maxScrollDepth > 0) {\n sendBeacon(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: 'scroll_depth_final',\n data: { depth: maxScrollDepth },\n url: window.location.href,\n })\n }\n })\n\n // Form interaction tracking\n document.addEventListener('focusin', (e) => {\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n sendBehavioralEvent('form_focus', {\n field_type: target.tagName.toLowerCase(),\n field_name: (target as HTMLInputElement).name || (target as HTMLInputElement).id || 'unknown',\n })\n }\n })\n\n // Form submit tracking\n document.addEventListener('submit', (e) => {\n const form = e.target as HTMLFormElement\n sendBehavioralEvent('form_submit', {\n form_id: form.id || form.name || 'unknown',\n form_action: form.action ? new URL(form.action).pathname : 'unknown',\n })\n })\n\n // Click tracking for links\n document.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n const link = target.closest('a')\n \n if (link && link.href) {\n const isExternal = link.hostname !== window.location.hostname\n sendBehavioralEvent('click', {\n element: 'link',\n href: truncateText(link.href, 200),\n text: truncateText(link.textContent || '', 100),\n is_external: isExternal,\n })\n }\n })\n}\n\n/**\n * Send a behavioral event\n */\nfunction sendBehavioralEvent(eventType: string, data: Record<string, unknown>): void {\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: eventType,\n data,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Behavioral:', eventType, data)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Get current session ID\n */\nfunction getCurrentSessionId(): string | null {\n return sessionId\n}\n\n/**\n * Get current visitor ID\n */\nfunction getCurrentVisitorId(): string | null {\n return visitorId\n}\n\n/**\n * Get AI detection result\n */\nfunction getAIDetectionResult(): AIDetectionResult | null {\n return aiDetection\n}\n\n/**\n * Get navigation timing result\n */\nfunction getNavigationTimingResult(): NavigationTiming | null {\n return navigationTiming\n}\n\n/**\n * Check if initialized\n */\nfunction isTrackerInitialized(): boolean {\n return initialized\n}\n\n/**\n * Reset the tracker\n */\nfunction reset(): void {\n log('Resetting tracker')\n initialized = false\n visitorId = null\n sessionId = null\n sessionStartTime = null\n navigationTiming = null\n aiDetection = null\n \n try {\n sessionStorage.removeItem('loamly_session')\n sessionStorage.removeItem('loamly_start')\n } catch {\n // Ignore\n }\n}\n\n/**\n * Enable/disable debug mode\n */\nfunction setDebug(enabled: boolean): void {\n debugMode = enabled\n log('Debug mode:', enabled ? 'enabled' : 'disabled')\n}\n\n/**\n * The Loamly Tracker instance\n */\nexport const loamly: LoamlyTracker = {\n init,\n pageview,\n track,\n conversion,\n identify,\n getSessionId: getCurrentSessionId,\n getVisitorId: getCurrentVisitorId,\n getAIDetection: getAIDetectionResult,\n getNavigationTiming: getNavigationTimingResult,\n isInitialized: isTrackerInitialized,\n reset,\n debug: setDebug,\n}\n\nexport default loamly\n\n\n"],"mappings":"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","names":["browser_exports","__export","browser_default","loamly","VERSION","DEFAULT_CONFIG","AI_PLATFORMS","detectNavigationType","entries","nav","signals","pasteScore","fetchStartDelta","calculateTimingVariance","confidence","timings","t","mean","a","b","variance","sum","detectAIFromReferrer","referrer","hostname","pattern","platform","AI_PLATFORMS","detectAIFromUTM","url","utmSource","generateUUID","c","r","getVisitorId","stored","newId","getSessionId","storedSession","storedStart","newSession","startTime","extractUTMParams","url","params","searchParams","utmKeys","key","value","truncateText","text","maxLength","safeFetch","options","timeout","controller","timeoutId","response","sendBeacon","data","config","DEFAULT_CONFIG","initialized","debugMode","visitorId","sessionId","sessionStartTime","navigationTiming","aiDetection","log","args","endpoint","path","init","userConfig","VERSION","getVisitorId","session","getSessionId","detectNavigationType","detectAIFromReferrer","detectAIFromUTM","pageview","setupBehavioralTracking","customUrl","url","payload","extractUTMParams","safeFetch","track","eventName","options","conversion","revenue","currency","identify","userId","traits","maxScrollDepth","lastScrollUpdate","lastTimeUpdate","scrollTicking","scrollPercent","milestones","milestone","sendBehavioralEvent","trackTimeSpent","now","delta","sendBeacon","e","target","form","link","isExternal","truncateText","eventType","data","getCurrentSessionId","getCurrentVisitorId","getAIDetectionResult","getNavigationTimingResult","isTrackerInitialized","reset","setDebug","enabled","loamly","autoInit","scripts","scriptTag","script","config","loamly","browser_default","loamly"]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/config.ts","../src/detection/navigation-timing.ts","../src/detection/referrer.ts","../src/detection/behavioral-classifier.ts","../src/utils.ts","../src/core.ts"],"sourcesContent":["/**\n * Loamly Tracker - Browser Bundle (IIFE)\n * \n * This file is the entry point for the browser script tag version.\n * It auto-initializes from data attributes on the script tag.\n * \n * Usage:\n * <script src=\"https://unpkg.com/@loamly/tracker\" data-api-key=\"your-key\"></script>\n * \n * @module @loamly/tracker/browser\n */\n\nimport { loamly } from './core'\nimport type { LoamlyConfig } from './types'\n\n// Auto-initialize from script tag data attributes\nfunction autoInit(): void {\n // Find the script tag that loaded us\n const scripts = document.getElementsByTagName('script')\n let scriptTag: HTMLScriptElement | null = null\n \n for (const script of scripts) {\n if (script.src.includes('loamly') || script.dataset.loamly !== undefined) {\n scriptTag = script\n break\n }\n }\n\n if (!scriptTag) {\n // No matching script tag found, don't auto-init\n return\n }\n\n // Extract configuration from data attributes\n const config: LoamlyConfig = {}\n \n if (scriptTag.dataset.apiKey) {\n config.apiKey = scriptTag.dataset.apiKey\n }\n \n if (scriptTag.dataset.apiHost) {\n config.apiHost = scriptTag.dataset.apiHost\n }\n \n if (scriptTag.dataset.debug === 'true') {\n config.debug = true\n }\n \n if (scriptTag.dataset.disableAutoPageview === 'true') {\n config.disableAutoPageview = true\n }\n \n if (scriptTag.dataset.disableBehavioral === 'true') {\n config.disableBehavioral = true\n }\n\n // Initialize if we have configuration\n if (config.apiKey || scriptTag.dataset.loamly !== undefined) {\n loamly.init(config)\n }\n}\n\n// Run auto-init when DOM is ready\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoInit)\n } else {\n // Use requestIdleCallback if available for non-blocking init\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(autoInit)\n } else {\n setTimeout(autoInit, 0)\n }\n }\n}\n\n// Export for manual usage\nexport { loamly }\nexport default loamly\n\n\n","/**\n * Loamly Tracker Configuration\n * @module @loamly/tracker\n */\n\nexport const VERSION = '1.7.0'\n\nexport const DEFAULT_CONFIG = {\n apiHost: 'https://app.loamly.ai',\n endpoints: {\n visit: '/api/ingest/visit',\n behavioral: '/api/ingest/behavioral',\n session: '/api/ingest/session',\n resolve: '/api/tracker/resolve',\n health: '/api/tracker/health',\n ping: '/api/tracker/ping',\n },\n pingInterval: 30000, // 30 seconds\n batchSize: 10,\n batchTimeout: 5000,\n sessionTimeout: 1800000, // 30 minutes\n maxTextLength: 100,\n timeSpentThresholdMs: 5000, // Only send time_spent when delta >= 5 seconds\n} as const\n\n/**\n * Known AI platforms for referrer detection\n */\nexport const AI_PLATFORMS: Record<string, string> = {\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n 'claude.ai': 'claude',\n 'perplexity.ai': 'perplexity',\n 'bard.google.com': 'bard',\n 'gemini.google.com': 'gemini',\n 'copilot.microsoft.com': 'copilot',\n 'github.com/copilot': 'github-copilot',\n 'you.com': 'you',\n 'phind.com': 'phind',\n 'poe.com': 'poe',\n}\n\n/**\n * User agents of known AI crawlers\n */\nexport const AI_BOT_PATTERNS = [\n 'GPTBot',\n 'ChatGPT-User',\n 'ClaudeBot',\n 'Claude-Web',\n 'PerplexityBot',\n 'Amazonbot',\n 'Google-Extended',\n 'CCBot',\n 'anthropic-ai',\n 'cohere-ai',\n]\n\n\n","/**\n * Navigation Timing API Detection\n * \n * Detects whether the user arrived via paste (from AI chat) vs click\n * by analyzing Navigation Timing API patterns.\n * \n * @module @loamly/tracker/detection\n */\n\nimport type { NavigationTiming } from '../types'\n\n/**\n * Analyze Navigation Timing API to detect paste vs click navigation\n * \n * When users paste a URL (common after copying from AI chat), \n * the timing patterns are distinctive:\n * 1. fetchStart is virtually immediate after navigationStart\n * 2. DNS/connect times are often 0 (cached or direct)\n * 3. No redirect chain\n * 4. Uniform timing patterns\n * \n * @returns NavigationTiming result with type and confidence\n */\nexport function detectNavigationType(): NavigationTiming {\n try {\n const entries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[]\n \n if (!entries || entries.length === 0) {\n return { nav_type: 'unknown', confidence: 0, signals: ['no_timing_data'] }\n }\n\n const nav = entries[0]\n const signals: string[] = []\n let pasteScore = 0\n\n // Signal 1: fetchStart delta from navigationStart\n // Paste navigation typically has very small delta (< 5ms)\n const fetchStartDelta = nav.fetchStart - nav.startTime\n if (fetchStartDelta < 5) {\n pasteScore += 0.25\n signals.push('instant_fetch_start')\n } else if (fetchStartDelta < 20) {\n pasteScore += 0.15\n signals.push('fast_fetch_start')\n }\n\n // Signal 2: DNS lookup time\n // Paste = likely 0 (direct URL entry, no link warmup)\n const dnsTime = nav.domainLookupEnd - nav.domainLookupStart\n if (dnsTime === 0) {\n pasteScore += 0.15\n signals.push('no_dns_lookup')\n }\n\n // Signal 3: TCP connect time\n // Paste = likely 0 (no preconnect from previous page)\n const connectTime = nav.connectEnd - nav.connectStart\n if (connectTime === 0) {\n pasteScore += 0.15\n signals.push('no_tcp_connect')\n }\n\n // Signal 4: No redirects\n // Paste URLs are typically direct, no redirects\n if (nav.redirectCount === 0) {\n pasteScore += 0.1\n signals.push('no_redirects')\n }\n\n // Signal 5: Timing uniformity check\n // Paste navigation tends to have more uniform patterns\n const timingVariance = calculateTimingVariance(nav)\n if (timingVariance < 10) {\n pasteScore += 0.15\n signals.push('uniform_timing')\n }\n\n // Signal 6: No referrer (common for paste)\n if (!document.referrer || document.referrer === '') {\n pasteScore += 0.1\n signals.push('no_referrer')\n }\n\n // Determine navigation type based on score\n const confidence = Math.min(pasteScore, 1)\n const nav_type = pasteScore >= 0.5 ? 'likely_paste' : 'likely_click'\n\n return {\n nav_type,\n confidence: Math.round(confidence * 1000) / 1000,\n signals,\n }\n } catch {\n return { nav_type: 'unknown', confidence: 0, signals: ['detection_error'] }\n }\n}\n\n/**\n * Calculate timing variance to detect paste patterns\n */\nfunction calculateTimingVariance(nav: PerformanceNavigationTiming): number {\n const timings = [\n nav.fetchStart - nav.startTime,\n nav.domainLookupEnd - nav.domainLookupStart,\n nav.connectEnd - nav.connectStart,\n nav.responseStart - nav.requestStart,\n ].filter((t) => t >= 0)\n\n if (timings.length === 0) return 100\n\n const mean = timings.reduce((a, b) => a + b, 0) / timings.length\n const variance = timings.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / timings.length\n \n return Math.sqrt(variance)\n}\n\n\n","/**\n * Referrer-based AI Detection\n * \n * Detects when users arrive from known AI platforms\n * based on the document.referrer header.\n * \n * @module @loamly/tracker/detection\n */\n\nimport { AI_PLATFORMS } from '../config'\nimport type { AIDetectionResult } from '../types'\n\n/**\n * Detect AI platform from referrer URL\n * \n * @param referrer - The document.referrer value\n * @returns AI detection result or null if no AI detected\n */\nexport function detectAIFromReferrer(referrer: string): AIDetectionResult | null {\n if (!referrer) {\n return null\n }\n\n try {\n const url = new URL(referrer)\n const hostname = url.hostname.toLowerCase()\n\n // Check against known AI platforms\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (hostname.includes(pattern) || referrer.includes(pattern)) {\n return {\n isAI: true,\n platform,\n confidence: 0.95, // High confidence when referrer matches\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n // Invalid URL, try pattern matching on raw string\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (referrer.toLowerCase().includes(pattern.toLowerCase())) {\n return {\n isAI: true,\n platform,\n confidence: 0.85,\n method: 'referrer',\n }\n }\n }\n return null\n }\n}\n\n/**\n * Extract AI platform from UTM parameters\n * \n * @param url - The current page URL\n * @returns AI platform name or null\n */\nexport function detectAIFromUTM(url: string): AIDetectionResult | null {\n try {\n const params = new URL(url).searchParams\n \n // Check utm_source for AI platforms\n const utmSource = params.get('utm_source')?.toLowerCase()\n if (utmSource) {\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (utmSource.includes(pattern.split('.')[0])) {\n return {\n isAI: true,\n platform,\n confidence: 0.99, // Very high confidence from explicit UTM\n method: 'referrer',\n }\n }\n }\n \n // Generic AI source patterns\n if (utmSource.includes('ai') || utmSource.includes('llm') || utmSource.includes('chatbot')) {\n return {\n isAI: true,\n platform: utmSource,\n confidence: 0.9,\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n return null\n }\n}\n\n\n","/**\n * Lightweight Behavioral ML Classifier\n * \n * LOA-180: Client-side Naive Bayes classifier for AI traffic detection.\n * Research: 75-90% accuracy with 5-8 behavioral signals (Perplexity Dec 2025)\n * \n * @module @loamly/tracker/detection/behavioral-classifier\n */\n\n/**\n * Behavioral signal types for classification\n */\nexport type BehavioralSignal = \n // Time-based signals\n | 'time_to_first_click_immediate' // < 500ms\n | 'time_to_first_click_fast' // 500ms - 2s\n | 'time_to_first_click_normal' // 2s - 10s\n | 'time_to_first_click_delayed' // > 10s\n // Scroll signals\n | 'scroll_speed_none' // No scroll\n | 'scroll_speed_uniform' // Bot-like uniform scrolling\n | 'scroll_speed_variable' // Human-like variable\n | 'scroll_speed_erratic' // Very erratic\n // Navigation signals\n | 'nav_timing_paste'\n | 'nav_timing_click'\n | 'nav_timing_unknown'\n // Context signals\n | 'no_referrer'\n | 'has_referrer'\n | 'deep_landing' // Non-homepage first page\n | 'homepage_landing'\n // Mouse signals\n | 'mouse_movement_none'\n | 'mouse_movement_linear' // Bot-like straight lines\n | 'mouse_movement_curved' // Human-like curves\n // Form signals\n | 'form_fill_instant' // < 100ms per field\n | 'form_fill_fast' // 100-500ms per field\n | 'form_fill_normal' // > 500ms per field\n // Focus signals\n | 'focus_blur_rapid' // Rapid tab switching\n | 'focus_blur_normal'\n\n/**\n * Classification result\n */\nexport interface BehavioralClassificationResult {\n /** Overall classification */\n classification: 'human' | 'ai_influenced' | 'uncertain'\n /** Human probability (0-1) */\n humanProbability: number\n /** AI-influenced probability (0-1) */\n aiProbability: number\n /** Confidence in classification (0-1) */\n confidence: number\n /** Signals detected */\n signals: BehavioralSignal[]\n /** Time when classification was made */\n timestamp: number\n /** Session duration when classified */\n sessionDurationMs: number\n}\n\n/**\n * Behavioral data collector\n */\ninterface BehavioralData {\n firstClickTime: number | null\n scrollEvents: { time: number; position: number }[]\n mouseEvents: { time: number; x: number; y: number }[]\n formEvents: { fieldId: string; startTime: number; endTime: number }[]\n focusBlurEvents: { type: 'focus' | 'blur'; time: number }[]\n startTime: number\n}\n\n/**\n * Pre-trained Naive Bayes weights\n * Research-validated weights from Perplexity Dec 2025 research\n */\nconst NAIVE_BAYES_WEIGHTS = {\n human: {\n time_to_first_click_delayed: 0.85,\n time_to_first_click_normal: 0.75,\n time_to_first_click_fast: 0.50,\n time_to_first_click_immediate: 0.25,\n scroll_speed_variable: 0.80,\n scroll_speed_erratic: 0.70,\n scroll_speed_uniform: 0.35,\n scroll_speed_none: 0.45,\n nav_timing_click: 0.75,\n nav_timing_unknown: 0.55,\n nav_timing_paste: 0.35,\n has_referrer: 0.70,\n no_referrer: 0.45,\n homepage_landing: 0.65,\n deep_landing: 0.50,\n mouse_movement_curved: 0.90,\n mouse_movement_linear: 0.30,\n mouse_movement_none: 0.40,\n form_fill_normal: 0.85,\n form_fill_fast: 0.60,\n form_fill_instant: 0.20,\n focus_blur_normal: 0.75,\n focus_blur_rapid: 0.45,\n },\n ai_influenced: {\n time_to_first_click_immediate: 0.75,\n time_to_first_click_fast: 0.55,\n time_to_first_click_normal: 0.40,\n time_to_first_click_delayed: 0.35,\n scroll_speed_none: 0.55,\n scroll_speed_uniform: 0.70,\n scroll_speed_variable: 0.35,\n scroll_speed_erratic: 0.40,\n nav_timing_paste: 0.75,\n nav_timing_unknown: 0.50,\n nav_timing_click: 0.35,\n no_referrer: 0.65,\n has_referrer: 0.40,\n deep_landing: 0.60,\n homepage_landing: 0.45,\n mouse_movement_none: 0.60,\n mouse_movement_linear: 0.75,\n mouse_movement_curved: 0.25,\n form_fill_instant: 0.80,\n form_fill_fast: 0.55,\n form_fill_normal: 0.30,\n focus_blur_rapid: 0.60,\n focus_blur_normal: 0.40,\n },\n} as const\n\n// Prior probabilities (base rates)\nconst PRIORS = {\n human: 0.85,\n ai_influenced: 0.15,\n}\n\n// Default weight for unknown signals\nconst DEFAULT_WEIGHT = 0.5\n\n/**\n * Behavioral Classifier\n * \n * Lightweight Naive Bayes classifier (~2KB) for client-side AI traffic detection.\n * Collects behavioral signals and classifies after configurable session time.\n */\nexport class BehavioralClassifier {\n private data: BehavioralData\n private classified = false\n private result: BehavioralClassificationResult | null = null\n private minSessionTime: number\n private onClassify: ((result: BehavioralClassificationResult) => void) | null = null\n\n /**\n * Create a new classifier\n * @param minSessionTimeMs Minimum session time before classification (default: 10s)\n */\n constructor(minSessionTimeMs = 10000) {\n this.minSessionTime = minSessionTimeMs\n this.data = {\n firstClickTime: null,\n scrollEvents: [],\n mouseEvents: [],\n formEvents: [],\n focusBlurEvents: [],\n startTime: Date.now(),\n }\n }\n\n /**\n * Set callback for when classification completes\n */\n setOnClassify(callback: (result: BehavioralClassificationResult) => void): void {\n this.onClassify = callback\n }\n\n /**\n * Record a click event\n */\n recordClick(): void {\n if (this.data.firstClickTime === null) {\n this.data.firstClickTime = Date.now()\n }\n this.checkAndClassify()\n }\n\n /**\n * Record a scroll event\n */\n recordScroll(position: number): void {\n this.data.scrollEvents.push({ time: Date.now(), position })\n // Keep only last 50 events to limit memory\n if (this.data.scrollEvents.length > 50) {\n this.data.scrollEvents = this.data.scrollEvents.slice(-50)\n }\n this.checkAndClassify()\n }\n\n /**\n * Record mouse movement\n */\n recordMouse(x: number, y: number): void {\n this.data.mouseEvents.push({ time: Date.now(), x, y })\n // Keep only last 100 events\n if (this.data.mouseEvents.length > 100) {\n this.data.mouseEvents = this.data.mouseEvents.slice(-100)\n }\n this.checkAndClassify()\n }\n\n /**\n * Record form field interaction start\n */\n recordFormStart(fieldId: string): void {\n const existing = this.data.formEvents.find(e => e.fieldId === fieldId && e.endTime === 0)\n if (!existing) {\n this.data.formEvents.push({ fieldId, startTime: Date.now(), endTime: 0 })\n }\n }\n\n /**\n * Record form field interaction end\n */\n recordFormEnd(fieldId: string): void {\n const event = this.data.formEvents.find(e => e.fieldId === fieldId && e.endTime === 0)\n if (event) {\n event.endTime = Date.now()\n }\n this.checkAndClassify()\n }\n\n /**\n * Record focus/blur event\n */\n recordFocusBlur(type: 'focus' | 'blur'): void {\n this.data.focusBlurEvents.push({ type, time: Date.now() })\n // Keep only last 20 events\n if (this.data.focusBlurEvents.length > 20) {\n this.data.focusBlurEvents = this.data.focusBlurEvents.slice(-20)\n }\n }\n\n /**\n * Check if we have enough data and classify\n */\n private checkAndClassify(): void {\n if (this.classified) return\n \n const sessionDuration = Date.now() - this.data.startTime\n if (sessionDuration < this.minSessionTime) return\n \n // Need at least some behavioral data\n const hasData = \n this.data.scrollEvents.length >= 2 ||\n this.data.mouseEvents.length >= 5 ||\n this.data.firstClickTime !== null\n \n if (!hasData) return\n \n this.classify()\n }\n\n /**\n * Force classification (for beforeunload)\n */\n forceClassify(): BehavioralClassificationResult | null {\n if (this.classified) return this.result\n return this.classify()\n }\n\n /**\n * Perform classification\n */\n private classify(): BehavioralClassificationResult {\n const sessionDuration = Date.now() - this.data.startTime\n const signals = this.extractSignals()\n \n // Naive Bayes log-probability calculation\n let humanLogProb = Math.log(PRIORS.human)\n let aiLogProb = Math.log(PRIORS.ai_influenced)\n \n for (const signal of signals) {\n const humanWeight = NAIVE_BAYES_WEIGHTS.human[signal as keyof typeof NAIVE_BAYES_WEIGHTS.human] ?? DEFAULT_WEIGHT\n const aiWeight = NAIVE_BAYES_WEIGHTS.ai_influenced[signal as keyof typeof NAIVE_BAYES_WEIGHTS.ai_influenced] ?? DEFAULT_WEIGHT\n \n humanLogProb += Math.log(humanWeight)\n aiLogProb += Math.log(aiWeight)\n }\n \n // Convert to probabilities using log-sum-exp trick\n const maxLog = Math.max(humanLogProb, aiLogProb)\n const humanExp = Math.exp(humanLogProb - maxLog)\n const aiExp = Math.exp(aiLogProb - maxLog)\n const total = humanExp + aiExp\n \n const humanProbability = humanExp / total\n const aiProbability = aiExp / total\n \n // Determine classification\n let classification: 'human' | 'ai_influenced' | 'uncertain'\n let confidence: number\n \n if (humanProbability > 0.6) {\n classification = 'human'\n confidence = humanProbability\n } else if (aiProbability > 0.6) {\n classification = 'ai_influenced'\n confidence = aiProbability\n } else {\n classification = 'uncertain'\n confidence = Math.max(humanProbability, aiProbability)\n }\n \n this.result = {\n classification,\n humanProbability,\n aiProbability,\n confidence,\n signals,\n timestamp: Date.now(),\n sessionDurationMs: sessionDuration,\n }\n \n this.classified = true\n \n // Call callback if set\n if (this.onClassify) {\n this.onClassify(this.result)\n }\n \n return this.result\n }\n\n /**\n * Extract behavioral signals from collected data\n */\n private extractSignals(): BehavioralSignal[] {\n const signals: BehavioralSignal[] = []\n // Session duration available for future enhancements\n // const sessionDuration = Date.now() - this.data.startTime\n \n // Time to first click\n if (this.data.firstClickTime !== null) {\n const timeToClick = this.data.firstClickTime - this.data.startTime\n if (timeToClick < 500) {\n signals.push('time_to_first_click_immediate')\n } else if (timeToClick < 2000) {\n signals.push('time_to_first_click_fast')\n } else if (timeToClick < 10000) {\n signals.push('time_to_first_click_normal')\n } else {\n signals.push('time_to_first_click_delayed')\n }\n }\n \n // Scroll behavior\n if (this.data.scrollEvents.length === 0) {\n signals.push('scroll_speed_none')\n } else if (this.data.scrollEvents.length >= 3) {\n const scrollDeltas: number[] = []\n for (let i = 1; i < this.data.scrollEvents.length; i++) {\n const delta = this.data.scrollEvents[i].time - this.data.scrollEvents[i - 1].time\n scrollDeltas.push(delta)\n }\n \n // Calculate coefficient of variation for scroll timing\n const mean = scrollDeltas.reduce((a, b) => a + b, 0) / scrollDeltas.length\n const variance = scrollDeltas.reduce((sum, d) => sum + Math.pow(d - mean, 2), 0) / scrollDeltas.length\n const stdDev = Math.sqrt(variance)\n const cv = mean > 0 ? stdDev / mean : 0\n \n if (cv < 0.2) {\n signals.push('scroll_speed_uniform') // Very consistent = bot-like\n } else if (cv < 0.6) {\n signals.push('scroll_speed_variable') // Natural variation\n } else {\n signals.push('scroll_speed_erratic')\n }\n }\n \n // Mouse movement analysis\n if (this.data.mouseEvents.length === 0) {\n signals.push('mouse_movement_none')\n } else if (this.data.mouseEvents.length >= 10) {\n // Calculate linearity using R² of best-fit line\n const n = Math.min(this.data.mouseEvents.length, 20)\n const recentMouse = this.data.mouseEvents.slice(-n)\n \n let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0\n for (const event of recentMouse) {\n sumX += event.x\n sumY += event.y\n sumXY += event.x * event.y\n sumX2 += event.x * event.x\n }\n \n const denominator = (n * sumX2 - sumX * sumX)\n const slope = denominator !== 0 ? (n * sumXY - sumX * sumY) / denominator : 0\n const intercept = (sumY - slope * sumX) / n\n \n // Calculate R²\n let ssRes = 0, ssTot = 0\n const yMean = sumY / n\n for (const event of recentMouse) {\n const yPred = slope * event.x + intercept\n ssRes += Math.pow(event.y - yPred, 2)\n ssTot += Math.pow(event.y - yMean, 2)\n }\n \n const r2 = ssTot !== 0 ? 1 - (ssRes / ssTot) : 0\n \n if (r2 > 0.95) {\n signals.push('mouse_movement_linear') // Too straight = bot-like\n } else {\n signals.push('mouse_movement_curved') // Natural curves\n }\n }\n \n // Form fill timing\n const completedForms = this.data.formEvents.filter(e => e.endTime > 0)\n if (completedForms.length > 0) {\n const avgFillTime = completedForms.reduce((sum, e) => sum + (e.endTime - e.startTime), 0) / completedForms.length\n \n if (avgFillTime < 100) {\n signals.push('form_fill_instant')\n } else if (avgFillTime < 500) {\n signals.push('form_fill_fast')\n } else {\n signals.push('form_fill_normal')\n }\n }\n \n // Focus/blur patterns\n if (this.data.focusBlurEvents.length >= 4) {\n const recentFB = this.data.focusBlurEvents.slice(-10)\n const intervals: number[] = []\n \n for (let i = 1; i < recentFB.length; i++) {\n intervals.push(recentFB[i].time - recentFB[i - 1].time)\n }\n \n const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length\n \n if (avgInterval < 1000) {\n signals.push('focus_blur_rapid')\n } else {\n signals.push('focus_blur_normal')\n }\n }\n \n // Context signals (will be set from outside)\n // These are typically added by the tracker itself\n \n return signals\n }\n\n /**\n * Add context signals (set by tracker from external data)\n */\n addContextSignal(_signal: BehavioralSignal): void {\n // These will be picked up on next classification\n // For now, we'll handle them in the classify method\n // TODO: Store signals for next classification run\n }\n\n /**\n * Get current result (null if not yet classified)\n */\n getResult(): BehavioralClassificationResult | null {\n return this.result\n }\n\n /**\n * Check if classification has been performed\n */\n hasClassified(): boolean {\n return this.classified\n }\n}\n\n/**\n * Create a classifier instance with standard configuration\n */\nexport function createBehavioralClassifier(minSessionTimeMs = 10000): BehavioralClassifier {\n return new BehavioralClassifier(minSessionTimeMs)\n}\n\n","/**\n * Utility functions for Loamly Tracker\n * @module @loamly/tracker\n */\n\n/**\n * Generate a UUID v4\n */\nexport function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID()\n }\n \n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Get or create a persistent visitor ID\n * (Privacy-respecting, no cookies)\n */\nexport function getVisitorId(): string {\n // Try to get from localStorage first\n try {\n const stored = localStorage.getItem('_loamly_vid')\n if (stored) return stored\n \n const newId = generateUUID()\n localStorage.setItem('_loamly_vid', newId)\n return newId\n } catch {\n // localStorage not available, generate ephemeral ID\n return generateUUID()\n }\n}\n\n/**\n * Get or create a session ID using sessionStorage\n * (Cookie-free session tracking)\n */\nexport function getSessionId(): { sessionId: string; isNew: boolean } {\n try {\n const storedSession = sessionStorage.getItem('loamly_session')\n const storedStart = sessionStorage.getItem('loamly_start')\n \n if (storedSession && storedStart) {\n return { sessionId: storedSession, isNew: false }\n }\n \n const newSession = generateUUID()\n const startTime = Date.now().toString()\n \n sessionStorage.setItem('loamly_session', newSession)\n sessionStorage.setItem('loamly_start', startTime)\n \n return { sessionId: newSession, isNew: true }\n } catch {\n // sessionStorage not available\n return { sessionId: generateUUID(), isNew: true }\n }\n}\n\n/**\n * Extract UTM parameters from URL\n */\nexport function extractUTMParams(url: string): Record<string, string> {\n const params: Record<string, string> = {}\n \n try {\n const searchParams = new URL(url).searchParams\n const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']\n \n for (const key of utmKeys) {\n const value = searchParams.get(key)\n if (value) params[key] = value\n }\n } catch {\n // Invalid URL\n }\n \n return params\n}\n\n/**\n * Truncate text to max length\n */\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.substring(0, maxLength - 3) + '...'\n}\n\n/**\n * Safe fetch with timeout\n */\nexport async function safeFetch(\n url: string,\n options: RequestInit,\n timeout = 10000\n): Promise<Response | null> {\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n \n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n })\n \n clearTimeout(timeoutId)\n return response\n } catch {\n return null\n }\n}\n\n/**\n * Send beacon (for unload events)\n */\nexport function sendBeacon(url: string, data: unknown): boolean {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n return navigator.sendBeacon(url, JSON.stringify(data))\n }\n return false\n}\n\n\n","/**\n * Loamly Tracker Core\n * \n * Cookie-free, privacy-first analytics with AI traffic detection.\n * \n * @module @loamly/tracker\n */\n\nimport { VERSION, DEFAULT_CONFIG } from './config'\nimport { detectNavigationType } from './detection/navigation-timing'\nimport { detectAIFromReferrer, detectAIFromUTM } from './detection/referrer'\nimport { \n BehavioralClassifier, \n type BehavioralClassificationResult \n} from './detection/behavioral-classifier'\nimport { \n getVisitorId, \n getSessionId, \n extractUTMParams, \n truncateText,\n safeFetch,\n sendBeacon \n} from './utils'\nimport type { \n LoamlyConfig, \n LoamlyTracker, \n TrackEventOptions, \n NavigationTiming,\n AIDetectionResult,\n BehavioralMLResult\n} from './types'\n\n// State\nlet config: LoamlyConfig & { apiHost: string } = { apiHost: DEFAULT_CONFIG.apiHost }\nlet initialized = false\nlet debugMode = false\nlet visitorId: string | null = null\nlet sessionId: string | null = null\nlet sessionStartTime: number | null = null\nlet navigationTiming: NavigationTiming | null = null\nlet aiDetection: AIDetectionResult | null = null\nlet behavioralClassifier: BehavioralClassifier | null = null\nlet behavioralMLResult: BehavioralMLResult | null = null\n\n/**\n * Debug logger\n */\nfunction log(...args: unknown[]): void {\n if (debugMode) {\n console.log('[Loamly]', ...args)\n }\n}\n\n/**\n * Build API endpoint URL\n */\nfunction endpoint(path: string): string {\n return `${config.apiHost}${path}`\n}\n\n/**\n * Initialize the tracker\n */\nfunction init(userConfig: LoamlyConfig = {}): void {\n if (initialized) {\n log('Already initialized')\n return\n }\n\n config = {\n ...config,\n ...userConfig,\n apiHost: userConfig.apiHost || DEFAULT_CONFIG.apiHost,\n }\n \n debugMode = userConfig.debug ?? false\n \n log('Initializing Loamly Tracker v' + VERSION)\n \n // Get/create visitor ID\n visitorId = getVisitorId()\n log('Visitor ID:', visitorId)\n \n // Get/create session\n const session = getSessionId()\n sessionId = session.sessionId\n sessionStartTime = Date.now()\n log('Session ID:', sessionId, session.isNew ? '(new)' : '(existing)')\n \n // Detect navigation timing (paste vs click)\n navigationTiming = detectNavigationType()\n log('Navigation timing:', navigationTiming)\n \n // Detect AI from referrer\n aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href)\n if (aiDetection) {\n log('AI detected:', aiDetection)\n }\n \n initialized = true\n \n // Auto pageview unless disabled\n if (!userConfig.disableAutoPageview) {\n pageview()\n }\n \n // Set up behavioral tracking unless disabled\n if (!userConfig.disableBehavioral) {\n setupBehavioralTracking()\n }\n \n // Initialize behavioral ML classifier (LOA-180)\n behavioralClassifier = new BehavioralClassifier(10000) // 10s min session\n behavioralClassifier.setOnClassify(handleBehavioralClassification)\n setupBehavioralMLTracking()\n \n log('Initialization complete')\n}\n\n/**\n * Track a page view\n */\nfunction pageview(customUrl?: string): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const url = customUrl || window.location.href\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n url,\n referrer: document.referrer || null,\n title: document.title || null,\n utm_source: extractUTMParams(url).utm_source || null,\n utm_medium: extractUTMParams(url).utm_medium || null,\n utm_campaign: extractUTMParams(url).utm_campaign || null,\n user_agent: navigator.userAgent,\n screen_width: window.screen?.width,\n screen_height: window.screen?.height,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n tracker_version: VERSION,\n navigation_timing: navigationTiming,\n ai_platform: aiDetection?.platform || null,\n is_ai_referrer: aiDetection?.isAI || false,\n }\n\n log('Pageview:', payload)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.visit), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a custom event\n */\nfunction track(eventName: string, options: TrackEventOptions = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_name: eventName,\n event_type: 'custom',\n properties: options.properties || {},\n revenue: options.revenue,\n currency: options.currency || 'USD',\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Event:', eventName, payload)\n\n safeFetch(endpoint('/api/ingest/event'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a conversion/revenue event\n */\nfunction conversion(eventName: string, revenue: number, currency = 'USD'): void {\n track(eventName, { revenue, currency, properties: { type: 'conversion' } })\n}\n\n/**\n * Identify a user\n */\nfunction identify(userId: string, traits: Record<string, unknown> = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n log('Identify:', userId, traits)\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n user_id: userId,\n traits,\n timestamp: new Date().toISOString(),\n }\n\n safeFetch(endpoint('/api/ingest/identify'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Set up behavioral tracking (scroll, time spent, etc.)\n */\nfunction setupBehavioralTracking(): void {\n let maxScrollDepth = 0\n let lastScrollUpdate = 0\n let lastTimeUpdate = Date.now()\n\n // Scroll tracking with requestAnimationFrame throttling\n let scrollTicking = false\n \n window.addEventListener('scroll', () => {\n if (!scrollTicking) {\n requestAnimationFrame(() => {\n const scrollPercent = Math.round(\n ((window.scrollY + window.innerHeight) / document.documentElement.scrollHeight) * 100\n )\n \n if (scrollPercent > maxScrollDepth) {\n maxScrollDepth = scrollPercent\n \n // Report at milestones (25%, 50%, 75%, 100%)\n const milestones = [25, 50, 75, 100]\n for (const milestone of milestones) {\n if (scrollPercent >= milestone && lastScrollUpdate < milestone) {\n lastScrollUpdate = milestone\n sendBehavioralEvent('scroll_depth', { depth: milestone })\n }\n }\n }\n \n scrollTicking = false\n })\n scrollTicking = true\n }\n })\n\n // Time spent tracking (every 5 seconds minimum)\n const trackTimeSpent = (): void => {\n const now = Date.now()\n const delta = now - lastTimeUpdate\n \n if (delta >= DEFAULT_CONFIG.timeSpentThresholdMs) {\n lastTimeUpdate = now\n sendBehavioralEvent('time_spent', { \n seconds: Math.round(delta / 1000),\n total_seconds: Math.round((now - (sessionStartTime || now)) / 1000)\n })\n }\n }\n\n // Track on visibility change\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n trackTimeSpent()\n }\n })\n\n // Track on page unload\n window.addEventListener('beforeunload', () => {\n trackTimeSpent()\n \n // Send final scroll depth\n if (maxScrollDepth > 0) {\n sendBeacon(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: 'scroll_depth_final',\n data: { depth: maxScrollDepth },\n url: window.location.href,\n })\n }\n })\n\n // Form interaction tracking\n document.addEventListener('focusin', (e) => {\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n sendBehavioralEvent('form_focus', {\n field_type: target.tagName.toLowerCase(),\n field_name: (target as HTMLInputElement).name || (target as HTMLInputElement).id || 'unknown',\n })\n }\n })\n\n // Form submit tracking\n document.addEventListener('submit', (e) => {\n const form = e.target as HTMLFormElement\n sendBehavioralEvent('form_submit', {\n form_id: form.id || form.name || 'unknown',\n form_action: form.action ? new URL(form.action).pathname : 'unknown',\n })\n })\n\n // Click tracking for links\n document.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n const link = target.closest('a')\n \n if (link && link.href) {\n const isExternal = link.hostname !== window.location.hostname\n sendBehavioralEvent('click', {\n element: 'link',\n href: truncateText(link.href, 200),\n text: truncateText(link.textContent || '', 100),\n is_external: isExternal,\n })\n }\n })\n}\n\n/**\n * Send a behavioral event\n */\nfunction sendBehavioralEvent(eventType: string, data: Record<string, unknown>): void {\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: eventType,\n data,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Behavioral:', eventType, data)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Set up behavioral ML signal collection (LOA-180)\n * Collects mouse, scroll, and interaction signals for Naive Bayes classification\n */\nfunction setupBehavioralMLTracking(): void {\n if (!behavioralClassifier) return\n \n // Mouse movement tracking (sampled for performance)\n let mouseSampleCount = 0\n document.addEventListener('mousemove', (e) => {\n mouseSampleCount++\n // Sample every 10th event for performance\n if (mouseSampleCount % 10 === 0 && behavioralClassifier) {\n behavioralClassifier.recordMouse(e.clientX, e.clientY)\n }\n }, { passive: true })\n \n // Click tracking\n document.addEventListener('click', () => {\n if (behavioralClassifier) {\n behavioralClassifier.recordClick()\n }\n }, { passive: true })\n \n // Scroll tracking for ML (separate from milestone-based)\n let lastScrollY = 0\n document.addEventListener('scroll', () => {\n const currentY = window.scrollY\n if (Math.abs(currentY - lastScrollY) > 50 && behavioralClassifier) {\n lastScrollY = currentY\n behavioralClassifier.recordScroll(currentY)\n }\n }, { passive: true })\n \n // Focus/blur tracking\n document.addEventListener('focusin', (e) => {\n if (behavioralClassifier) {\n behavioralClassifier.recordFocusBlur('focus')\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {\n behavioralClassifier.recordFormStart(target.id || target.getAttribute('name') || 'unknown')\n }\n }\n }, { passive: true })\n \n document.addEventListener('focusout', (e) => {\n if (behavioralClassifier) {\n behavioralClassifier.recordFocusBlur('blur')\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {\n behavioralClassifier.recordFormEnd(target.id || target.getAttribute('name') || 'unknown')\n }\n }\n }, { passive: true })\n \n // Force classification on page unload\n window.addEventListener('beforeunload', () => {\n if (behavioralClassifier && !behavioralClassifier.hasClassified()) {\n const result = behavioralClassifier.forceClassify()\n if (result) {\n handleBehavioralClassification(result)\n }\n }\n })\n \n // Also try to classify after 30 seconds as backup\n setTimeout(() => {\n if (behavioralClassifier && !behavioralClassifier.hasClassified()) {\n behavioralClassifier.forceClassify()\n }\n }, 30000)\n}\n\n/**\n * Handle behavioral ML classification result\n */\nfunction handleBehavioralClassification(result: BehavioralClassificationResult): void {\n log('Behavioral ML classification:', result)\n \n // Store result\n behavioralMLResult = {\n classification: result.classification,\n humanProbability: result.humanProbability,\n aiProbability: result.aiProbability,\n confidence: result.confidence,\n signals: result.signals,\n sessionDurationMs: result.sessionDurationMs,\n }\n \n // Send to backend\n sendBehavioralEvent('ml_classification', {\n classification: result.classification,\n human_probability: result.humanProbability,\n ai_probability: result.aiProbability,\n confidence: result.confidence,\n signals: result.signals,\n session_duration_ms: result.sessionDurationMs,\n navigation_timing: navigationTiming,\n ai_detection: aiDetection,\n })\n \n // If AI-influenced detected with high confidence, update AI detection\n if (result.classification === 'ai_influenced' && result.confidence >= 0.7) {\n aiDetection = {\n isAI: true,\n confidence: result.confidence,\n method: 'behavioral',\n }\n log('AI detection updated from behavioral ML:', aiDetection)\n }\n}\n\n/**\n * Get current session ID\n */\nfunction getCurrentSessionId(): string | null {\n return sessionId\n}\n\n/**\n * Get current visitor ID\n */\nfunction getCurrentVisitorId(): string | null {\n return visitorId\n}\n\n/**\n * Get AI detection result\n */\nfunction getAIDetectionResult(): AIDetectionResult | null {\n return aiDetection\n}\n\n/**\n * Get navigation timing result\n */\nfunction getNavigationTimingResult(): NavigationTiming | null {\n return navigationTiming\n}\n\n/**\n * Get behavioral ML classification result\n */\nfunction getBehavioralMLResult(): BehavioralMLResult | null {\n return behavioralMLResult\n}\n\n/**\n * Check if initialized\n */\nfunction isTrackerInitialized(): boolean {\n return initialized\n}\n\n/**\n * Reset the tracker\n */\nfunction reset(): void {\n log('Resetting tracker')\n initialized = false\n visitorId = null\n sessionId = null\n sessionStartTime = null\n navigationTiming = null\n aiDetection = null\n behavioralClassifier = null\n behavioralMLResult = null\n \n try {\n sessionStorage.removeItem('loamly_session')\n sessionStorage.removeItem('loamly_start')\n } catch {\n // Ignore\n }\n}\n\n/**\n * Enable/disable debug mode\n */\nfunction setDebug(enabled: boolean): void {\n debugMode = enabled\n log('Debug mode:', enabled ? 'enabled' : 'disabled')\n}\n\n/**\n * The Loamly Tracker instance\n */\nexport const loamly: LoamlyTracker = {\n init,\n pageview,\n track,\n conversion,\n identify,\n getSessionId: getCurrentSessionId,\n getVisitorId: getCurrentVisitorId,\n getAIDetection: getAIDetectionResult,\n getNavigationTiming: getNavigationTimingResult,\n getBehavioralML: getBehavioralMLResult,\n isInitialized: isTrackerInitialized,\n reset,\n debug: setDebug,\n}\n\nexport default loamly\n\n\n"],"mappings":"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","names":["browser_exports","__export","browser_default","loamly","VERSION","DEFAULT_CONFIG","AI_PLATFORMS","detectNavigationType","entries","nav","signals","pasteScore","fetchStartDelta","calculateTimingVariance","confidence","timings","t","mean","a","b","variance","sum","detectAIFromReferrer","referrer","hostname","pattern","platform","AI_PLATFORMS","detectAIFromUTM","url","utmSource","NAIVE_BAYES_WEIGHTS","PRIORS","DEFAULT_WEIGHT","BehavioralClassifier","minSessionTimeMs","callback","position","x","y","fieldId","e","event","type","sessionDuration","signals","humanLogProb","aiLogProb","signal","humanWeight","aiWeight","maxLog","humanExp","aiExp","total","humanProbability","aiProbability","classification","confidence","timeToClick","scrollDeltas","i","delta","mean","a","b","variance","sum","d","stdDev","cv","recentMouse","sumX","sumY","sumXY","sumX2","denominator","slope","intercept","ssRes","ssTot","yMean","yPred","completedForms","avgFillTime","recentFB","intervals","_signal","generateUUID","c","r","getVisitorId","stored","newId","getSessionId","storedSession","storedStart","newSession","startTime","extractUTMParams","url","params","searchParams","utmKeys","key","value","truncateText","text","maxLength","safeFetch","options","timeout","controller","timeoutId","response","sendBeacon","data","config","DEFAULT_CONFIG","initialized","debugMode","visitorId","sessionId","sessionStartTime","navigationTiming","aiDetection","behavioralClassifier","behavioralMLResult","log","args","endpoint","path","init","userConfig","VERSION","getVisitorId","session","getSessionId","detectNavigationType","detectAIFromReferrer","detectAIFromUTM","pageview","setupBehavioralTracking","BehavioralClassifier","handleBehavioralClassification","setupBehavioralMLTracking","customUrl","url","payload","extractUTMParams","safeFetch","track","eventName","options","conversion","revenue","currency","identify","userId","traits","maxScrollDepth","lastScrollUpdate","lastTimeUpdate","scrollTicking","scrollPercent","milestones","milestone","sendBehavioralEvent","trackTimeSpent","now","delta","sendBeacon","e","target","form","link","isExternal","truncateText","eventType","data","mouseSampleCount","lastScrollY","currentY","result","getCurrentSessionId","getCurrentVisitorId","getAIDetectionResult","getNavigationTimingResult","getBehavioralMLResult","isTrackerInitialized","reset","setDebug","enabled","loamly","autoInit","scripts","scriptTag","script","config","loamly","browser_default","loamly"]}
|
package/package.json
CHANGED