1(() => { 2 'use strict'; 3 4 const runtimeKey = '__clwmcpAnalyticsRuntime'; 5 if (window[runtimeKey]?.send) return; 6 7 const queue = []; 8 const maxQueueSize = 100; 9 const pollIntervalMs = 250; 10 const maxPollAttempts = 120; 11 const allowedEvents = new Set([ 12 'ai_search_view', 13 'ai_search_expand', 14 'ai_search_collapse', 15 'ai_search_submit', 16 'ai_answer_success', 17 'ai_answer_no_result', 18 'ai_source_click', 19 'ai_followup_submit', 20 'ai_labocafe_product_click', 21 'ai_search_error', 22 'webmcp_tool_call', 23 'ai_placement', 24 ]); 25 const allowedParams = new Set([ 26 'ai_search_context', 27 'ai_search_mode', 28 'ai_answer_intent', 29 'ai_response_mode', 30 'ai_answer_source_count', 31 'ai_source_position', 32 'ai_search_error_type', 33 'webmcp_tool_name', 34 'webmcp_status', 35 'webmcp_result_count', 36 'content_type', 37 'content_id', 38 'experiment_id', 39 ]); 40 let pollTimer = 0; 41 let pollAttempts = 0; 42 43 const normalizeParams = (params) => { 44 const normalized = {}; 45 Object.entries(params || {}).forEach(([key, value]) => { 46 if (!allowedParams.has(key)) return; 47 if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { 48 normalized[key] = value; 49 } 50 }); 51 return normalized; 52 }; 53 54 const stopPolling = () => { 55 if (pollTimer) window.clearInterval(pollTimer); 56 pollTimer = 0; 57 pollAttempts = 0; 58 }; 59 60 const sendNow = (entry) => { 61 if (typeof window.gtag !== 'function') return false; 62 try { 63 window.gtag('event', entry.name, entry.params); 64 return true; 65 } catch (_) { 66 return false; 67 } 68 }; 69 70 const flush = () => { 71 if (typeof window.gtag !== 'function') return false; 72 73 while (queue.length) { 74 const entry = queue[0]; 75 if (!sendNow(entry)) return false; 76 queue.shift(); 77 } 78 79 stopPolling(); 80 return true; 81 }; 82 83 const scheduleFlush = () => { 84 if (pollTimer) return; 85 pollAttempts = 0; 86 pollTimer = window.setInterval(() => { 87 pollAttempts += 1; 88 if (flush() || pollAttempts >= maxPollAttempts) stopPolling(); 89 }, pollIntervalMs); 90 }; 91 92 const send = (name, params = {}) => { 93 const eventName = String(name || '').trim(); 94 if (!allowedEvents.has(eventName)) return false; 95 96 const entry = { 97 name: eventName, 98 params: normalizeParams(params), 99 }; 100 101 if (flush() && sendNow(entry)) return true; 102 103 if (queue.length >= maxQueueSize) queue.shift(); 104 queue.push(entry); 105 scheduleFlush(); 106 return false; 107 }; 108 109 window.addEventListener('load', flush); 110 window.addEventListener('pageshow', flush); 111 document.addEventListener('visibilitychange', () => { 112 if (document.visibilityState === 'visible') flush(); 113 }); 114 115 window[runtimeKey] = Object.freeze({ 116 send, 117 flush, 118 pendingCount: () => queue.length, 119 }); 120})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.