pub const CLIENT_SCRIPT: &str = "(() => {\n // src/browser/client.ts\n (function() {\n if (window.__DEBUGGER_INITIALIZED__)\n return;\n window.__DEBUGGER_INITIALIZED__ = true;\n const INGEST_URL = window.__DEBUGGER_INGEST_URL__ || \"/_/d\";\n function safeSerialize(v, d = 0, s) {\n if (d > 3)\n return \"[max depth]\";\n if (v === null)\n return null;\n if (v === undefined)\n return \"[undefined]\";\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\")\n return v;\n if (t === \"bigint\")\n return v.toString() + \"n\";\n if (t === \"symbol\")\n return v.toString();\n if (t === \"function\")\n return `[Function: ${v.name || \"anonymous\"}]`;\n if (t !== \"object\")\n return String(v);\n const o = v;\n if (!s)\n s = new WeakSet;\n if (s.has(o))\n return \"[Circular]\";\n s.add(o);\n if (o instanceof Error)\n return { name: o.name, message: o.message, stack: o.stack };\n if (typeof Node !== \"undefined\" && o instanceof Node)\n return `[${o.tagName || \"Node\"}]`;\n if (Array.isArray(o))\n return o.map((i) => safeSerialize(i, d + 1, s));\n const r = {};\n try {\n for (const k of Object.keys(o))\n r[k] = safeSerialize(o[k], d + 1, s);\n } catch {\n return \"[unserializable]\";\n }\n return r;\n }\n function detectBrowser() {\n const ua = navigator.userAgent;\n const n = navigator;\n if (n.brave)\n return \"Brave\";\n const known = [\n [\"Edg/\", \"Edge\"],\n [\"OPR/\", \"Opera\"],\n [\"Opera/\", \"Opera\"],\n [\"Dia/\", \"Dia\"],\n [\"Arc/\", \"Arc\"],\n [\"Comet/\", \"Comet\"],\n [\"Atlas/\", \"ChatGPT Atlas\"],\n [\"Mariner/\", \"Mariner\"],\n [\"SigmaOS/\", \"SigmaOS\"],\n [\"Sidekick/\", \"Sidekick\"],\n [\"Wavebox/\", \"Wavebox\"],\n [\"Beam/\", \"Beam\"],\n [\"Mighty/\", \"Mighty\"],\n [\"Island/\", \"Island\"],\n [\"Stack/\", \"Stack\"],\n [\"Shift/\", \"Shift\"],\n [\"Station/\", \"Station\"],\n [\"DuckDuckGo/\", \"DuckDuckGo\"],\n [\"Mullvad/\", \"Mullvad\"],\n [\"Tor/\", \"Tor\"],\n [\"Epic/\", \"Epic\"],\n [\"Ghost/\", \"Ghost\"],\n [\"Polypane/\", \"Polypane\"],\n [\"Blisk/\", \"Blisk\"],\n [\"Responsively/\", \"Responsively\"],\n [\"Vivaldi/\", \"Vivaldi\"],\n [\"YaBrowser/\", \"Yandex\"],\n [\"SamsungBrowser/\", \"Samsung\"],\n [\"UCBrowser/\", \"UC\"],\n [\"Whale/\", \"Whale\"],\n [\"Puffin/\", \"Puffin\"],\n [\"QQBrowser/\", \"QQ\"],\n [\"Sleipnir/\", \"Sleipnir\"],\n [\"Maxthon/\", \"Maxthon\"],\n [\"Naver/\", \"Naver\"],\n [\"KiwiBrowser/\", \"Kiwi\"],\n [\"Colibri/\", \"Colibri\"],\n [\"Orion/\", \"Orion\"],\n [\"Zen/\", \"Zen\"],\n [\"Ladybird/\", \"Ladybird\"],\n [\"Neon/\", \"Opera Neon\"],\n [\"Min/\", \"Min\"],\n [\"Midori/\", \"Midori\"],\n [\"LibreWolf/\", \"LibreWolf\"],\n [\"Waterfox/\", \"Waterfox\"],\n [\"PaleMoon/\", \"Pale Moon\"]\n ];\n for (const [token, name] of known) {\n if (ua.includes(token))\n return name;\n }\n if (ua.includes(\"Firefox/\"))\n return \"Firefox\";\n if (ua.includes(\"Safari/\") && !ua.includes(\"Chrome/\"))\n return \"Safari\";\n const generic = new Set([\"Mozilla\", \"AppleWebKit\", \"Chrome\", \"Safari\", \"Gecko\", \"KHTML\"]);\n const tokens = ua.match(/(\\w[\\w\\s]*?)\\/[\\d.]+/g);\n if (tokens) {\n for (let i = tokens.length - 1;i >= 0; i--) {\n const name = tokens[i].split(\"/\")[0].trim();\n if (!generic.has(name))\n return name;\n }\n }\n if (ua.includes(\"Chrome/\"))\n return \"Chrome\";\n return \"Unknown\";\n }\n const BROWSER_NAME = detectBrowser();\n let buf = [];\n let timer = null;\n function flush() {\n if (buf.length === 0)\n return;\n const batch = buf;\n buf = [];\n timer = null;\n const p = JSON.stringify(batch);\n try {\n if (navigator.sendBeacon) {\n if (navigator.sendBeacon(INGEST_URL, new Blob([p], { type: \"application/json\" })))\n return;\n }\n } catch {}\n try {\n fetch(INGEST_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: p,\n keepalive: true,\n credentials: \"omit\",\n mode: \"cors\"\n }).catch(() => {});\n } catch {}\n }\n function send(data) {\n buf.push(data);\n if (buf.length >= 10) {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n flush();\n } else if (!timer) {\n timer = setTimeout(flush, 100);\n }\n }\n function netEntry(url, method, start, status, failed, kind, extra) {\n const e = { type: \"network\", url, method, status, duration: Date.now() - start, timestamp: start, failed, source: \"browser\", kind, browser: BROWSER_NAME };\n if (extra)\n for (const k in extra)\n e[k] = extra[k];\n return e;\n }\n for (const level of [\"log\", \"warn\", \"error\", \"debug\", \"info\"]) {\n const orig = console[level];\n console[level] = function(...args) {\n send({ type: \"console\", level, args: args.map((a) => safeSerialize(a)), timestamp: Date.now(), source: \"browser\", browser: BROWSER_NAME });\n return orig.apply(console, args);\n };\n }\n window.addEventListener(\"error\", (ev) => {\n send({ type: \"error\", message: ev.message, stack: ev.error?.stack || null, timestamp: Date.now(), source: \"browser\", url: location.href, browser: BROWSER_NAME });\n });\n window.addEventListener(\"unhandledrejection\", (ev) => {\n const r = ev.reason;\n send({ type: \"error\", message: r instanceof Error ? r.message : String(r), stack: r instanceof Error ? r.stack : null, timestamp: Date.now(), source: \"browser\", url: location.href, browser: BROWSER_NAME });\n });\n function extractHeaders(h) {\n if (!h)\n return;\n const r = {};\n if (h instanceof Headers)\n h.forEach((v, k) => {\n r[k] = v;\n });\n else if (Array.isArray(h))\n for (const [k, v] of h)\n r[k] = v;\n else\n for (const k of Object.keys(h))\n r[k] = h[k];\n return Object.keys(r).length > 0 ? r : undefined;\n }\n const origFetch = window.fetch;\n window.fetch = function(input, init) {\n const m = init?.method?.toUpperCase() || \"GET\";\n const u = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const t = Date.now();\n return origFetch.apply(window, [input, init]).then((res) => {\n const e = netEntry(u, m, t, res.status, !res.ok, \"fetch\", { requestHeaders: extractHeaders(init?.headers), responseHeaders: extractHeaders(res.headers) });\n if (!res.ok) {\n try {\n res.clone().text().then((txt) => {\n e.responseBody = txt.slice(0, 4096);\n send(e);\n }).catch(() => send(e));\n return res;\n } catch {}\n }\n send(e);\n return res;\n }, (err) => {\n send(netEntry(u, m, t, 0, true, \"fetch\", { requestHeaders: extractHeaders(init?.headers) }));\n throw err;\n });\n };\n Object.assign(window.fetch, origFetch);\n const XP = XMLHttpRequest.prototype;\n const origOpen = XP.open;\n const origSend = XP.send;\n XP.open = function(method, url, ...rest) {\n this._dm = method.toUpperCase();\n this._du = typeof url === \"string\" ? url : url.href;\n return origOpen.apply(this, [method, url, ...rest]);\n };\n XP.send = function(body) {\n const t = Date.now();\n const m = this._dm || \"GET\";\n const u = this._du || \"\";\n const emit = (status, failed) => {\n const rh = {};\n try {\n const raw = this.getAllResponseHeaders();\n if (raw)\n for (const ln of raw.trim().split(/[\\r\\n]+/)) {\n const i = ln.indexOf(\": \");\n if (i > 0)\n rh[ln.slice(0, i).toLowerCase()] = ln.slice(i + 2);\n }\n } catch {}\n const e = netEntry(u, m, t, status, failed, \"xhr\", { responseHeaders: Object.keys(rh).length > 0 ? rh : undefined });\n if (failed) {\n try {\n const b = typeof this.responseText === \"string\" ? this.responseText : \"\";\n if (b)\n e.responseBody = b.slice(0, 4096);\n } catch {}\n }\n send(e);\n };\n this.addEventListener(\"load\", function() {\n emit(this.status, this.status >= 400);\n });\n this.addEventListener(\"error\", function() {\n emit(0, true);\n });\n return origSend.call(this, body);\n };\n const OrigWS = window.WebSocket;\n function wsEntry(u, t, status, failed, cid, mc, rs) {\n return netEntry(u, \"WS\", t, status, failed, \"ws\", { connectionId: cid, messageCount: mc, wsReadyState: rs });\n }\n const WrappedWS = function(url, protocols) {\n const ws = new OrigWS(url, protocols);\n const t = Date.now();\n const u = typeof url === \"string\" ? url : url.href;\n const cid = Math.random().toString(16).slice(2, 8);\n let mc = 0;\n send(wsEntry(u, t, 0, false, cid, 0, OrigWS.CONNECTING));\n ws.addEventListener(\"message\", () => {\n mc++;\n });\n ws.addEventListener(\"close\", (ev) => {\n send(wsEntry(u, t, ev.code, ev.code !== 1000 && ev.code !== 1001, cid, mc, OrigWS.CLOSED));\n });\n ws.addEventListener(\"error\", () => {\n send(wsEntry(u, t, 0, true, cid, mc, OrigWS.CLOSED));\n });\n return ws;\n };\n WrappedWS.prototype = OrigWS.prototype;\n WrappedWS.CONNECTING = OrigWS.CONNECTING;\n WrappedWS.OPEN = OrigWS.OPEN;\n WrappedWS.CLOSING = OrigWS.CLOSING;\n WrappedWS.CLOSED = OrigWS.CLOSED;\n window.WebSocket = WrappedWS;\n let lastAppHash = \"\";\n function parseCookies() {\n if (!document.cookie)\n return [];\n return document.cookie.split(\"; \").map((c) => {\n const i = c.indexOf(\"=\");\n return { name: c.slice(0, i), value: c.slice(i + 1) };\n });\n }\n function storageToRecord(storage) {\n const r = {};\n for (let i = 0;i < storage.length; i++) {\n const k = storage.key(i);\n if (k !== null)\n r[k] = storage.getItem(k) || \"\";\n }\n return r;\n }\n async function captureAppState() {\n const entry = {\n type: \"app\",\n timestamp: Date.now(),\n source: \"browser\",\n browser: BROWSER_NAME\n };\n entry.cookies = parseCookies();\n try {\n entry.localStorage = storageToRecord(localStorage);\n } catch {}\n try {\n entry.sessionStorage = storageToRecord(sessionStorage);\n } catch {}\n try {\n if (navigator.serviceWorker) {\n const regs = await navigator.serviceWorker.getRegistrations();\n entry.serviceWorkers = regs.map((r) => ({\n scope: r.scope,\n scriptURL: (r.active || r.installing || r.waiting)?.scriptURL || \"\",\n state: (r.active || r.installing || r.waiting)?.state || \"unknown\"\n }));\n }\n } catch {}\n try {\n if (typeof caches !== \"undefined\") {\n const names = await caches.keys();\n const info = [];\n for (const name of names) {\n try {\n const cache = await caches.open(name);\n const keys = await cache.keys();\n info.push({ name, entryCount: keys.length });\n } catch {}\n }\n entry.cacheStorage = info;\n }\n } catch {}\n try {\n if (navigator.permissions) {\n const names = [\"geolocation\", \"notifications\", \"camera\", \"microphone\", \"clipboard-read\", \"clipboard-write\"];\n const perms = [];\n for (const name of names) {\n try {\n const result = await navigator.permissions.query({ name });\n perms.push({ name, state: result.state });\n } catch {}\n }\n entry.permissions = perms;\n }\n } catch {}\n try {\n if (navigator.storage && navigator.storage.estimate) {\n const est = await navigator.storage.estimate();\n entry.storageEstimate = { usage: est.usage || 0, quota: est.quota || 0 };\n }\n } catch {}\n const hash = JSON.stringify([entry.cookies, entry.localStorage, entry.sessionStorage, entry.serviceWorkers, entry.cacheStorage, entry.permissions, entry.storageEstimate]);\n if (hash !== lastAppHash) {\n lastAppHash = hash;\n send(entry);\n }\n }\n let appCaptureScheduled = false;\n function scheduleAppCapture() {\n if (appCaptureScheduled)\n return;\n appCaptureScheduled = true;\n setTimeout(() => {\n appCaptureScheduled = false;\n captureAppState();\n }, 50);\n }\n const origSetItem = Storage.prototype.setItem;\n const origRemoveItem = Storage.prototype.removeItem;\n const origClear = Storage.prototype.clear;\n Storage.prototype.setItem = function(key, value) {\n origSetItem.call(this, key, value);\n scheduleAppCapture();\n };\n Storage.prototype.removeItem = function(key) {\n origRemoveItem.call(this, key);\n scheduleAppCapture();\n };\n Storage.prototype.clear = function() {\n origClear.call(this);\n scheduleAppCapture();\n };\n if (document.readyState === \"complete\") {\n captureAppState();\n } else {\n window.addEventListener(\"load\", () => captureAppState());\n }\n setInterval(() => captureAppState(), 1e4);\n window.addEventListener(\"storage\", () => captureAppState());\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\")\n flush();\n });\n window.addEventListener(\"pagehide\", flush);\n })();\n})();\n";Expand description
The browser instrumentation IIFE script, embedded at compile time.