PageSourceSearch

https://xagent.id/templates/aeo-site-sdk.js

js xagent.id collected 2026-10-05 06:32:45 UTC 6,407 bytes, 180 lines download raw bytes

1(function () {
2  // Kept equal to packages/agent-id/package.json by
3  // tests/test_sdk_v02_package.py. This sensor is served as a plain
4  // browser script and shipped in the package byte-for-byte, so it can
5  // neither read the package at runtime nor go through a build step:
6  // the version is written once here and CI refuses a mismatch.
7  var SDK_VERSION = "0.3.49";
8  var w = window;
9  var d = document;
10  var s = d.currentScript || {};
11  var cfg = w.twin3AEO || {};
12  var endpoint = cfg.endpoint || s.getAttribute && s.getAttribute("data-endpoint") || "/api/agent/sdk_event";
13  var agentId = cfg.agentId || s.getAttribute && s.getAttribute("data-agent-id") || "";
14  var siteToken = cfg.siteToken || s.getAttribute && s.getAttribute("data-site-token") || "";
15  var eventName = cfg.event || "page_view";
16  var aiRefHosts = /(chatgpt|openai|perplexity|gemini|bard|claude|anthropic|copilot|bing|you\.com|poe\.com)/i;
17  var clsScore = 0;
18  var clsEntries = 0;
19
20  function clip(value, n) {
21    value = value == null ? "" : String(value);
22    return value.length > n ? value.slice(0, n) : value;
23  }
24
25  function attr(el, name) {
26    return el && el.getAttribute ? (el.getAttribute(name) || "") : "";
27  }
28
29  function text(el) {
30    return clip((el && el.textContent || "").replace(/\s+/g, " ").trim(), 160);
31  }
32
33  function meta(name) {
34    var el = d.querySelector('meta[name="' + name + '"],meta[property="' + name + '"]');
35    return attr(el, "content");
36  }
37
38  function hasName(el) {
39    if (!el) return false;
40    return !!(attr(el, "aria-label") || attr(el, "title") || attr(el, "alt") || attr(el, "placeholder") || attr(el, "name") || text(el));
41  }
42
43  function cleanData(obj, depth) {
44    if (!obj || typeof obj !== "object" || depth > 2) return obj;
45    var out = Array.isArray(obj) ? [] : {};
46    Object.keys(obj).slice(0, 40).forEach(function (key) {
47      var low = key.toLowerCase();
48      if (/email|phone|tel|name|note|message|token|key|secret|password/.test(low)) {
49        out[key] = "[redacted]";
50        return;
51      }
52      var v = obj[key];
53      if (typeof v === "string") out[key] = clip(v, 160);
54      else if (typeof v === "number" || typeof v === "boolean") out[key] = v;
55      else if (v && typeof v === "object") out[key] = cleanData(v, depth + 1);
56    });
57    return out;
58  }
59
60  function collectSignals() {
61    var forms = Array.prototype.slice.call(d.querySelectorAll("form"));
62    var webmcpForms = forms.filter(function (f) { return attr(f, "toolname") && attr(f, "tooldescription"); });
63    var badToolInputs = 0;
64    webmcpForms.forEach(function (f) {
65      Array.prototype.slice.call(f.querySelectorAll("input,select,textarea")).forEach(function (el) {
66        if ((attr(el, "type") || "").toLowerCase() === "hidden") return;
67        if (!attr(el, "name")) badToolInputs += 1;
68      });
69    });
70    var interactive = Array.prototype.slice.call(d.querySelectorAll("a,button,input,select,textarea"));
71    var namedInteractive = interactive.filter(function (el) {
72      if (el.tagName === "INPUT" && (attr(el, "type") || "").toLowerCase() === "hidden") return true;
73      return hasName(el);
74    });
75    var imgs = Array.prototype.slice.call(d.querySelectorAll("img"));
76    var missingImgSize = imgs.filter(function (img) { return !(attr(img, "width") && attr(img, "height")); });
77    var ref = d.referrer || "";
78    var refHost = "";
79    try { refHost = ref ? (new URL(ref)).hostname : ""; } catch (_) { refHost = ""; }
80    return {
81      title_len: (d.title || "").length,
82      description_len: meta("description").length,
83      canonical: !!d.querySelector('link[rel~="canonical"]'),
84      viewport: !!meta("viewport"),
85      lang: clip(d.documentElement && d.documentElement.lang || "", 24),
86      h1_count: d.querySelectorAll("h1").length,
87      jsonld_count: d.querySelectorAll('script[type="application/ld+json"]').length,
88      links: d.querySelectorAll("a[href]").length,
89      forms: forms.length,
90      webmcp_forms: webmcpForms.length,
91      webmcp_bad_inputs: badToolInputs,
92      interactive: interactive.length,
93      named_interactive: namedInteractive.length,
94      images: imgs.length,
95      images_missing_size: missingImgSize.length,
96      cls_score: Math.round(clsScore * 10000) / 10000,
97      cls_entries: clsEntries,
98      referrer_host: clip(refHost, 80),
99      ai_referrer: aiRefHosts.test(ref)
100    };
101  }
102
103  function observeLayoutShift() {
104    if (!("PerformanceObserver" in w)) return;
105    try {
106      var observer = new PerformanceObserver(function (list) {
107        list.getEntries().forEach(function (entry) {
108          if (entry.hadRecentInput) return;
109          clsScore += entry.value || 0;
110          clsEntries += 1;
111        });
112      });
113      observer.observe({ type: "layout-shift", buffered: true });
114    } catch (_) {}
115  }
116
117  function payload(event, data) {
118    var out = {
119      sdk: "aeo-site-sdk",
120      sdk_version: SDK_VERSION,
121      event: event || eventName,
122      timestamp: new Date().toISOString(),
123      agent_id: agentId,
124      url: location.href,
125      path: location.pathname
126    };
127    if (siteToken) out.site_token = siteToken;
128    if (data) out.data = cleanData(data, 0);
129    return JSON.stringify(out);
130  }
131
132  function post(body) {
133    if (navigator.sendBeacon) {
134      var ok = navigator.sendBeacon(endpoint, new Blob([body], { type: "application/json" }));
135      if (ok) return;
136    }
137    fetch(endpoint, {
138      method: "POST",
139      headers: { "content-type": "application/json" },
140      body: body,
141      keepalive: true,
142      credentials: "omit"
143    }).catch(function () {});
144  }
145
146  function send(event, data) {
147    post(payload(event, data));
148  }
149
150  function signal(extra) {
151    send("page_signal", { signals: collectSignals(), extra: extra || {} });
152  }
153
154  function conversion(name, data) {
155    send("conversion", { conversion: clip(name || "conversion", 80), referrer: d.referrer || "", value: cleanData(data || {}, 0) });
156  }
157
158  w.twin3AEO = {
159    agentId: agentId,
160    siteToken: siteToken,
161    endpoint: endpoint,
162    version: SDK_VERSION,
163    track: send,
164    signal: signal,
165    conversion: conversion,
166    collectSignals: collectSignals
167  };
168
169  function boot() {
170    observeLayoutShift();
171    send("page_view");
172    setTimeout(function () { signal(); }, 700);
173  }
174
175  if (d.readyState === "loading") {
176    d.addEventListener("DOMContentLoaded", boot, { once: true });
177  } else {
178    boot();
179  }
180})();

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.