@wenathlan/extension 1.1.40 → 1.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/httpclient.d.ts +158 -0
- package/dist/httpclient.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1138 -6
- package/dist/index.js.map +4 -4
- package/dist/media.d.ts +96 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/memory.d.ts +55 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +37 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +37 -4
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +336 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1832 -13
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +203 -3
- package/extension/dist/pagebridge.js.map +3 -3
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +22 -2
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +400 -2
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/dist/style.css +4 -0
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Devthink review</title><link rel="stylesheet" href="style.css"></head>
|
|
4
|
-
<body><main class="wide"><header><p class="eyebrow">REVIEW GATE</p><h1>Plan before action</h1><p>Every browser operation remains blocked until the user reviews and approves it here.</p></header><label for="objective">Objective</label><textarea id="objective" rows="4" placeholder="Describe a browser task to turn into a reviewable plan."></textarea><div class="actions"><button id="localplan">Create local observation plan</button><button id="remoteplan" class="secondary">Ask configured endpoint</button><button id="diagnostic" class="secondary">Capture local diagnostics</button></div><p id="status" role="status">Loading session state.</p><p id="capabilitiestext" role="status" class="muted">Optional capabilities unknown.</p><section><h2>Current plan</h2><progress id="planprogress" max="1" value="0"></progress><div id="plan" class="panel"></div></section><section><h2>Clickable map</h2><ol id="map" class="audit maplist"></ol></section><section><h2>Navigation trail</h2><ol id="trail" class="audit"></ol></section><section><h2>Navigation state</h2><div id="navigation" class="panel"></div></section><section><h2>Tabs and windows</h2><div id="tabswindows" class="panel"></div></section><section><h2>Forms and data</h2><div id="forms" class="panel"></div></section><section><h2>Files and downloads</h2><div id="files" class="panel"></div></section><section><h2>Capture gallery</h2><div id="captures" class="panel"></div></section><section><h2>Datasets</h2><div id="datasets" class="panel"></div></section><section><h2>Consent banners</h2><div id="banners" class="panel"></div></section><section><h2>Accessibility tree</h2><div id="a11y" class="panel"></div></section><section><h2>Reader view</h2><div id="reader" class="panel"></div></section><section><h2>Detected shapes</h2><ol id="detections" class="audit maplist"></ol></section><section><h2>Watch stream</h2><ol id="stream" class="audit"></ol></section><section><h2>Snapshot diffs</h2><div id="diffs" class="panel"></div></section><section><h2>Derived selectors</h2><ol id="selectors" class="audit maplist"></ol></section><section><h2>Structured diagnostics</h2><ul id="diagnostics" class="audit"></ul></section><section><h2>Local audit</h2><ol id="audit" class="audit"></ol></section></main><script type="module" src="sidepanel.js"></script></body>
|
|
4
|
+
<body><main class="wide"><header><p class="eyebrow">REVIEW GATE</p><h1>Plan before action</h1><p>Every browser operation remains blocked until the user reviews and approves it here.</p></header><label for="objective">Objective</label><textarea id="objective" rows="4" placeholder="Describe a browser task to turn into a reviewable plan."></textarea><div class="actions"><button id="localplan">Create local observation plan</button><button id="remoteplan" class="secondary">Ask configured endpoint</button><button id="diagnostic" class="secondary">Capture local diagnostics</button></div><p id="status" role="status">Loading session state.</p><p id="capabilitiestext" role="status" class="muted">Optional capabilities unknown.</p><section><h2>Current plan</h2><progress id="planprogress" max="1" value="0"></progress><div id="plan" class="panel"></div></section><section><h2>Clickable map</h2><ol id="map" class="audit maplist"></ol></section><section><h2>Navigation trail</h2><ol id="trail" class="audit"></ol></section><section><h2>Navigation state</h2><div id="navigation" class="panel"></div></section><section><h2>Tabs and windows</h2><div id="tabswindows" class="panel"></div></section><section><h2>Forms and data</h2><div id="forms" class="panel"></div></section><section><h2>Files and downloads</h2><div id="files" class="panel"></div></section><section><h2>Capture gallery</h2><div id="captures" class="panel"></div></section><section><h2>Media</h2><div id="media" class="panel"></div></section><section><h2>Network calls</h2><div id="calls" class="panel"></div></section><section><h2>Datasets</h2><div id="datasets" class="panel"></div></section><section><h2>Consent banners</h2><div id="banners" class="panel"></div></section><section><h2>Accessibility tree</h2><div id="a11y" class="panel"></div></section><section><h2>Reader view</h2><div id="reader" class="panel"></div></section><section><h2>Detected shapes</h2><ol id="detections" class="audit maplist"></ol></section><section><h2>Watch stream</h2><ol id="stream" class="audit"></ol></section><section><h2>Snapshot diffs</h2><div id="diffs" class="panel"></div></section><section><h2>Derived selectors</h2><ol id="selectors" class="audit maplist"></ol></section><section><h2>Structured diagnostics</h2><ul id="diagnostics" class="audit"></ul></section><section><h2>Local audit</h2><ol id="audit" class="audit"></ol></section></main><script type="module" src="sidepanel.js"></script></body>
|
|
5
5
|
</html>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// policy.ts
|
|
2
|
-
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts"]);
|
|
2
|
+
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql"]);
|
|
3
3
|
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
|
|
4
|
-
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"]);
|
|
4
|
+
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml"]);
|
|
5
5
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
6
6
|
|
|
7
7
|
// extension/tabscommand.ts
|
|
@@ -64,6 +64,8 @@ var formsroot = document.querySelector("#forms");
|
|
|
64
64
|
var datasetsroot = document.querySelector("#datasets");
|
|
65
65
|
var filesroot = document.querySelector("#files");
|
|
66
66
|
var capturesroot = document.querySelector("#captures");
|
|
67
|
+
var mediaroot = document.querySelector("#media");
|
|
68
|
+
var callsroot = document.querySelector("#calls");
|
|
67
69
|
var statusnode = document.querySelector("#status");
|
|
68
70
|
var progressnode = document.querySelector("#planprogress");
|
|
69
71
|
var capabilitiestext = document.querySelector("#capabilitiestext");
|
|
@@ -1333,6 +1335,255 @@ function opencapture(record) {
|
|
|
1333
1335
|
viewer.append(actions);
|
|
1334
1336
|
capturesroot.append(viewer);
|
|
1335
1337
|
}
|
|
1338
|
+
async function mediabytes(id) {
|
|
1339
|
+
try {
|
|
1340
|
+
const record = await request({ kind: "mediabytes", id });
|
|
1341
|
+
return record.bytes;
|
|
1342
|
+
} catch {
|
|
1343
|
+
return void 0;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
function playframes(frameids, interval, label) {
|
|
1347
|
+
if (!mediaroot) return;
|
|
1348
|
+
const viewer = document.createElement("div");
|
|
1349
|
+
viewer.className = "panel captureviewer";
|
|
1350
|
+
const head = document.createElement("p");
|
|
1351
|
+
head.textContent = `${label}: ${frameids.length} frames at ${interval} millisecond intervals`;
|
|
1352
|
+
viewer.append(head);
|
|
1353
|
+
const image = document.createElement("img");
|
|
1354
|
+
image.alt = "Lapse frame";
|
|
1355
|
+
viewer.append(image);
|
|
1356
|
+
let index = 0;
|
|
1357
|
+
let stopped = false;
|
|
1358
|
+
const show = async () => {
|
|
1359
|
+
if (stopped) return;
|
|
1360
|
+
const bytes = await capturebytes(frameids[index] ?? "");
|
|
1361
|
+
if (bytes) image.src = bytes;
|
|
1362
|
+
index = (index + 1) % Math.max(1, frameids.length);
|
|
1363
|
+
};
|
|
1364
|
+
void show();
|
|
1365
|
+
const timer = window.setInterval(() => {
|
|
1366
|
+
void show();
|
|
1367
|
+
}, Math.max(100, interval));
|
|
1368
|
+
const actions = document.createElement("div");
|
|
1369
|
+
actions.className = "actions";
|
|
1370
|
+
actions.append(button("Stop", async () => {
|
|
1371
|
+
stopped = true;
|
|
1372
|
+
window.clearInterval(timer);
|
|
1373
|
+
viewer.remove();
|
|
1374
|
+
}));
|
|
1375
|
+
viewer.append(actions);
|
|
1376
|
+
mediaroot.append(viewer);
|
|
1377
|
+
}
|
|
1378
|
+
function rendermedia(context) {
|
|
1379
|
+
if (!mediaroot) return;
|
|
1380
|
+
mediaroot.replaceChildren();
|
|
1381
|
+
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
1382
|
+
if ((context.recordingactive ?? []).length > 0) {
|
|
1383
|
+
const indicator = document.createElement("p");
|
|
1384
|
+
indicator.className = "recordingindicator";
|
|
1385
|
+
indicator.textContent = `\u25CF recording in progress: ${context.recordingactive?.map((item) => `${item.kind} of ${item.scope} scope`).join(", ")}`;
|
|
1386
|
+
mediaroot.append(indicator);
|
|
1387
|
+
}
|
|
1388
|
+
for (const consent of (context.recordingconsents ?? []).filter((item) => item.approved === void 0)) {
|
|
1389
|
+
const card = document.createElement("div");
|
|
1390
|
+
card.className = "panel recordingcard";
|
|
1391
|
+
const head = document.createElement("p");
|
|
1392
|
+
head.textContent = `Recording consent ${consent.id} for step ${consent.stepid} on ${consent.origin}: ${consent.prompt}`;
|
|
1393
|
+
card.append(head);
|
|
1394
|
+
const actions = document.createElement("div");
|
|
1395
|
+
actions.className = "actions";
|
|
1396
|
+
actions.append(button("Approve recording", () => request({ kind: "approverecordingconsent", id: consent.id, approved: true }).then(() => status(`Recording consent ${consent.id} approved; rerun the recording step.`)).then(refresh)));
|
|
1397
|
+
actions.append(button("Decline", () => request({ kind: "approverecordingconsent", id: consent.id, approved: false }).then(() => status(`Recording consent ${consent.id} declined.`)).then(refresh)));
|
|
1398
|
+
card.append(actions);
|
|
1399
|
+
mediaroot.append(card);
|
|
1400
|
+
}
|
|
1401
|
+
const records = context.media ?? [];
|
|
1402
|
+
const pdfs = records.filter((record) => record.pages !== void 0);
|
|
1403
|
+
const recordings = records.filter((record) => record.startedat !== void 0);
|
|
1404
|
+
const frames = records.filter((record) => record.timestamp !== void 0);
|
|
1405
|
+
const canvases = records.filter((record) => record.context !== void 0);
|
|
1406
|
+
const streams = records.filter((record) => record.tracks !== void 0);
|
|
1407
|
+
const assets = records.filter((record) => record.url !== void 0 && record.kind !== void 0 && (record.kind === "favicon" || record.kind === "logo"));
|
|
1408
|
+
if (pdfs.length > 0) {
|
|
1409
|
+
const card = document.createElement("div");
|
|
1410
|
+
card.className = "panel";
|
|
1411
|
+
const head = document.createElement("p");
|
|
1412
|
+
head.textContent = `pdf reports: ${pdfs.length}`;
|
|
1413
|
+
card.append(head);
|
|
1414
|
+
for (const pdf of pdfs) {
|
|
1415
|
+
const row = document.createElement("p");
|
|
1416
|
+
row.textContent = `${String(pdf.name ?? pdf.id)} \xB7 ${String(pdf.pages)} page${String(pdf.pages) === "1" ? "" : "s"} \xB7 ${String(pdf.pagewidth)}\xD7${String(pdf.pageheight)} pt${pdf.landscape === true ? " \xB7 landscape" : ""} \xB7 ${String(pdf.bytes)} bytes${pdf.bytesexpired === true ? " \xB7 bytes expired" : ""}`;
|
|
1417
|
+
card.append(row);
|
|
1418
|
+
const actions = document.createElement("div");
|
|
1419
|
+
actions.className = "actions";
|
|
1420
|
+
actions.append(button("Download pdf", () => request({ kind: "downloadmedia", id: String(pdf.id) }).then(() => status(`Downloaded the pdf report ${String(pdf.name ?? pdf.id)} through the reviewed download flow.`)), !active || pdf.bytesexpired === true));
|
|
1421
|
+
card.append(actions);
|
|
1422
|
+
}
|
|
1423
|
+
mediaroot.append(card);
|
|
1424
|
+
}
|
|
1425
|
+
for (const batch of context.imagebatches ?? []) {
|
|
1426
|
+
const card = document.createElement("div");
|
|
1427
|
+
card.className = "panel";
|
|
1428
|
+
const head = document.createElement("p");
|
|
1429
|
+
head.textContent = `image batch of step ${batch.stepid}: ${batch.matched} of ${batch.images.length} observed images matched, ${batch.downloaded} downloaded`;
|
|
1430
|
+
card.append(head);
|
|
1431
|
+
const list = document.createElement("ol");
|
|
1432
|
+
list.className = "audit";
|
|
1433
|
+
for (const image of batch.images.slice(0, 12)) {
|
|
1434
|
+
const entry = document.createElement("li");
|
|
1435
|
+
entry.textContent = `${image.url}${image.alt ? ` \xB7 ${image.alt}` : ""} \xB7 ${image.width}\xD7${image.height} \xB7 ${image.bytes} bytes \xB7 ${image.mime}`;
|
|
1436
|
+
list.append(entry);
|
|
1437
|
+
}
|
|
1438
|
+
card.append(list);
|
|
1439
|
+
mediaroot.append(card);
|
|
1440
|
+
}
|
|
1441
|
+
if (frames.length > 0) {
|
|
1442
|
+
const card = document.createElement("div");
|
|
1443
|
+
card.className = "panel";
|
|
1444
|
+
const head = document.createElement("p");
|
|
1445
|
+
head.textContent = `video frames: ${frames.length}`;
|
|
1446
|
+
card.append(head);
|
|
1447
|
+
const grid = document.createElement("div");
|
|
1448
|
+
grid.className = "capturegrid";
|
|
1449
|
+
for (const frame of frames) {
|
|
1450
|
+
const cell = document.createElement("button");
|
|
1451
|
+
cell.type = "button";
|
|
1452
|
+
cell.className = "capturecard";
|
|
1453
|
+
const label = document.createElement("span");
|
|
1454
|
+
label.textContent = `${String(frame.source)} \xB7 ${String(frame.timestamp)}s${frame.poster === true ? " \xB7 poster" : ""}${frame.bytesexpired === true ? " \xB7 bytes expired" : ""}`;
|
|
1455
|
+
cell.append(label);
|
|
1456
|
+
const image = document.createElement("img");
|
|
1457
|
+
image.alt = `Video frame ${String(frame.id)}`;
|
|
1458
|
+
if (frame.bytesexpired !== true) void mediabytes(String(frame.id)).then((bytes) => {
|
|
1459
|
+
if (bytes) image.src = bytes;
|
|
1460
|
+
});
|
|
1461
|
+
cell.append(image);
|
|
1462
|
+
grid.append(cell);
|
|
1463
|
+
}
|
|
1464
|
+
card.append(grid);
|
|
1465
|
+
mediaroot.append(card);
|
|
1466
|
+
}
|
|
1467
|
+
const timelapse = (context.captures ?? []).filter((record) => record.kind === "timelapse");
|
|
1468
|
+
if (timelapse.length > 1) {
|
|
1469
|
+
const card = document.createElement("div");
|
|
1470
|
+
card.className = "panel";
|
|
1471
|
+
const head = document.createElement("p");
|
|
1472
|
+
head.textContent = `time lapse sequences: ${timelapse.length} frames`;
|
|
1473
|
+
card.append(head);
|
|
1474
|
+
card.append(button("Play lapse sequence", async () => {
|
|
1475
|
+
const ordered = await request({ kind: "capturereport" });
|
|
1476
|
+
const ids = ordered.records.filter((record) => record.kind === "timelapse").sort((left, right) => left.capturedat - right.capturedat).map((record) => record.id);
|
|
1477
|
+
playframes(ids, 800, "time lapse");
|
|
1478
|
+
}));
|
|
1479
|
+
mediaroot.append(card);
|
|
1480
|
+
}
|
|
1481
|
+
if (canvases.length > 0) {
|
|
1482
|
+
const card = document.createElement("div");
|
|
1483
|
+
card.className = "panel";
|
|
1484
|
+
const head = document.createElement("p");
|
|
1485
|
+
head.textContent = `canvas captures: ${canvases.length}`;
|
|
1486
|
+
card.append(head);
|
|
1487
|
+
const grid = document.createElement("div");
|
|
1488
|
+
grid.className = "capturegrid";
|
|
1489
|
+
for (const canvas of canvases) {
|
|
1490
|
+
const cell = document.createElement("button");
|
|
1491
|
+
cell.type = "button";
|
|
1492
|
+
cell.className = "capturecard";
|
|
1493
|
+
const label = document.createElement("span");
|
|
1494
|
+
label.textContent = `${String(canvas.element)} \xB7 ${String(canvas.context)} \xB7 ${String(canvas.width)}\xD7${String(canvas.height)}${canvas.bytesexpired === true ? " \xB7 bytes expired" : ""}`;
|
|
1495
|
+
cell.append(label);
|
|
1496
|
+
const image = document.createElement("img");
|
|
1497
|
+
image.alt = `Canvas capture ${String(canvas.id)}`;
|
|
1498
|
+
if (canvas.bytesexpired !== true) void mediabytes(String(canvas.id)).then((bytes) => {
|
|
1499
|
+
if (bytes) image.src = bytes;
|
|
1500
|
+
});
|
|
1501
|
+
cell.append(image);
|
|
1502
|
+
grid.append(cell);
|
|
1503
|
+
}
|
|
1504
|
+
card.append(grid);
|
|
1505
|
+
mediaroot.append(card);
|
|
1506
|
+
}
|
|
1507
|
+
for (const stream of streams) {
|
|
1508
|
+
const card = document.createElement("div");
|
|
1509
|
+
card.className = "panel";
|
|
1510
|
+
const head = document.createElement("p");
|
|
1511
|
+
head.textContent = `stream probe ${String(stream.label || stream.id)}: ${String(stream.tracks)} track${String(stream.tracks) === "1" ? "" : "s"} \xB7 ${stream.live === true ? "live" : "ended"}`;
|
|
1512
|
+
card.append(head);
|
|
1513
|
+
const detail = stream.detail;
|
|
1514
|
+
if (Array.isArray(detail)) {
|
|
1515
|
+
const list = document.createElement("ul");
|
|
1516
|
+
list.className = "audit";
|
|
1517
|
+
for (const track of detail) {
|
|
1518
|
+
const entry = document.createElement("li");
|
|
1519
|
+
entry.textContent = `${track.kind} track${track.label ? ` ${track.label}` : ""}${track.width !== void 0 ? ` \xB7 ${track.width}\xD7${track.height}` : ""}${track.framerate !== void 0 ? ` \xB7 ${Math.round(track.framerate)} fps` : ""} \xB7 ${track.state}`;
|
|
1520
|
+
list.append(entry);
|
|
1521
|
+
}
|
|
1522
|
+
card.append(list);
|
|
1523
|
+
}
|
|
1524
|
+
mediaroot.append(card);
|
|
1525
|
+
}
|
|
1526
|
+
if (assets.length > 0) {
|
|
1527
|
+
const card = document.createElement("div");
|
|
1528
|
+
card.className = "panel";
|
|
1529
|
+
const head = document.createElement("p");
|
|
1530
|
+
head.textContent = `page assets: ${assets.filter((asset) => asset.kind === "favicon").length} favicon and ${assets.filter((asset) => asset.kind === "logo").length} logo entries`;
|
|
1531
|
+
card.append(head);
|
|
1532
|
+
const list = document.createElement("ul");
|
|
1533
|
+
list.className = "audit";
|
|
1534
|
+
for (const asset of assets.slice(0, 12)) {
|
|
1535
|
+
const entry = document.createElement("li");
|
|
1536
|
+
entry.textContent = `${String(asset.kind)} \xB7 ${String(asset.url)}${asset.sizes !== void 0 ? ` \xB7 ${String(asset.sizes)}` : ""}`;
|
|
1537
|
+
list.append(entry);
|
|
1538
|
+
}
|
|
1539
|
+
card.append(list);
|
|
1540
|
+
mediaroot.append(card);
|
|
1541
|
+
}
|
|
1542
|
+
for (const recording of recordings) {
|
|
1543
|
+
const card = document.createElement("div");
|
|
1544
|
+
card.className = "panel recordingcard";
|
|
1545
|
+
const head = document.createElement("p");
|
|
1546
|
+
head.textContent = `${String(recording.kind)} recording ${String(recording.id)} \xB7 ${String(recording.scope)} scope \xB7 ${String(recording.duration ?? 0)} ms \xB7 ${Array.isArray(recording.frames) ? String(recording.frames.length) : "0"} frames \xB7 manifest ${String(recording.bytes ?? 0)} bytes${recording.bytesexpired === true ? " \xB7 bytes expired" : ""}`;
|
|
1547
|
+
card.append(head);
|
|
1548
|
+
const actions = document.createElement("div");
|
|
1549
|
+
actions.className = "actions";
|
|
1550
|
+
const frames2 = Array.isArray(recording.frames) ? recording.frames : [];
|
|
1551
|
+
if (String(recording.kind) === "screen" && frames2.length > 0) {
|
|
1552
|
+
actions.append(button("Play frames", async () => {
|
|
1553
|
+
const state = await request({ kind: "recordingframes", id: String(recording.id) });
|
|
1554
|
+
playframes(state.frames, state.interval, `screen recording ${String(recording.id)}`);
|
|
1555
|
+
}));
|
|
1556
|
+
}
|
|
1557
|
+
actions.append(button("Download manifest", () => request({ kind: "downloadrecording", id: String(recording.id) }).then(() => status(`Downloaded the recording manifest ${String(recording.id)} through the reviewed download flow.`)), !active));
|
|
1558
|
+
actions.append(button("Delete", () => request({ kind: "deleterecording", id: String(recording.id) }).then(() => status(`Deleted the recording ${String(recording.id)}.`)).then(refresh), !active));
|
|
1559
|
+
card.append(actions);
|
|
1560
|
+
mediaroot.append(card);
|
|
1561
|
+
}
|
|
1562
|
+
const stored = (context.captures ?? []).filter((record) => record.bytesexpired !== true).slice(0, 12);
|
|
1563
|
+
if (stored.length > 0) {
|
|
1564
|
+
const card = document.createElement("div");
|
|
1565
|
+
card.className = "panel";
|
|
1566
|
+
const head = document.createElement("p");
|
|
1567
|
+
head.textContent = "capture transforms: convertimage and makethumbs actions on stored captures";
|
|
1568
|
+
card.append(head);
|
|
1569
|
+
for (const record of stored) {
|
|
1570
|
+
const row = document.createElement("div");
|
|
1571
|
+
row.className = "actions";
|
|
1572
|
+
const label = document.createElement("p");
|
|
1573
|
+
label.textContent = `${record.kind} ${record.id} (${record.format})`;
|
|
1574
|
+
row.append(label);
|
|
1575
|
+
for (const target of ["png", "jpeg", "webp"]) {
|
|
1576
|
+
if (target !== record.format) row.append(button(`\u2192 ${target}`, () => request({ kind: "convertcapture", id: record.id, target }).then(() => status(`Converted capture ${record.id} to ${target}.`)).then(refresh), !active));
|
|
1577
|
+
}
|
|
1578
|
+
row.append(button("thumbnail", () => request({ kind: "thumbcapture", id: record.id, size: 240, fit: "cover", suffix: "thumb" }).then(() => status(`Thumbnailed capture ${record.id}.`)).then(refresh), !active));
|
|
1579
|
+
card.append(row);
|
|
1580
|
+
}
|
|
1581
|
+
mediaroot.append(card);
|
|
1582
|
+
}
|
|
1583
|
+
if (records.length === 0 && (context.imagebatches ?? []).length === 0 && (context.recordingactive ?? []).length === 0) {
|
|
1584
|
+
mediaroot.append(Object.assign(document.createElement("p"), { textContent: "No media stored yet; run a capturepdf, recordscreen, captureaudio, captureframe, downloadimages, shotcanvas, probestream, readmedia, readassets, timelapse, convertimage or makethumbs step." }));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1336
1587
|
function rendercaptures(context) {
|
|
1337
1588
|
if (!capturesroot) return;
|
|
1338
1589
|
capturesroot.replaceChildren();
|
|
@@ -1437,6 +1688,151 @@ function rendercaptures(context) {
|
|
|
1437
1688
|
capturesroot.append(card);
|
|
1438
1689
|
}
|
|
1439
1690
|
}
|
|
1691
|
+
function rendercalls(context) {
|
|
1692
|
+
if (!callsroot) return;
|
|
1693
|
+
callsroot.replaceChildren();
|
|
1694
|
+
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
1695
|
+
for (const consent of (context.fetchconsents ?? []).filter((item) => item.approved === void 0)) {
|
|
1696
|
+
const card = document.createElement("div");
|
|
1697
|
+
card.className = "panel consentcard";
|
|
1698
|
+
const head = document.createElement("p");
|
|
1699
|
+
head.textContent = `Fetch consent ${consent.id} for ${consent.origin}: review every custom header before it is sent.`;
|
|
1700
|
+
card.append(head);
|
|
1701
|
+
for (const header of consent.headers) {
|
|
1702
|
+
const row = document.createElement("p");
|
|
1703
|
+
row.textContent = `${header.name}: ${header.value}`;
|
|
1704
|
+
card.append(row);
|
|
1705
|
+
}
|
|
1706
|
+
const note = document.createElement("p");
|
|
1707
|
+
note.className = "muted";
|
|
1708
|
+
note.textContent = `The prompt appears once per origin and expires ${new Date(consent.expiresat).toLocaleTimeString()}; header values never enter the audit trail.`;
|
|
1709
|
+
card.append(note);
|
|
1710
|
+
const actions2 = document.createElement("div");
|
|
1711
|
+
actions2.className = "actions";
|
|
1712
|
+
actions2.append(button("Approve headers", () => request({ kind: "approvefetchconsent", id: consent.id, approved: true }).then(() => status(`Fetch consent ${consent.id} approved; rerun the fetch step.`)).then(refresh)));
|
|
1713
|
+
actions2.append(button("Decline", () => request({ kind: "approvefetchconsent", id: consent.id, approved: false }).then(() => status(`Fetch consent ${consent.id} declined.`)).then(refresh)));
|
|
1714
|
+
card.append(actions2);
|
|
1715
|
+
callsroot.append(card);
|
|
1716
|
+
}
|
|
1717
|
+
const endpoints = context.endpoints ?? [];
|
|
1718
|
+
if (endpoints.length > 0) {
|
|
1719
|
+
const card = document.createElement("div");
|
|
1720
|
+
card.className = "panel";
|
|
1721
|
+
const head = document.createElement("p");
|
|
1722
|
+
head.textContent = `typed endpoints: ${endpoints.length}`;
|
|
1723
|
+
card.append(head);
|
|
1724
|
+
for (const endpoint of endpoints) {
|
|
1725
|
+
const row = document.createElement("p");
|
|
1726
|
+
row.textContent = `${endpoint.name} \xB7 ${endpoint.method} ${endpoint.url} \xB7 v${endpoint.version} \xB7 ${endpoint.schema?.fields.length ?? 0} payload field${endpoint.schema?.fields.length === 1 ? "" : "s"}${endpoint.headers !== void 0 ? ` \xB7 ${Object.keys(endpoint.headers).length} reviewed header${Object.keys(endpoint.headers).length === 1 ? "" : "s"}` : ""}`;
|
|
1727
|
+
card.append(row);
|
|
1728
|
+
}
|
|
1729
|
+
callsroot.append(card);
|
|
1730
|
+
}
|
|
1731
|
+
const apikeys = context.apikeys ?? [];
|
|
1732
|
+
if (apikeys.length > 0) {
|
|
1733
|
+
const card = document.createElement("div");
|
|
1734
|
+
card.className = "panel";
|
|
1735
|
+
const head = document.createElement("p");
|
|
1736
|
+
head.textContent = `api key references: ${apikeys.length} (secrets never listed)`;
|
|
1737
|
+
card.append(head);
|
|
1738
|
+
for (const ref of apikeys) {
|
|
1739
|
+
const row = document.createElement("p");
|
|
1740
|
+
row.textContent = `${ref.name} \xB7 header ${ref.header} \xB7 scoped to ${ref.origins.join(", ")}`;
|
|
1741
|
+
card.append(row);
|
|
1742
|
+
const actions2 = document.createElement("div");
|
|
1743
|
+
actions2.className = "actions";
|
|
1744
|
+
actions2.append(button(`Remove ${ref.name}`, () => request({ kind: "deleteapikey", name: ref.name }).then(() => status(`Api key reference ${ref.name} removed.`)).then(refresh)));
|
|
1745
|
+
card.append(actions2);
|
|
1746
|
+
}
|
|
1747
|
+
callsroot.append(card);
|
|
1748
|
+
}
|
|
1749
|
+
const callsmeta = context.calls ?? [];
|
|
1750
|
+
const filters = document.createElement("div");
|
|
1751
|
+
filters.className = "actions";
|
|
1752
|
+
const originfilter = document.createElement("input");
|
|
1753
|
+
originfilter.placeholder = "filter by origin";
|
|
1754
|
+
const methodfilter = document.createElement("input");
|
|
1755
|
+
methodfilter.placeholder = "filter by method";
|
|
1756
|
+
const classfilter = document.createElement("input");
|
|
1757
|
+
classfilter.placeholder = "filter by status class";
|
|
1758
|
+
filters.append(originfilter, methodfilter, classfilter);
|
|
1759
|
+
callsroot.append(filters);
|
|
1760
|
+
const list = document.createElement("div");
|
|
1761
|
+
const applyfilters = () => {
|
|
1762
|
+
list.replaceChildren();
|
|
1763
|
+
const origin = originfilter.value.trim().toLowerCase();
|
|
1764
|
+
const method = methodfilter.value.trim().toUpperCase();
|
|
1765
|
+
const statusclass = classfilter.value.trim().toLowerCase();
|
|
1766
|
+
const matched = callsmeta.filter((call) => (!origin || call.origin.toLowerCase().includes(origin)) && (!method || call.method.toUpperCase().includes(method)) && (!statusclass || call.statusclass.includes(statusclass)));
|
|
1767
|
+
if (matched.length === 0) {
|
|
1768
|
+
list.textContent = "No outbound call matches the filters yet.";
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
for (const call of matched) {
|
|
1772
|
+
const card = document.createElement("details");
|
|
1773
|
+
card.className = "panel callcard";
|
|
1774
|
+
const summary = document.createElement("summary");
|
|
1775
|
+
const credential = call.headernames.some((name) => name.toLowerCase().startsWith("apikey:") || ["authorization", "cookie", "proxy-authorization", "api-key", "x-api-key", "x-auth-token"].includes(name.toLowerCase()));
|
|
1776
|
+
summary.textContent = `${call.method} ${call.kind} ${call.status} ${call.statusclass} \xB7 ${new URL(call.url).host} \xB7 ${call.duration} ms \xB7 ${call.retries} retr${call.retries === 1 ? "y" : "ies"} \xB7 ${call.bytes} bytes \xB7 step ${call.stepid}`;
|
|
1777
|
+
if (credential) {
|
|
1778
|
+
const badge = document.createElement("span");
|
|
1779
|
+
badge.className = "credentialbadge";
|
|
1780
|
+
badge.textContent = " credential call ";
|
|
1781
|
+
summary.append(badge);
|
|
1782
|
+
}
|
|
1783
|
+
card.append(summary);
|
|
1784
|
+
const urlrow = document.createElement("p");
|
|
1785
|
+
urlrow.textContent = `url: ${call.url}${call.endpoint !== void 0 ? ` \xB7 endpoint ${call.endpoint}` : ""}${call.bodyexpired === true ? " \xB7 body expired from retention" : ""}`;
|
|
1786
|
+
card.append(urlrow);
|
|
1787
|
+
const headersrow = document.createElement("p");
|
|
1788
|
+
headersrow.textContent = `request header names: ${call.headernames.length > 0 ? call.headernames.join(", ") : "none"} (values never persist)`;
|
|
1789
|
+
card.append(headersrow);
|
|
1790
|
+
if ((call.fields ?? []).length > 0) {
|
|
1791
|
+
const fieldstitle = document.createElement("p");
|
|
1792
|
+
fieldstitle.textContent = `parsed fields: ${(call.fields ?? []).length}`;
|
|
1793
|
+
card.append(fieldstitle);
|
|
1794
|
+
for (const field of call.fields ?? []) {
|
|
1795
|
+
const fieldrow = document.createElement("p");
|
|
1796
|
+
fieldrow.textContent = `${field.name} (${field.kind}) from ${field.path}: ${field.missing === true ? "miss filled by the reviewed default" : JSON.stringify(field.value)}`;
|
|
1797
|
+
card.append(fieldrow);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
if (call.streambytes !== void 0) {
|
|
1801
|
+
const streamrow = document.createElement("p");
|
|
1802
|
+
streamrow.textContent = `streamed ${call.streambytes} bytes of the response body`;
|
|
1803
|
+
card.append(streamrow);
|
|
1804
|
+
const gauge = document.createElement("progress");
|
|
1805
|
+
gauge.max = Math.max(call.streambytes, call.bytes);
|
|
1806
|
+
gauge.value = call.streambytes;
|
|
1807
|
+
card.append(gauge);
|
|
1808
|
+
}
|
|
1809
|
+
for (const error of call.errors ?? []) {
|
|
1810
|
+
const errorrow = document.createElement("p");
|
|
1811
|
+
errorrow.className = "diffrow.removed";
|
|
1812
|
+
errorrow.textContent = `error of step ${call.stepid}: ${error}`;
|
|
1813
|
+
card.append(errorrow);
|
|
1814
|
+
}
|
|
1815
|
+
list.append(card);
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
originfilter.addEventListener("input", applyfilters);
|
|
1819
|
+
methodfilter.addEventListener("input", applyfilters);
|
|
1820
|
+
classfilter.addEventListener("input", applyfilters);
|
|
1821
|
+
applyfilters();
|
|
1822
|
+
callsroot.append(list);
|
|
1823
|
+
const actions = document.createElement("div");
|
|
1824
|
+
actions.className = "actions";
|
|
1825
|
+
actions.append(button(`Export ${callsmeta.length} call${callsmeta.length === 1 ? "" : "s"}`, () => request({ kind: "exportcalls" }).then(() => status(`Exported ${callsmeta.length} call record${callsmeta.length === 1 ? "" : "s"} through the reviewed download flow.`)).catch((error) => status(error instanceof Error ? error.message : String(error), true))));
|
|
1826
|
+
if (active) {
|
|
1827
|
+
const retentioninput = document.createElement("input");
|
|
1828
|
+
retentioninput.placeholder = "call body retention (records)";
|
|
1829
|
+
retentioninput.value = context.callretention !== void 0 ? String(context.callretention) : "";
|
|
1830
|
+
retentioninput.setAttribute("aria-label", "call body retention window");
|
|
1831
|
+
const retentionbutton = button("Save call retention", () => request({ kind: "setcallretention", retention: retentioninput.value === "" ? void 0 : Number(retentioninput.value) }).then(() => status(`Call body retention saved as ${retentioninput.value === "" ? "keep every body" : retentioninput.value} records; metadata always survives.`)).then(refresh));
|
|
1832
|
+
actions.append(retentioninput, retentionbutton);
|
|
1833
|
+
}
|
|
1834
|
+
callsroot.append(actions);
|
|
1835
|
+
}
|
|
1440
1836
|
function rendercapabilities(report) {
|
|
1441
1837
|
if (!capabilitiestext) return;
|
|
1442
1838
|
if (!report) {
|
|
@@ -1487,6 +1883,8 @@ async function refresh() {
|
|
|
1487
1883
|
renderdatasets(context);
|
|
1488
1884
|
renderfiles(context);
|
|
1489
1885
|
rendercaptures(context);
|
|
1886
|
+
rendermedia(context);
|
|
1887
|
+
rendercalls(context);
|
|
1490
1888
|
renderaudit(context.audit);
|
|
1491
1889
|
rendercapabilities(context.capabilities);
|
|
1492
1890
|
if (context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
|