Something went wrong. Try again.
this post sux
Something went wrong. Try again.
15 kB · 332 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333/* * Live end-to-end check for sux.user.js against real bsky.app and the real * TypeSafe API. Launches a throwaway headless Chrome and plays userscript * manager over CDP: injects the built script at document-start, backs GM_* * storage with an in-memory store, and performs GM_xmlhttpRequest from Node. * The page only ever sees a placeholder key; the harness swaps in the real one * for requests to api.typesafe.ai. The key is never printed. * * source typesafe_api_key.secret.sh && node userscript/scripts/live-e2e.mjs */import { spawn } from "node:child_process";import { mkdtempSync, readFileSync, rmSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path";
const API_KEY = process.env.TYPESAFE_API_KEY;if (!API_KEY) { console.error("Set TYPESAFE_API_KEY first: source typesafe_api_key.secret.sh"); process.exit(2);}const API_URL = "https://api.typesafe.ai/v1/systemone";const PLACEHOLDER = "key-held-by-harness";const CHROME = process.env.CHROME || "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";const PORT = 9333;const PAGE = process.argv[2] || "https://bsky.app/profile/bsky.app";const SCRIPT = readFileSync(new URL("../sux.user.js", import.meta.url), "utf8");
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const results = [];function check(name, ok, detail) { results.push({ name, ok }); console.log((ok ? "PASS " : "FAIL ") + name + (detail !== undefined ? " — " + detail : ""));}
/* ---------------- browser ---------------- */
const profile = mkdtempSync(join(tmpdir(), "sux-e2e-"));const chrome = spawn(CHROME, [ "--headless=new", `--remote-debugging-port=${PORT}`, `--user-data-dir=${profile}`, "--no-first-run", "--no-default-browser-check", "--window-size=1280,1000", "about:blank",], { stdio: "ignore" });function cleanup() { try { chrome.kill(); } catch { /* gone */ } try { rmSync(profile, { recursive: true, force: true }); } catch { /* ignore */ }}process.on("exit", cleanup);
let target;for (let i = 0; i < 50 && !target; i++) { await sleep(200); try { const list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json(); target = list.find((t) => t.type === "page"); } catch { /* not up yet */ }}if (!target) { console.error("Chrome did not start"); process.exit(1); }
const ws = new WebSocket(target.webSocketDebuggerUrl);await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; });let seq = 0;const pending = new Map();const listeners = [];ws.onmessage = (ev) => { const msg = JSON.parse(ev.data); if (msg.id && pending.has(msg.id)) { const { resolve, reject } = pending.get(msg.id); pending.delete(msg.id); msg.error ? reject(new Error(msg.error.message)) : resolve(msg.result); } else if (msg.method) { listeners.forEach((fn) => fn(msg)); }};function cdp(method, params = {}) { const id = ++seq; ws.send(JSON.stringify({ id, method, params })); return new Promise((resolve, reject) => pending.set(id, { resolve, reject }));}async function evaluate(expr) { const r = await cdp("Runtime.evaluate", { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) throw new Error(r.exceptionDetails.text + " " + (r.exceptionDetails.exception?.description || "")); return r.result.value;}
/* ---------------- userscript manager ---------------- */
const store = { settings: { enabled: true, apiKey: PLACEHOLDER, combine: "and", rulesA: [{ enabled: false, word1: "", word2: "" }], rulesB: [{ enabled: true, word: "humor", above: true }], }, panelOpen: true,};const api = { calls: 0, inflight: 0, non2xx: 0, statuses: {}, questionKeys: {} };const consoleErrors = [];
function managerPrelude() { return `(() => { const store = ${JSON.stringify(store)}; const xhrs = new Map(); let nextId = 0; const send = (m) => window.__suxGM(JSON.stringify(m)); window.__suxXhrDone = (id, r) => { const o = xhrs.get(id); xhrs.delete(id); if (!o) return; if (r.error === "timeout") o.ontimeout && o.ontimeout(); else if (r.error) o.onerror && o.onerror(); else o.onload(r); }; const GM = { GM_getValue: (k, d) => (k in store ? JSON.parse(JSON.stringify(store[k])) : d), GM_setValue: (k, v) => { store[k] = JSON.parse(JSON.stringify(v)); send({ op: "set", k, v: store[k] }); }, GM_addValueChangeListener: () => 0, GM_registerMenuCommand: () => 0, GM_xmlhttpRequest: (o) => { const id = ++nextId; xhrs.set(id, o); send({ op: "xhr", id, method: o.method, url: o.url, headers: o.headers, data: o.data, timeout: o.timeout }); }, unsafeWindow: window, }; new Function(...Object.keys(GM), ${JSON.stringify(SCRIPT)})(...Object.values(GM)); })();`;}
listeners.push(async (msg) => { if (msg.method === "Runtime.consoleAPICalled" && msg.params.type === "error") { consoleErrors.push(msg.params.args.map((a) => a.value ?? a.description).join(" ").slice(0, 200)); } if (msg.method === "Runtime.exceptionThrown") { consoleErrors.push((msg.params.exceptionDetails.exception?.description || msg.params.exceptionDetails.text).slice(0, 200)); } if (msg.method !== "Runtime.bindingCalled" || msg.params.name !== "__suxGM") return; const m = JSON.parse(msg.params.payload); if (m.op === "set") { store[m.k] = m.v; return; } if (m.op !== "xhr") return; let reply; try { const headers = { ...(m.headers || {}) }; if (m.url === API_URL && headers.Authorization === "Bearer " + PLACEHOLDER) { headers.Authorization = "Bearer " + API_KEY; } if (m.url === API_URL) { api.calls++; api.inflight++; } if (m.url === API_URL && m.data) { try { for (const k of Object.keys(JSON.parse(m.data).questions || {})) { api.questionKeys[k] = (api.questionKeys[k] || 0) + 1; } } catch { /* non-JSON body; ignore */ } } const res = await fetch(m.url, { method: m.method, headers, body: m.data, signal: AbortSignal.timeout(m.timeout || 30000), }); const text = await res.text(); api.statuses[res.status] = (api.statuses[res.status] || 0) + 1; if (res.status < 200 || res.status >= 300) api.non2xx++; const rawHeaders = [...res.headers].map(([k, v]) => k + ": " + v).join("\r\n"); reply = { status: res.status, responseHeaders: rawHeaders, responseText: text }; } catch (err) { reply = { error: err.name === "TimeoutError" ? "timeout" : "network" }; } finally { if (m.url === API_URL) api.inflight--; } cdp("Runtime.evaluate", { expression: `window.__suxXhrDone(${m.id}, ${JSON.stringify(reply)})` }).catch(() => {});});
await cdp("Runtime.enable");await cdp("Page.enable");await cdp("DOM.enable");await cdp("Runtime.addBinding", { name: "__suxGM" });let injected = null;async function install() { if (injected) await cdp("Page.removeScriptToEvaluateOnNewDocument", { identifier: injected }); injected = (await cdp("Page.addScriptToEvaluateOnNewDocument", { source: managerPrelude() })).identifier;}
async function navigate(url) { const loaded = new Promise((res) => listeners.push((m) => { if (m.method === "Page.loadEventFired") res(); })); await cdp("Page.navigate", { url }); await loaded;}
async function diag() { return evaluate(`(() => { const d = JSON.parse(window.__suxDiagJSON || "{}"); d.hiddenNow = document.querySelectorAll(".sux-hidden").length; return d; })()`);}
// Settle = feed items tracked, no API work in flight, and call count stable for 4s.async function settle(label, maxMs = 120000) { const start = Date.now(); let last = -1, stableSince = Date.now(); while (Date.now() - start < maxMs) { await sleep(1000); const d = await diag().catch(() => ({})); if (api.calls !== last || api.inflight > 0 || !d.items) { last = api.calls; stableSince = Date.now(); continue; } if (Date.now() - stableSince >= 4000) return d; } console.log("(" + label + ": did not settle within " + maxMs / 1000 + "s; api " + JSON.stringify(api) + ")"); console.log(" diag: " + JSON.stringify(await diag().catch((e) => e.message))); consoleErrors.slice(0, 5).forEach((e) => console.log(" page error: " + e)); return diag();}
// Closed shadow roots are reachable through CDP's pierced DOM.async function shadowNode(id) { const { root } = await cdp("DOM.getDocument", { depth: -1, pierce: true }); const stack = [root]; while (stack.length) { const n = stack.pop(); const attrs = n.attributes || []; for (let i = 0; i < attrs.length; i += 2) if (attrs[i] === "id" && attrs[i + 1] === id) return n; for (const k of ["children", "shadowRoots"]) if (n[k]) stack.push(...n[k]); if (n.contentDocument) stack.push(n.contentDocument); } return null;}async function click(id) { const n = await shadowNode(id); if (!n) throw new Error("no element #" + id); const { model } = await cdp("DOM.getBoxModel", { backendNodeId: n.backendNodeId }); const q = model.content; const x = (q[0] + q[4]) / 2, y = (q[1] + q[5]) / 2; for (const type of ["mousePressed", "mouseReleased"]) { await cdp("Input.dispatchMouseEvent", { type, x, y, button: "left", clickCount: 1 }); }}async function textOf(id) { const n = await shadowNode(id); const { object } = await cdp("DOM.resolveNode", { backendNodeId: n.backendNodeId }); const r = await cdp("Runtime.callFunctionOn", { objectId: object.objectId, functionDeclaration: "function(){return this.textContent}", returnByValue: true }); return r.result.value;}
/* ---------------- run ---------------- */
try { await install(); console.log("page: " + PAGE + " rule: keep posts that are mostly \"humor\""); await navigate(PAGE); let d = await settle("first load"); const errText = (await textOf("lastError")).trim(); console.log(`api: ${api.calls} calls, statuses ${JSON.stringify(api.statuses)}; feed: ${d.items} items, ${d.hiddenNow} hidden; near-50: ${d.near50} ${d.near50Samples || ""}`); check("script mounted and tracked feed items", d.items > 0, d.items + " items"); check("no unmatched feed items", d.unmatchedAnchors === 0, d.unmatchedAnchors); check("real Jev calls made", api.calls > 0, api.calls); check("all Jev calls 2xx", api.non2xx === 0, JSON.stringify(api.statuses)); check("panel shows no error", errText === "", errText || "(empty)"); check("some posts judged hide, some show", d.hiddenNow > 0 && d.hiddenNow < d.items, `${d.hiddenNow}/${d.items} hidden`);
const samples = await evaluate(`(() => { const t = (s) => s.replace(/\\s+/g, " ").trim().slice(0, 90); const hidden = [...document.querySelectorAll(".sux-hidden")].slice(0, 3).map((e) => t(e.textContent)); const shown = [...new Set([...document.querySelectorAll('a[href*="/post/"]')] .filter((a) => !a.closest(".sux-hidden")).map((a) => a.closest("[data-testid]")).filter(Boolean))] .slice(0, 3).map((e) => t(e.innerText)); return { hidden, shown }; })()`).catch(() => null); if (samples) { samples.hidden.forEach((s) => console.log(" hidden: " + s)); samples.shown.forEach((s) => console.log(" shown: " + s)); }
await click("testKey"); let testText = ""; for (let i = 0; i < 30 && !/Connected|fail|error|HTTP/i.test(testText); i++) { await sleep(500); testText = await textOf("testResult"); } check("panel Test button reaches the API", /^Connected/.test(testText), testText);
await click("enabled"); await sleep(1500); d = await diag(); check("Filtering off unhides everything", d.hiddenNow === 0 && d.settingsActive === false, d.hiddenNow + " hidden"); check("Filtering off persisted to storage", store.settings.enabled === false);
await click("enabled"); await sleep(1500); d = await diag(); check("Filtering back on re-hides from cache without new calls", d.hiddenNow > 0, d.hiddenNow + " hidden");
await sleep(2500); // cache write is debounced 2s const cached = Object.keys(store.scores || {}).length; check("score cache persisted", cached > 0, cached + " entries"); const before = api.calls; await install(); // re-seed with the persisted store, as a manager would on reload await navigate(PAGE); d = await settle("reload"); check("reload served from cache", api.calls - before <= 3, (api.calls - before) + " new calls, " + d.hiddenNow + " hidden");
/* ---------------- SUX-13: example rule through Jev ---------------- * The userscript has no local model (CSP blocks WASM until the runtime * agent proves otherwise — the ticket's escape hatch), so an example rule * must work Jev-only: buildQuestions emits example<i>, Jev answers it, and * jevToScores rescales onto the local scale. Seed one from a real post. */ const exampleText = await evaluate(`(() => { const t = (s) => s.replace(/\\s+/g, " ").trim(); const anchors = [...document.querySelectorAll('a[href*="/post/"]')] .filter((a) => !a.closest(".sux-hidden")); for (const a of anchors) { const item = a.closest("[data-testid]"); if (item && t(item.innerText).length > 40) return t(item.innerText).slice(0, 400); } return ""; })()`); check("found a feed post to seed as the example", typeof exampleText === "string" && exampleText.length > 40, exampleText.length + " chars");
store.settings = { enabled: true, apiKey: PLACEHOLDER, combine: "and", rulesA: [{ enabled: false, word1: "", word2: "" }], rulesB: [{ enabled: false, word: "humor", above: true }], rulesE: [{ enabled: true, name: "e2e example rule", examples: [{ text: exampleText, alt: "" }] }], }; delete store.scores; // fresh scores for the new rule config const callsBeforeExample = api.calls; await install(); await navigate(PAGE); d = await settle("example rule"); check("example rule tracked feed items", d.items > 0, d.items + " items"); const exampleQs = Object.keys(api.questionKeys).filter((k) => /^j\d+_example0$/.test(k)); check("Jev received the example question", exampleQs.length > 0, JSON.stringify(api.questionKeys)); check("posts batched per call", (api.questionKeys["j1_example0"] || 0) > 0, "j1_example0 × " + (api.questionKeys["j1_example0"] || 0)); check("Jev calls made for the example rule", api.calls - callsBeforeExample > 0, api.calls - callsBeforeExample + " calls"); check("all example-rule calls 2xx", api.non2xx === 0, JSON.stringify(api.statuses)); await sleep(2500); // cache write debounce const e0Scored = Object.entries(store.scores || {}).some(([k, v]) => k[0] === "E" && typeof v === "number"); check("example-rule scores cached (example rule decided)", e0Scored); console.log(`example rule: ${d.hiddenNow}/${d.items} hidden`);
check("no page errors from sux", !consoleErrors.some((e) => /sux/i.test(e)), consoleErrors.filter((e) => /sux/i.test(e)).join(" | ") || "none");} catch (err) { check("harness ran to completion", false, err.message);}
const failed = results.filter((r) => !r.ok).length;console.log(`\n${results.length - failed}/${results.length} passed`);ws.close();cleanup();process.exit(failed ? 1 : 0);