Files
BaaRai d8f47d4359 Traffic ingest: capture operator browsing via a browser extension
Closes #33. The MITM proxy only sees the agent's own actions, so
anything behind a human gate (signup captcha, email OTP, 2FA, SSO)
left the authenticated surface dark. The operator's browser becomes a
capture source instead:

- traffic: exported RecordExternal storing an externally captured
  exchange through the same spill/blob/FTS path as proxy traffic
  (insert logic shared with record()); rejects unparsable URLs and
  empty methods
- server: POST /api/traffic/ingest inside the authenticated API group,
  batch capped at 50 entries, bodies truncated at 256 KiB, per-entry
  ingested/failed counts
- extension/: Manifest V3 Chrome/Edge extension driving the
  chrome.debugger Network domain on the chosen tab, batching text
  exchanges to the ingest endpoint with the operator's URL + JWT in
  chrome.storage.local; binary bodies skipped; flush on batch/stop
- tests for both layers, README section
2026-10-11 14:53:08 +02:00

47 lines
1.5 KiB
JavaScript

const $ = (id) => document.getElementById(id);
async function refreshState() {
const st = await chrome.runtime.sendMessage({ type: "getState" });
const lines = [];
lines.push(st.capturing
? `● Recording tab ${st.tabId}`
: "○ Not recording");
lines.push(`ingested: ${st.ingested} · queued: ${st.queued} · failed: ${st.failed}`);
if (st.lastError) lines.push(st.lastError);
$("status").textContent = lines.join("\n");
$("error").textContent = st.capturing ? "" : (st.lastError || "");
}
$("save").addEventListener("click", async () => {
await chrome.storage.local.set({
serverUrl: $("serverUrl").value.trim(),
token: $("token").value.trim(),
});
$("save").textContent = "Saved ✓";
setTimeout(() => ($("save").textContent = "Save settings"), 1200);
});
$("start").addEventListener("click", async () => {
const out = await chrome.runtime.sendMessage({ type: "start" });
if (out && out.ok === false) $("error").textContent = out.error || "failed to start";
refreshState();
});
$("stop").addEventListener("click", async () => {
await chrome.runtime.sendMessage({ type: "stop" });
refreshState();
});
$("flush").addEventListener("click", async () => {
await chrome.runtime.sendMessage({ type: "flushNow" });
refreshState();
});
(async () => {
const { serverUrl = "", token = "" } = await chrome.storage.local.get(["serverUrl", "token"]);
$("serverUrl").value = serverUrl;
$("token").value = token;
refreshState();
setInterval(refreshState, 1500);
})();