Skip to main content

CLIENT_SCRIPT

Constant CLIENT_SCRIPT 

Source
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.