1(() => { 2 "use strict"; 3 4 const COOKIE_NAME = "sogni_attribution_v1"; 5 const CHANGE_EVENT = "sogni-attribution-change"; 6 const HANDOFF_EVENT = "sogni_handoff"; 7 const MAX_AGE_SECONDS = 60 * 60 * 24 * 90; 8 const VERSION = 1; 9 const AGENT_DISCOVERY_VERSION = "2026-09-10.1"; 10 let agentEntryRecorded = false; 11 12 function agentPage() { 13 const path = window.location.pathname.replace(/\/$/, "") || "/"; 14 if (["www.sogni.ai", "sogni.ai"].includes(window.location.hostname)) { 15 return ({ "/agent": "agent_landing", "/quickstart": "quickstart" })[path]; 16 } 17 if (window.location.hostname !== "docs.sogni.ai") return undefined; 18 return ({ 19 "/agents": "agent_hub", 20 "/agents/first-result": "first_result", 21 "/agents/authentication": "authentication", 22 "/agents/pricing": "pricing", 23 "/agents/jobs": "jobs", 24 })[path]; 25 } 26 27 function trackAgentEntry() { 28 const page = agentPage(); 29 if (!page || agentEntryRecorded || !analyticsAllowed() || typeof window.gtag !== "function") return; 30 try { 31 window.gtag("event", "agent_entry_viewed", { 32 schema_version: 1, 33 event_id: randomId(), 34 occurred_at: new Date().toISOString(), 35 environment: "production", 36 producer: "public_web", 37 producer_version: AGENT_DISCOVERY_VERSION, 38 agent_page: page, 39 exposure_version: AGENT_DISCOVERY_VERSION, 40 outcome: "viewed", 41 // This is a consented browser view, not authenticated runtime identity. 42 attribution_confidence: "unknown", 43 page_location: window.location.origin + window.location.pathname, 44 page_referrer: safeReferrer() || "", 45 transport_type: "beacon", 46 }); 47 agentEntryRecorded = true; 48 } catch { /* Optional analytics must never interfere with the page. */ } 49 } 50 const OPTIONAL_TOUCH_FIELDS = [ 51 "campaign", 52 "content", 53 "term", 54 "gclid", 55 "gbraid", 56 "wbraid", 57 "referrer", 58 ]; 59 60 function isSogniDomain(hostname) { 61 return hostname === "sogni.ai" || hostname.endsWith(".sogni.ai"); 62 } 63 64 function limit(value, length) { 65 if (typeof value !== "string") return undefined; 66 const trimmed = value.trim(); 67 return trimmed ? trimmed.slice(0, length) : undefined; 68 } 69 70 function internalSource(value) { 71 const source = limit(value, 100); 72 return source && /^[a-z0-9][a-z0-9._-]*$/i.test(source) ? source : undefined; 73 } 74 75 function isTouch(value) { 76 if (!value || typeof value !== "object") return false; 77 if ( 78 typeof value.source !== "string" || 79 !value.source.trim() || 80 typeof value.medium !== "string" || 81 !value.medium.trim() || 82 typeof value.landingPath !== "string" || 83 !value.landingPath.trim() || 84 typeof value.at !== "string" || 85 !Number.isFinite(Date.parse(value.at)) 86 ) { 87 return false; 88 } 89 return OPTIONAL_TOUCH_FIELDS.every((field) => { 90 const fieldValue = value[field]; 91 return ( 92 fieldValue === undefined || (typeof fieldValue === "string" && Boolean(fieldValue.trim())) 93 ); 94 }); 95 } 96 97 function readCookie() { 98 const prefix = `${COOKIE_NAME}=`; 99 const part = document.cookie.split(";").find((item) => item.trim().startsWith(prefix)); 100 if (!part) return null; 101 102 try { 103 const parsed = JSON.parse(decodeURIComponent(part.trim().slice(prefix.length))); 104 if ( 105 !parsed || 106 parsed.version !== VERSION || 107 typeof parsed.attributionId !== "string" || 108 !parsed.attributionId.trim() || 109 parsed.attributionId.length > 128 || 110 !isTouch(parsed.firstTouch) || 111 !isTouch(parsed.lastTouch) 112 ) { 113 return null; 114 } 115 return parsed; 116 } catch { 117 return null; 118 } 119 } 120 121 function cookieAttributes(maxAge) { 122 const attributes = ["Path=/", `Max-Age=${maxAge}`, "SameSite=Lax"]; 123 if (isSogniDomain(window.location.hostname)) attributes.push("Domain=.sogni.ai"); 124 if (window.location.protocol === "https:") attributes.push("Secure"); 125 return attributes; 126 } 127 128 function writeCookie(value) { 129 const encoded = encodeURIComponent(JSON.stringify(value)); 130 // Leave room for attributes and browser-dependent overhead. Normal visits 131 // are far smaller; reject oversized URL parameters instead of writing a 132 // silently truncated cross-domain cookie. 133 if (encoded.length > 3800) return; 134 document.cookie = [`${COOKIE_NAME}=${encoded}
134`, ...cookieAttributes(MAX_AGE_SECONDS)].join( 135 "; ", 136 ); 137 } 138 139 function clearCookie() { 140 document.cookie = [`${COOKIE_NAME}=`, ...cookieAttributes(0)].join("; "); 141 } 142 143 function analyticsAllowed() { 144 const consent = window.SogniConsent?.read?.(); 145 // Attribution is an optional analytics cookie. Do not create it until the 146 // visitor explicitly grants analytics storage. 147 return consent?.analytics_storage === "granted"; 148 } 149 150 function randomId() { 151 if (typeof window.crypto?.randomUUID === "function") return window.crypto.randomUUID(); 152 return `sat-${Date.now()}-${Math.random().toString(36).slice(2, 14)}`; 153 } 154 155 function safeReferrer() { 156 if (!document.referrer) return undefined; 157 try { 158 const url = new URL(document.referrer); 159 return limit(`${url.origin}${url.pathname}`, 512); 160 } catch { 161 return undefined; 162 } 163 } 164 165 function sourceFromReferrer(referrer) { 166 if (!referrer) return { source: "(direct)", medium: "(none)", external: false }; 167 168 try { 169 const hostname = new URL(referrer).hostname.toLowerCase(); 170 if (isSogniDomain(hostname)) return { source: "sogni", medium: "internal", external: false }; 171 if (/^(?:www\.)?google\.[a-z.]+$/.test(hostname)) { 172 return { source: "google", medium: "organic", external: true }; 173 } 174 if (/^(?:www\.)?bing\.com$/.test(hostname)) { 175 return { source: "bing", medium: "organic", external: true }; 176 } 177 return { source: hostname, medium: "referral", external: true }; 178 } catch { 179 return { source: "(direct)", medium: "(none)", external: false }; 180 } 181 } 182 183 function trackHandoffClick(event) { 184 if (!analyticsAllowed()) return; 185 const target = event.target; 186 if (!(target instanceof Element)) return; 187 188 const anchor = target.closest("a[href]"); 189 if (!(anchor instanceof HTMLAnchorElement)) return; 190 191 let destination; 192 try { 193 destination = new URL(anchor.href, window.location.href); 194 } catch { 195 return; 196 } 197 198 const source = internalSource(destination.searchParams.get("source")); 199 if ( 200 !source || 201 !isSogniDomain(destination.hostname.toLowerCase()) || 202 destination.hostname === window.location.hostname 203 ) { 204 return; 205 } 206 207 // Keep the real acquisition source intact. This event records the internal 208 // www -> app/chat handoff without introducing an internal UTM campaign. 209 try { window.gtag?.("event", HANDOFF_EVENT, { 210 handoff_stage: "click", 211 internal_source: source, 212 origin_path: limit(window.location.pathname, 100) || "/", 213 destination_path: limit(destination.pathname, 100) || "/", 214 destination_host: limit(destination.hostname, 100), 215 ...(agentPage() ? { agent_page: agentPage(), exposure_version: AGENT_DISCOVERY_VERSION } : {}), 216 page_location: window.location.origin + window.location.pathname, 217 page_referrer: safeReferrer() || "", 218 transport_type: "beacon", 219 }); } catch { /* A telemetry failure must not block navigation. */ } 220 } 221 222 function currentTouch() { 223 const url = new URL(window.location.href); 224 const referrer = safeReferrer(); 225 const derived = sourceFromReferrer(referrer); 226 const utmSource = limit(url.searchParams.get("utm_source"), 128); 227 const utmMedium = limit(url.searchParams.get("utm_medium"), 128); 228 const campaign = limit(url.searchParams.get("utm_campaign"), 256); 229 const content = limit(url.searchParams.get("utm_content"), 256); 230 const term = limit(url.searchParams.get("utm_term"), 256); 231 const gclid = limit(url.searchParams.get("gclid"), 256); 232 const gbraid = limit(url.searchParams.get("gbraid"), 256); 233 const wbraid = limit(url.searchParams.get("wbraid"), 256); 234 const hasGoogleClickId = Boolean(gclid || gbraid || wbraid); 235 const internalCampaign = derived.medium === "internal" && utmSource === "website"; 236 const source = internalCampaign 237 ? derived.source 238 : utmSource || (hasGoogleClickId ? "google" : derived.source); 239 const medium = internalCampaign 240 ? derived.medium 241 : utmMedium || (hasGoogleClickId ? "cpc" : derived.medium); 242 243 const touch = { 244 source, 245 medium, 246 landingPath: limit(url.pathname, 512) || "/", 247 at: new Date().toISOString(), 248 }; 249 250 const optional = { 251 campaign, 252 content, 253 term, 254 gclid, 255 gbraid, 256 wbraid, 257 referrer, 258 }; 259 for (const [key, value] of Object.entries(optional)) { 260 if (value !== undefined) touch[key] = value; 261 } 262 263 return { 264 touch, 265 meaningful: 266 !internalCampaign && 267 Boolean( 268 utmSource || 269 utmMedium || 270 campaign || 271 content || 272 term || 273 hasGoogleClickId || 274 derived.external, 275 ), 276 }; 277 } 278 279 function capture() { 280 if (!analyticsAllowed()) { 281 clearCookie(); 282 return null; 283 } 284 285 const existing = readCookie(); 286 const { touch, meaningful } = currentTouch(); 287 const next = existing 288 ? { 289 ...existing, 290 lastTouch: meaningful ? touch : existing.lastTouch, 291 } 292 : { 293 version: VERSION, 294 attributionId: randomId(), 295 firstTouch: touch, 296 lastTouch: touch, 297 }; 298 299 writeCookie(next); 300 window.dispatchEvent(new CustomEvent(CHANGE_EVENT, { detail: next })); 301 trackAgentEntry(); 302 return next; 303 } 304 305 window.SogniAttribution = Object.freeze({ 306 cookieName: COOKIE_NAME, 307 changeEvent: CHANGE_EVENT, 308 read: readCookie, 309 capture, 310 }); 311 312 capture(); 313 window.addEventListener(window.SogniConsent?.changeEvent || "sogni-consent-change", capture); 314 document.addEventListener("click", trackHandoffClick, true); 315})();
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.