Created
January 6, 2026 10:38
-
-
Save the-code-rider/0e19f59df01f627b0d7ded787849bfb0 to your computer and use it in GitHub Desktop.
display network request as a heads up display
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (() => { | |
| // --- Guard (so you can re-run safely) --- | |
| if (window.__NET_HUD__) { | |
| window.__NET_HUD__.destroy(); | |
| console.log("[net-hud] removed existing HUD"); | |
| } | |
| // --- Config --- | |
| const CFG = { | |
| maxItems: 200, | |
| bodyPreviewChars: 1200, | |
| hudWidth: 520, | |
| hudMaxHeight: 420 | |
| }; | |
| // --- State --- | |
| const state = { | |
| enabled: true, | |
| minimized: false, | |
| filter: "", | |
| items: [], | |
| expandedId: null, | |
| seq: 0, | |
| origFetch: window.fetch, | |
| origXHROpen: XMLHttpRequest.prototype.open, | |
| origXHRSend: XMLHttpRequest.prototype.send | |
| }; | |
| const nowISO = () => new Date().toISOString(); | |
| const ms = (t0) => Math.max(0, Math.round(performance.now() - t0)); | |
| const clampStr = (s, n) => (s && s.length > n ? s.slice(0, n) + "…" : s); | |
| function safeStringify(obj) { | |
| try { return JSON.stringify(obj, null, 2); } catch { return String(obj); } | |
| } | |
| function toHeaderObject(headers) { | |
| try { | |
| if (!headers) return null; | |
| if (typeof headers.forEach === "function") { | |
| const o = {}; | |
| headers.forEach((v, k) => (o[k] = v)); | |
| return o; | |
| } | |
| if (Array.isArray(headers)) return Object.fromEntries(headers); | |
| if (typeof headers === "object") return headers; | |
| return null; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| function pushItem(item) { | |
| state.items.unshift(item); | |
| if (state.items.length > CFG.maxItems) state.items.length = CFG.maxItems; | |
| render(); | |
| } | |
| function matchesFilter(item) { | |
| const f = state.filter.trim().toLowerCase(); | |
| if (!f) return true; | |
| return ( | |
| (item.url || "").toLowerCase().includes(f) || | |
| (item.method || "").toLowerCase().includes(f) || | |
| String(item.status || "").includes(f) | |
| ); | |
| } | |
| // --- HUD UI --- | |
| const root = document.createElement("div"); | |
| root.id = "__net_hud__"; | |
| root.style.cssText = ` | |
| position: fixed; | |
| top: 12px; | |
| right: 12px; | |
| width: ${CFG.hudWidth}px; | |
| max-width: calc(100vw - 24px); | |
| z-index: 2147483647; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |
| font-size: 12px; | |
| color: #eaeaea; | |
| background: rgba(10, 10, 12, 0.88); | |
| border: 1px solid rgba(255,255,255,0.14); | |
| border-radius: 12px; | |
| box-shadow: 0 18px 60px rgba(0,0,0,0.5); | |
| overflow: hidden; | |
| backdrop-filter: blur(8px); | |
| `; | |
| const header = document.createElement("div"); | |
| header.style.cssText = ` | |
| display: flex; | |
| gap: 8px; | |
| align-items: center; | |
| padding: 10px 10px 8px 10px; | |
| border-bottom: 1px solid rgba(255,255,255,0.10); | |
| user-select: none; | |
| cursor: default; | |
| `; | |
| const title = document.createElement("div"); | |
| title.textContent = "NET HUD"; | |
| title.style.cssText = `font-weight: 700; letter-spacing: 0.4px;`; | |
| const pill = document.createElement("div"); | |
| pill.style.cssText = ` | |
| margin-left: 6px; | |
| padding: 2px 8px; | |
| border-radius: 999px; | |
| font-size: 11px; | |
| border: 1px solid rgba(255,255,255,0.14); | |
| color: rgba(255,255,255,0.85); | |
| `; | |
| const spacer = document.createElement("div"); | |
| spacer.style.flex = "1"; | |
| const filter = document.createElement("input"); | |
| filter.placeholder = "filter (url / method / status)…"; | |
| filter.style.cssText = ` | |
| flex: 1; | |
| min-width: 140px; | |
| padding: 6px 8px; | |
| border-radius: 10px; | |
| border: 1px solid rgba(255,255,255,0.14); | |
| outline: none; | |
| background: rgba(255,255,255,0.06); | |
| color: #fff; | |
| `; | |
| filter.addEventListener("input", () => { | |
| state.filter = filter.value || ""; | |
| render(); | |
| }); | |
| function mkBtn(label, onClick) { | |
| const b = document.createElement("button"); | |
| b.textContent = label; | |
| b.style.cssText = ` | |
| padding: 6px 8px; | |
| border-radius: 10px; | |
| border: 1px solid rgba(255,255,255,0.14); | |
| background: rgba(255,255,255,0.06); | |
| color: rgba(255,255,255,0.92); | |
| cursor: pointer; | |
| `; | |
| b.onmouseenter = () => (b.style.background = "rgba(255,255,255,0.10)"); | |
| b.onmouseleave = () => (b.style.background = "rgba(255,255,255,0.06)"); | |
| b.onclick = onClick; | |
| return b; | |
| } | |
| const btnPause = mkBtn("Pause", () => { | |
| state.enabled = !state.enabled; | |
| btnPause.textContent = state.enabled ? "Pause" : "Resume"; | |
| render(); | |
| }); | |
| const btnClear = mkBtn("Clear", () => { | |
| state.items = []; | |
| state.expandedId = null; | |
| render(); | |
| }); | |
| const btnCopy = mkBtn("Copy JSON", async () => { | |
| const payload = state.items.slice().reverse(); // oldest->newest | |
| const text = safeStringify(payload); | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| btnCopy.textContent = "Copied"; | |
| setTimeout(() => (btnCopy.textContent = "Copy JSON"), 700); | |
| } catch { | |
| console.log(text); | |
| btnCopy.textContent = "Logged"; | |
| setTimeout(() => (btnCopy.textContent = "Copy JSON"), 700); | |
| } | |
| }); | |
| const btnMin = mkBtn("Min", () => { | |
| state.minimized = !state.minimized; | |
| btnMin.textContent = state.minimized ? "Max" : "Min"; | |
| render(); | |
| }); | |
| const btnClose = mkBtn("×", () => window.__NET_HUD__.destroy()); | |
| btnClose.style.padding = "6px 10px"; | |
| btnClose.style.fontWeight = "800"; | |
| header.appendChild(title); | |
| header.appendChild(pill); | |
| header.appendChild(spacer); | |
| header.appendChild(filter); | |
| header.appendChild(btnPause); | |
| header.appendChild(btnClear); | |
| header.appendChild(btnCopy); | |
| header.appendChild(btnMin); | |
| header.appendChild(btnClose); | |
| const body = document.createElement("div"); | |
| body.style.cssText = ` | |
| max-height: ${CFG.hudMaxHeight}px; | |
| overflow: auto; | |
| `; | |
| const details = document.createElement("div"); | |
| details.style.cssText = ` | |
| display: none; | |
| padding: 10px; | |
| border-top: 1px solid rgba(255,255,255,0.10); | |
| background: rgba(255,255,255,0.03); | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| `; | |
| root.appendChild(header); | |
| root.appendChild(body); | |
| root.appendChild(details); | |
| document.documentElement.appendChild(root); | |
| function statusBadgeColor(status) { | |
| if (status == null) return "rgba(255,255,255,0.10)"; | |
| if (status >= 200 && status < 300) return "rgba(60, 200, 120, 0.28)"; | |
| if (status >= 300 && status < 400) return "rgba(120, 180, 240, 0.26)"; | |
| if (status >= 400 && status < 500) return "rgba(240, 180, 80, 0.26)"; | |
| return "rgba(240, 90, 90, 0.26)"; | |
| } | |
| function render() { | |
| pill.textContent = `${state.enabled ? "live" : "paused"} • ${state.items.length}/${CFG.maxItems}`; | |
| if (state.minimized) { | |
| body.style.display = "none"; | |
| details.style.display = "none"; | |
| filter.style.display = "none"; | |
| return; | |
| } else { | |
| body.style.display = "block"; | |
| filter.style.display = "block"; | |
| } | |
| // list | |
| body.innerHTML = ""; | |
| const visible = state.items.filter(matchesFilter); | |
| visible.slice(0, CFG.maxItems).forEach(item => { | |
| const row = document.createElement("div"); | |
| row.style.cssText = ` | |
| display: grid; | |
| grid-template-columns: 54px 1fr 52px 52px; | |
| gap: 8px; | |
| align-items: center; | |
| padding: 8px 10px; | |
| border-bottom: 1px solid rgba(255,255,255,0.06); | |
| cursor: pointer; | |
| `; | |
| row.onmouseenter = () => (row.style.background = "rgba(255,255,255,0.05)"); | |
| row.onmouseleave = () => (row.style.background = "transparent"); | |
| const method = document.createElement("div"); | |
| method.textContent = (item.method || "GET").toUpperCase(); | |
| method.style.cssText = `opacity: 0.92; font-weight: 700;`; | |
| const url = document.createElement("div"); | |
| url.textContent = item.url || "(unknown url)"; | |
| url.style.cssText = `opacity: 0.92; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;`; | |
| const status = document.createElement("div"); | |
| status.textContent = item.status == null ? "—" : String(item.status); | |
| status.style.cssText = ` | |
| text-align: center; | |
| padding: 2px 6px; | |
| border-radius: 999px; | |
| background: ${statusBadgeColor(item.status)}; | |
| border: 1px solid rgba(255,255,255,0.10); | |
| `; | |
| const dur = document.createElement("div"); | |
| dur.textContent = item.durationMs != null ? `${item.durationMs}ms` : "—"; | |
| dur.style.cssText = `text-align: right; opacity: 0.85;`; | |
| row.appendChild(method); | |
| row.appendChild(url); | |
| row.appendChild(status); | |
| row.appendChild(dur); | |
| row.onclick = () => { | |
| state.expandedId = state.expandedId === item.id ? null : item.id; | |
| renderDetails(); | |
| }; | |
| body.appendChild(row); | |
| }); | |
| renderDetails(); | |
| } | |
| function renderDetails() { | |
| const item = state.items.find(x => x.id === state.expandedId); | |
| if (!item) { | |
| details.style.display = "none"; | |
| details.textContent = ""; | |
| return; | |
| } | |
| details.style.display = "block"; | |
| const parts = []; | |
| parts.push(`[${item.ts}] ${item.method} ${item.url}`); | |
| if (item.status != null) parts.push(`status: ${item.status}`); | |
| if (item.durationMs != null) parts.push(`duration: ${item.durationMs}ms`); | |
| if (item.type) parts.push(`type: ${item.type}`); | |
| if (item.error) parts.push(`error: ${item.error}`); | |
| parts.push(""); | |
| if (item.requestHeaders) { | |
| parts.push("request headers:"); | |
| parts.push(safeStringify(item.requestHeaders)); | |
| parts.push(""); | |
| } | |
| if (item.responseHeaders) { | |
| parts.push("response headers:"); | |
| parts.push(safeStringify(item.responseHeaders)); | |
| parts.push(""); | |
| } | |
| if (item.requestBodyPreview) { | |
| parts.push("request body (preview):"); | |
| parts.push(item.requestBodyPreview); | |
| parts.push(""); | |
| } | |
| if (item.responseBodyPreview) { | |
| parts.push("response body (preview):"); | |
| parts.push(item.responseBodyPreview); | |
| parts.push(""); | |
| } | |
| details.textContent = parts.join("\n"); | |
| } | |
| render(); | |
| // --- Hooks: fetch --- | |
| window.fetch = async (...args) => { | |
| const id = `f_${++state.seq}`; | |
| const t0 = performance.now(); | |
| let url = ""; | |
| let method = "GET"; | |
| let reqHeaders = null; | |
| let reqBodyPreview = null; | |
| try { | |
| const [input, init] = args; | |
| url = typeof input === "string" ? input : input?.url || ""; | |
| method = (init?.method || (typeof input !== "string" && input?.method) || "GET").toUpperCase(); | |
| reqHeaders = toHeaderObject(init?.headers) || toHeaderObject((typeof input !== "string" && input?.headers) ? input.headers : null); | |
| if (init?.body != null) reqBodyPreview = clampStr(String(init.body), CFG.bodyPreviewChars); | |
| } catch {} | |
| if (state.enabled) { | |
| pushItem({ | |
| id, | |
| ts: nowISO(), | |
| type: "fetch", | |
| phase: "start", | |
| url, | |
| method, | |
| requestHeaders: reqHeaders, | |
| requestBodyPreview: reqBodyPreview | |
| }); | |
| } | |
| try { | |
| const res = await state.origFetch(...args); | |
| const durationMs = ms(t0); | |
| let responseHeaders = null; | |
| let responseBodyPreview = null; | |
| try { | |
| responseHeaders = toHeaderObject(res.headers); | |
| // Best-effort body preview (may fail for opaque/cors/stream) | |
| const ct = res.headers?.get?.("content-type") || ""; | |
| if (!/image|audio|video|font|octet-stream/i.test(ct)) { | |
| const text = await res.clone().text(); | |
| responseBodyPreview = clampStr(text, CFG.bodyPreviewChars); | |
| } | |
| } catch {} | |
| if (state.enabled) { | |
| pushItem({ | |
| id, | |
| ts: nowISO(), | |
| type: "fetch", | |
| phase: "done", | |
| url, | |
| method, | |
| status: res.status, | |
| durationMs, | |
| responseHeaders, | |
| responseBodyPreview | |
| }); | |
| } | |
| return res; | |
| } catch (err) { | |
| if (state.enabled) { | |
| pushItem({ | |
| id, | |
| ts: nowISO(), | |
| type: "fetch", | |
| phase: "error", | |
| url, | |
| method, | |
| durationMs: ms(t0), | |
| error: String(err?.message || err) | |
| }); | |
| } | |
| throw err; | |
| } | |
| }; | |
| // --- Hooks: XHR --- | |
| XMLHttpRequest.prototype.open = function (method, url) { | |
| this.__netHudMeta = { | |
| id: `x_${++state.seq}`, | |
| t0: performance.now(), | |
| method: String(method || "GET").toUpperCase(), | |
| url: String(url || "") | |
| }; | |
| return state.origXHROpen.apply(this, arguments); | |
| }; | |
| XMLHttpRequest.prototype.send = function (body) { | |
| const meta = this.__netHudMeta || { | |
| id: `x_${++state.seq}`, | |
| t0: performance.now(), | |
| method: "GET", | |
| url: "(unknown)" | |
| }; | |
| const reqBodyPreview = body != null ? clampStr(String(body), CFG.bodyPreviewChars) : null; | |
| if (state.enabled) { | |
| pushItem({ | |
| id: meta.id, | |
| ts: nowISO(), | |
| type: "xhr", | |
| phase: "start", | |
| url: meta.url, | |
| method: meta.method, | |
| requestBodyPreview: reqBodyPreview | |
| }); | |
| } | |
| this.addEventListener("loadend", () => { | |
| if (!state.enabled) return; | |
| let responseBodyPreview = null; | |
| try { | |
| const ct = this.getResponseHeader?.("content-type") || ""; | |
| if (!/image|audio|video|font|octet-stream/i.test(ct)) { | |
| responseBodyPreview = clampStr(String(this.responseText || ""), CFG.bodyPreviewChars); | |
| } | |
| } catch {} | |
| pushItem({ | |
| id: meta.id, | |
| ts: nowISO(), | |
| type: "xhr", | |
| phase: "done", | |
| url: meta.url, | |
| method: meta.method, | |
| status: this.status, | |
| durationMs: ms(meta.t0), | |
| responseBodyPreview | |
| }); | |
| }); | |
| return state.origXHRSend.apply(this, arguments); | |
| }; | |
| // --- Drag to move (HUD) --- | |
| let drag = null; | |
| header.addEventListener("mousedown", (e) => { | |
| // avoid dragging when clicking buttons/input | |
| const tag = (e.target?.tagName || "").toLowerCase(); | |
| if (tag === "button" || tag === "input") return; | |
| drag = { | |
| startX: e.clientX, | |
| startY: e.clientY, | |
| rect: root.getBoundingClientRect() | |
| }; | |
| e.preventDefault(); | |
| }); | |
| window.addEventListener("mousemove", (e) => { | |
| if (!drag) return; | |
| const dx = e.clientX - drag.startX; | |
| const dy = e.clientY - drag.startY; | |
| // switch to left/top positioning when dragged | |
| root.style.right = "auto"; | |
| root.style.bottom = "auto"; | |
| root.style.left = Math.max(8, drag.rect.left + dx) + "px"; | |
| root.style.top = Math.max(8, drag.rect.top + dy) + "px"; | |
| }); | |
| window.addEventListener("mouseup", () => (drag = null)); | |
| // --- Public control surface --- | |
| window.__NET_HUD__ = { | |
| state, | |
| export() { | |
| return state.items.slice().reverse(); | |
| }, | |
| destroy() { | |
| // restore originals | |
| window.fetch = state.origFetch; | |
| XMLHttpRequest.prototype.open = state.origXHROpen; | |
| XMLHttpRequest.prototype.send = state.origXHRSend; | |
| // remove UI | |
| root.remove(); | |
| delete window.__NET_HUD__; | |
| console.log("[net-hud] destroyed and hooks restored"); | |
| } | |
| }; | |
| console.log("[net-hud] running. Controls: __NET_HUD__.export(), __NET_HUD__.destroy()"); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment