@wenathlan/extension 1.1.47 → 1.1.49
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/emulation.d.ts +89 -0
- package/dist/emulation.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +886 -4
- package/dist/index.js.map +4 -4
- package/dist/memory.d.ts +83 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +51 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +65 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/sessions.d.ts +79 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/types.d.ts +250 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1413 -8
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +288 -5
- package/extension/dist/pagebridge.js.map +3 -3
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +24 -2
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +375 -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>Media</h2><div id="media" class="panel"></div></section><section><h2>Network calls</h2><div id="calls" class="panel"></div></section><section><h2>Network view</h2><div id="netview" class="panel"></div></section><section><h2>Traffic control</h2><div id="traffic" class="panel"></div></section><section><h2>Run timeline</h2><div id="timeline" class="panel"></div></section><section><h2>Debugger</h2><div id="debugger" class="panel"></div></section><section><h2>Profiling</h2><div id="profiling" class="panel"></div></section><section><h2>Console diff</h2><div id="consolediff" 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>Network view</h2><div id="netview" class="panel"></div></section><section><h2>Traffic control</h2><div id="traffic" class="panel"></div></section><section><h2>Run timeline</h2><div id="timeline" class="panel"></div></section><section><h2>Debugger</h2><div id="debugger" class="panel"></div></section><section><h2>Profiling</h2><div id="profiling" class="panel"></div></section><section><h2>Emulation</h2><div id="emulation" class="panel"></div></section><section><h2>Sessions</h2><div id="sessions" class="panel"></div></section><section><h2>Console diff</h2><div id="consolediff" 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", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps"]);
|
|
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", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "restoresession", "exportsessions", "importsessions"]);
|
|
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", "capturebodies", "setbreakpoint", "stepcode", "watchexpr"]);
|
|
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", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace"]);
|
|
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", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace", "blackboxscripts", "persiststate", "capturesession", "namedsessions", "diffsessions", "searchsessions"]);
|
|
5
5
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
6
6
|
|
|
7
7
|
// extension/tabscommand.ts
|
|
@@ -71,7 +71,10 @@ var timelineroot = document.querySelector("#timeline");
|
|
|
71
71
|
var consolediffroot = document.querySelector("#consolediff");
|
|
72
72
|
var debuggerroot = document.querySelector("#debugger");
|
|
73
73
|
var profilingroot = document.querySelector("#profiling");
|
|
74
|
+
var emulationroot = document.querySelector("#emulation");
|
|
74
75
|
var netviewroot = document.querySelector("#netview");
|
|
76
|
+
var sessionsroot = document.querySelector("#sessions");
|
|
77
|
+
var sessionsview = { term: "", window: "all", diffselection: [], restorereview: void 0, importreview: void 0 };
|
|
75
78
|
var statusnode = document.querySelector("#status");
|
|
76
79
|
var progressnode = document.querySelector("#planprogress");
|
|
77
80
|
var capabilitiestext = document.querySelector("#capabilitiestext");
|
|
@@ -2347,6 +2350,164 @@ function renderdebugger(context) {
|
|
|
2347
2350
|
ceilinginput.value = context.breakpointceiling !== void 0 ? String(context.breakpointceiling) : "";
|
|
2348
2351
|
debuggerroot.append(settingsline, retentioninput, ceilinginput, button("Save debugger settings", () => Promise.all([request({ kind: "setpauseretention", retention: retentioninput.value === "" ? void 0 : Number(retentioninput.value) }), request({ kind: "setbreakpointceiling", ceiling: ceilinginput.value === "" ? void 0 : Number(ceilinginput.value) })]).then(() => status(`Debugger settings saved: pause retention ${retentioninput.value === "" ? "keeps every capture" : retentioninput.value} and breakpoint ceiling ${ceilinginput.value === "" ? "none" : ceilinginput.value}.`)).then(refresh)));
|
|
2349
2352
|
}
|
|
2353
|
+
function renderemulation(context) {
|
|
2354
|
+
if (!emulationroot) return;
|
|
2355
|
+
emulationroot.replaceChildren();
|
|
2356
|
+
const emulation = context.emulation;
|
|
2357
|
+
if (!emulation) {
|
|
2358
|
+
emulationroot.textContent = "No emulation state is available yet; start a session and review a plan with emulation steps.";
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
const pending = emulation.consents.filter((consent) => consent.approved === void 0 && consent.revokedat === void 0);
|
|
2362
|
+
for (const consent of pending) {
|
|
2363
|
+
const card = document.createElement("div");
|
|
2364
|
+
card.className = "bannercard";
|
|
2365
|
+
const title = document.createElement("p");
|
|
2366
|
+
title.textContent = `Location consent needed on ${consent.origin}`;
|
|
2367
|
+
const coordinates = document.createElement("p");
|
|
2368
|
+
coordinates.textContent = `Reviewed coordinates: ${consent.latitude}, ${consent.longitude}; the override applies through a page-injected geolocation mask and the true browser location stays untouched.`;
|
|
2369
|
+
card.append(title, coordinates, button("Approve location consent", () => request({ kind: "approvelocationconsent", id: consent.id }).then(() => {
|
|
2370
|
+
status(`Approved the location consent for ${consent.latitude}, ${consent.longitude} on ${consent.origin}.`);
|
|
2371
|
+
return refresh();
|
|
2372
|
+
})));
|
|
2373
|
+
emulationroot.append(card);
|
|
2374
|
+
}
|
|
2375
|
+
const active = emulation.layers.filter((layer) => layer.revertedat === void 0);
|
|
2376
|
+
const state = document.createElement("p");
|
|
2377
|
+
state.textContent = `${active.length} active emulation layer${active.length === 1 ? "" : "s"} of run ${active[0]?.runid ?? "none"}${active.length > 1 ? "; the last applied layer wins conflicts" : ""}.`;
|
|
2378
|
+
emulationroot.append(state);
|
|
2379
|
+
if (active.length > 1) {
|
|
2380
|
+
const warn = document.createElement("p");
|
|
2381
|
+
warn.textContent = `Warning: ${active.length} layers stack on one tab (${active.map((layer) => layer.name).join(", ")}); every mask reverts at run end in reverse order.`;
|
|
2382
|
+
warn.className = "muted";
|
|
2383
|
+
emulationroot.append(warn);
|
|
2384
|
+
}
|
|
2385
|
+
const offlinelayer = active.find((layer) => layer.family === "network" && emulation.networks.some((preset) => preset.name === layer.name && preset.offline));
|
|
2386
|
+
if (offlinelayer !== void 0) {
|
|
2387
|
+
const offline = document.createElement("p");
|
|
2388
|
+
offline.textContent = `Offline: the network layer ${offlinelayer.name} cuts the traffic the extension initiates; page traffic stays observed only.`;
|
|
2389
|
+
offline.className = "muted";
|
|
2390
|
+
emulationroot.append(offline);
|
|
2391
|
+
}
|
|
2392
|
+
for (const layer of active) {
|
|
2393
|
+
const row = document.createElement("div");
|
|
2394
|
+
row.className = "emulationrow";
|
|
2395
|
+
row.dataset.stacked = active.length > 1 ? "true" : "false";
|
|
2396
|
+
const line = document.createElement("p");
|
|
2397
|
+
line.textContent = `${layer.family} layer ${layer.name} on ${layer.originscope} applied ${new Date(layer.appliedat).toLocaleTimeString()} with the revert plan ${layer.revertplan.join(", ")}.`;
|
|
2398
|
+
row.append(line, button("Revert now", () => request({ kind: "revertemulation" }).then(() => {
|
|
2399
|
+
status("Reverted every active emulation layer of the run on review panel demand.");
|
|
2400
|
+
return refresh();
|
|
2401
|
+
})));
|
|
2402
|
+
emulationroot.append(row);
|
|
2403
|
+
}
|
|
2404
|
+
if (active.length > 0) {
|
|
2405
|
+
emulationroot.append(button("Restore stored layers after a crash", () => request({ kind: "restoreemulation" }).then(() => {
|
|
2406
|
+
status("Restored the stored emulation layers of the run record.");
|
|
2407
|
+
return refresh();
|
|
2408
|
+
})));
|
|
2409
|
+
}
|
|
2410
|
+
const families = [
|
|
2411
|
+
{ label: "device presets", presets: emulation.devices.map((preset) => ({ label: `${preset.name}: ${preset.width}x${preset.height} @${preset.pixelratio}${preset.mobile ? " mobile" : ""}`, kind: "device" })) },
|
|
2412
|
+
{ label: "network presets", presets: emulation.networks.map((preset) => ({ label: `${preset.name}: ${preset.latency}ms, ${preset.download}/${preset.upload} kbps${preset.offline ? ", offline" : ""}`, kind: "network" })) },
|
|
2413
|
+
{ label: "location presets", presets: emulation.locations.map((preset) => ({ label: `${preset.name}: ${preset.latitude}, ${preset.longitude} \xB1${preset.accuracy}m`, kind: "location" })) },
|
|
2414
|
+
{ label: "agent presets", presets: emulation.agents.map((preset) => ({ label: `${preset.name}: ${preset.platform} with ${preset.brands.length} brands`, kind: "agent" })) }
|
|
2415
|
+
];
|
|
2416
|
+
for (const family of families) {
|
|
2417
|
+
const head = document.createElement("h4");
|
|
2418
|
+
head.textContent = family.label;
|
|
2419
|
+
emulationroot.append(head);
|
|
2420
|
+
if (family.presets.length === 0) {
|
|
2421
|
+
const empty = document.createElement("p");
|
|
2422
|
+
empty.className = "muted";
|
|
2423
|
+
empty.textContent = "No user curated preset yet; the library stays user data instead of a hardcoded list.";
|
|
2424
|
+
emulationroot.append(empty);
|
|
2425
|
+
continue;
|
|
2426
|
+
}
|
|
2427
|
+
for (const preset of family.presets) {
|
|
2428
|
+
const line = document.createElement("p");
|
|
2429
|
+
line.textContent = preset.label;
|
|
2430
|
+
emulationroot.append(line);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
const editor = document.createElement("details");
|
|
2434
|
+
const summary = document.createElement("summary");
|
|
2435
|
+
summary.textContent = "Preset editor";
|
|
2436
|
+
editor.append(summary);
|
|
2437
|
+
const nameinput = document.createElement("input");
|
|
2438
|
+
nameinput.placeholder = "preset name";
|
|
2439
|
+
const valueinput = document.createElement("input");
|
|
2440
|
+
valueinput.placeholder = 'preset json, for example {"name":"phone","width":390,"height":844,"pixelratio":3,"mobile":true}';
|
|
2441
|
+
const familyselect = document.createElement("select");
|
|
2442
|
+
for (const family of ["device", "network", "location", "agent"]) {
|
|
2443
|
+
const option = document.createElement("option");
|
|
2444
|
+
option.value = family;
|
|
2445
|
+
option.textContent = family;
|
|
2446
|
+
familyselect.append(option);
|
|
2447
|
+
}
|
|
2448
|
+
editor.append(nameinput, valueinput, familyselect, button("Save preset", async () => {
|
|
2449
|
+
let payload;
|
|
2450
|
+
try {
|
|
2451
|
+
payload = JSON.parse(valueinput.value);
|
|
2452
|
+
} catch {
|
|
2453
|
+
throw new Error("The preset payload must be reviewed JSON.");
|
|
2454
|
+
}
|
|
2455
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload) && nameinput.value.trim()) payload = { ...payload, name: nameinput.value.trim() };
|
|
2456
|
+
const kind = familyselect.value === "device" ? "setdevicepreset" : familyselect.value === "network" ? "setnetworkpreset" : familyselect.value === "location" ? "setlocationpreset" : "setagentpreset";
|
|
2457
|
+
const field = familyselect.value;
|
|
2458
|
+
await request({ kind, [field]: payload });
|
|
2459
|
+
status(`Stored the ${familyselect.value} preset in the user curated library.`);
|
|
2460
|
+
await refresh();
|
|
2461
|
+
}));
|
|
2462
|
+
const exportrow = document.createElement("div");
|
|
2463
|
+
exportrow.className = "actions";
|
|
2464
|
+
exportrow.append(button("Export preset file", () => request({ kind: "exportpresets" }).then((value) => {
|
|
2465
|
+
const parsed = value;
|
|
2466
|
+
status(`Exported ${parsed.exported} preset${parsed.exported === 1 ? "" : "s"} through the reviewed download flow.`);
|
|
2467
|
+
return refresh();
|
|
2468
|
+
})));
|
|
2469
|
+
const importinput = document.createElement("input");
|
|
2470
|
+
importinput.type = "file";
|
|
2471
|
+
importinput.accept = "application/json";
|
|
2472
|
+
importinput.addEventListener("change", () => {
|
|
2473
|
+
const file = importinput.files?.[0];
|
|
2474
|
+
if (!file) return;
|
|
2475
|
+
void file.text().then((content) => JSON.parse(content)).then((filevalue) => request({ kind: "importpresets", file: filevalue })).then((value) => {
|
|
2476
|
+
const parsed = value;
|
|
2477
|
+
status(`Imported ${parsed.imported} reviewed preset${parsed.imported === 1 ? "" : "s"} into the library.`);
|
|
2478
|
+
return refresh();
|
|
2479
|
+
}).catch((error) => status(error instanceof Error ? error.message : String(error), true));
|
|
2480
|
+
});
|
|
2481
|
+
exportrow.append(importinput);
|
|
2482
|
+
editor.append(exportrow);
|
|
2483
|
+
emulationroot.append(editor);
|
|
2484
|
+
const blackbox = emulation.blackbox.filter((entry) => entry.rules.length > 0);
|
|
2485
|
+
if (blackbox.length > 0) {
|
|
2486
|
+
const head = document.createElement("h4");
|
|
2487
|
+
head.textContent = "blackboxed patterns of the run";
|
|
2488
|
+
emulationroot.append(head);
|
|
2489
|
+
for (const entry of blackbox) {
|
|
2490
|
+
const line = document.createElement("p");
|
|
2491
|
+
line.textContent = `${entry.origin}: ${entry.rules.flatMap((rule) => rule.urlpatterns).join(", ")} (${entry.rules.map((rule) => rule.tracescope).join(", ")} scope)`;
|
|
2492
|
+
emulationroot.append(line);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
const restored = emulation.permissions.filter((record) => record.restoredat === void 0);
|
|
2496
|
+
if (restored.length > 0) {
|
|
2497
|
+
const head = document.createElement("h4");
|
|
2498
|
+
head.textContent = "permission overrides pending restore";
|
|
2499
|
+
emulationroot.append(head);
|
|
2500
|
+
for (const record of restored) {
|
|
2501
|
+
const line = document.createElement("p");
|
|
2502
|
+
line.textContent = `${record.name} of ${record.origin} answered ${record.state} for run ${record.runid}; the prior ${record.priorstate} state restores at run end.`;
|
|
2503
|
+
emulationroot.append(line);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
const retentionrow = document.createElement("p");
|
|
2507
|
+
retentionrow.className = "muted";
|
|
2508
|
+
retentionrow.textContent = `Reverted layer state retention: ${context.emulationretention === void 0 ? "keep every prior state" : `${context.emulationretention} layer${context.emulationretention === 1 ? "" : "s"}`}; the layer history itself always survives.`;
|
|
2509
|
+
emulationroot.append(retentionrow);
|
|
2510
|
+
}
|
|
2350
2511
|
async function refresh() {
|
|
2351
2512
|
const context = await request({ kind: "context" });
|
|
2352
2513
|
renderplan(context.plan, context.progress, context.outcomes ?? [], context.retries ?? []);
|
|
@@ -2373,6 +2534,8 @@ async function refresh() {
|
|
|
2373
2534
|
rendertimeline(context);
|
|
2374
2535
|
renderdebugger(context);
|
|
2375
2536
|
renderprofiling(context);
|
|
2537
|
+
renderemulation(context);
|
|
2538
|
+
rendersessions(context);
|
|
2376
2539
|
renderconsolediff();
|
|
2377
2540
|
renderaudit(context.audit);
|
|
2378
2541
|
rendercapabilities(context.capabilities);
|
|
@@ -2542,4 +2705,214 @@ function renderprofiling(context) {
|
|
|
2542
2705
|
ceilinginput.value = context.traceceiling !== void 0 ? String(context.traceceiling) : "";
|
|
2543
2706
|
profilingroot.append(settingsline, retentioninput, ceilinginput, button("Save profiling settings", () => Promise.all([request({ kind: "setprofileretention", retention: retentioninput.value === "" ? void 0 : Number(retentioninput.value) }), request({ kind: "settraceceiling", ceiling: ceilinginput.value === "" ? void 0 : Number(ceilinginput.value) })]).then(() => status(`Profiling settings saved: profile retention ${retentioninput.value === "" ? "keeps every heavy artifact" : `${retentioninput.value} milliseconds`} and trace byte ceiling ${ceilinginput.value === "" ? "none" : `${ceilinginput.value} bytes`}.`)).then(refresh)));
|
|
2544
2707
|
}
|
|
2708
|
+
function rendersessions(context) {
|
|
2709
|
+
if (!sessionsroot) return;
|
|
2710
|
+
sessionsroot.replaceChildren();
|
|
2711
|
+
const memory = context.sessionmemory;
|
|
2712
|
+
const auto = context.autosnapshotstate;
|
|
2713
|
+
const crashed = memory?.crashed === true;
|
|
2714
|
+
if (crashed) {
|
|
2715
|
+
const banner = document.createElement("div");
|
|
2716
|
+
banner.className = "crashbanner";
|
|
2717
|
+
const title = document.createElement("p");
|
|
2718
|
+
title.textContent = `Browser restart interrupted the run${context.taskstate ? ` at step cursor ${context.taskstate.stepcursor}` : ""}; the crash restore stays inside the session consent model.`;
|
|
2719
|
+
banner.append(title);
|
|
2720
|
+
banner.append(button("Resume the interrupted run", async () => {
|
|
2721
|
+
const result = await request({ kind: "resumerun" });
|
|
2722
|
+
status(`Resumed the run: ${result.executed} of ${result.remaining} remaining steps executed.`);
|
|
2723
|
+
await refresh();
|
|
2724
|
+
}));
|
|
2725
|
+
banner.append(" ", button("Dismiss crash banner", async () => {
|
|
2726
|
+
await request({ kind: "clearautosnapshot" }).catch(() => void 0);
|
|
2727
|
+
status("Crash banner dismissed; the saved sessions stay available for restore.");
|
|
2728
|
+
await refresh();
|
|
2729
|
+
}));
|
|
2730
|
+
sessionsroot.append(banner);
|
|
2731
|
+
}
|
|
2732
|
+
if (auto) {
|
|
2733
|
+
const state = document.createElement("p");
|
|
2734
|
+
state.textContent = `Auto snapshots: every ${auto.interval.period} ms \xB7 ${auto.count} of ${auto.interval.maxsnapshots} taken \xB7 expiry ${auto.interval.expiry} ms.`;
|
|
2735
|
+
sessionsroot.append(state, button("Clear auto snapshot interval", async () => {
|
|
2736
|
+
await request({ kind: "clearautosnapshot" });
|
|
2737
|
+
status("Auto snapshot interval cleared.");
|
|
2738
|
+
await refresh();
|
|
2739
|
+
}));
|
|
2740
|
+
}
|
|
2741
|
+
const search = document.createElement("div");
|
|
2742
|
+
search.className = "actions";
|
|
2743
|
+
const term = document.createElement("input");
|
|
2744
|
+
term.type = "search";
|
|
2745
|
+
term.placeholder = "Search sessions by name, url, title or captured text";
|
|
2746
|
+
term.value = sessionsview.term;
|
|
2747
|
+
term.addEventListener("input", () => {
|
|
2748
|
+
sessionsview.term = term.value;
|
|
2749
|
+
rendersessions(context);
|
|
2750
|
+
});
|
|
2751
|
+
const windowselect = document.createElement("select");
|
|
2752
|
+
for (const option of [["all", "all time"], ["hour", "last hour"], ["day", "last day"], ["week", "last week"]]) {
|
|
2753
|
+
const candidate = document.createElement("option");
|
|
2754
|
+
candidate.value = option[0];
|
|
2755
|
+
candidate.textContent = option[1];
|
|
2756
|
+
candidate.selected = sessionsview.window === option[0];
|
|
2757
|
+
windowselect.append(candidate);
|
|
2758
|
+
}
|
|
2759
|
+
windowselect.addEventListener("change", () => {
|
|
2760
|
+
sessionsview.window = windowselect.value;
|
|
2761
|
+
rendersessions(context);
|
|
2762
|
+
});
|
|
2763
|
+
search.append(term, windowselect);
|
|
2764
|
+
sessionsroot.append(search);
|
|
2765
|
+
const importfile = document.createElement("input");
|
|
2766
|
+
importfile.type = "file";
|
|
2767
|
+
importfile.accept = "application/json";
|
|
2768
|
+
importfile.addEventListener("change", async () => {
|
|
2769
|
+
const file = importfile.files?.[0];
|
|
2770
|
+
if (!file) return;
|
|
2771
|
+
const content = await file.text();
|
|
2772
|
+
const review = await request({ kind: "loadsessionfile", content });
|
|
2773
|
+
sessionsview.importreview = { records: review.records, file: JSON.parse(content) };
|
|
2774
|
+
status(`Session file loaded: ${review.records.length} record${review.records.length === 1 ? "" : "s"} await review.`);
|
|
2775
|
+
await refresh();
|
|
2776
|
+
});
|
|
2777
|
+
sessionsroot.append(importfile);
|
|
2778
|
+
if (sessionsview.importreview) {
|
|
2779
|
+
const review = document.createElement("div");
|
|
2780
|
+
review.className = "sessionrow";
|
|
2781
|
+
const title = document.createElement("p");
|
|
2782
|
+
title.textContent = `Import review: ${sessionsview.importreview.records.map((record) => `${record.name} (${record.tabs} tabs)`).join(", ")}.`;
|
|
2783
|
+
review.append(title, button("Approve full record import", async () => {
|
|
2784
|
+
const result = await request({ kind: "importsessionrecords", file: sessionsview.importreview?.file });
|
|
2785
|
+
sessionsview.importreview = void 0;
|
|
2786
|
+
status(`Imported ${result.imported} session record${result.imported === 1 ? "" : "s"} after review.`);
|
|
2787
|
+
await refresh();
|
|
2788
|
+
}), " ", button("Cancel import", async () => {
|
|
2789
|
+
sessionsview.importreview = void 0;
|
|
2790
|
+
status("Import cancelled.");
|
|
2791
|
+
await refresh();
|
|
2792
|
+
}));
|
|
2793
|
+
sessionsroot.append(review);
|
|
2794
|
+
}
|
|
2795
|
+
const now = Date.now();
|
|
2796
|
+
const windowspan = { all: Number.POSITIVE_INFINITY, hour: 36e5, day: 864e5, week: 6048e5 };
|
|
2797
|
+
const records = (memory?.records ?? []).filter((record) => {
|
|
2798
|
+
if (now - record.createdat > windowspan[sessionsview.window]) return false;
|
|
2799
|
+
if (!sessionsview.term.trim()) return true;
|
|
2800
|
+
const haystack = [record.name, record.folder ?? "", ...record.tags, ...record.tabs.flatMap((tab) => [tab.url, tab.title, ...tab.forms.map((form) => form.value)])].join(" ").toLowerCase();
|
|
2801
|
+
return sessionsview.term.trim().toLowerCase().split(/\s+/).every((term2) => haystack.includes(term2));
|
|
2802
|
+
});
|
|
2803
|
+
if (sessionsview.diffselection.length >= 2) {
|
|
2804
|
+
const [left, right] = sessionsview.diffselection;
|
|
2805
|
+
sessionsroot.append(button("Diff the two selected sessions", async () => {
|
|
2806
|
+
const result = await request({ kind: "sessiondiff", left, right });
|
|
2807
|
+
status(`Session diff: ${result.changes.length} change${result.changes.length === 1 ? "" : "s"}.`);
|
|
2808
|
+
await refresh();
|
|
2809
|
+
}));
|
|
2810
|
+
}
|
|
2811
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2812
|
+
for (const record of records) {
|
|
2813
|
+
const key = record.folder ?? "";
|
|
2814
|
+
groups.set(key, [...groups.get(key) ?? [], record]);
|
|
2815
|
+
}
|
|
2816
|
+
for (const [folder, entries] of groups) {
|
|
2817
|
+
const group = document.createElement("details");
|
|
2818
|
+
group.className = "sessiongroup";
|
|
2819
|
+
group.open = true;
|
|
2820
|
+
const summary = document.createElement("summary");
|
|
2821
|
+
summary.textContent = folder === "" ? "Saved sessions" : `Folder ${folder} (${entries.length})`;
|
|
2822
|
+
group.append(summary);
|
|
2823
|
+
for (const record of entries) group.append(sessionrow(record, context.plan));
|
|
2824
|
+
sessionsroot.append(group);
|
|
2825
|
+
}
|
|
2826
|
+
if (records.length === 0) {
|
|
2827
|
+
const empty = document.createElement("p");
|
|
2828
|
+
empty.textContent = "No saved session matches the search yet; run a capturesession step to snapshot the browsing session.";
|
|
2829
|
+
sessionsroot.append(empty);
|
|
2830
|
+
}
|
|
2831
|
+
if (memory && memory.diffs.length > 0) {
|
|
2832
|
+
const diff = memory.diffs[0];
|
|
2833
|
+
if (diff) {
|
|
2834
|
+
const diffview = document.createElement("details");
|
|
2835
|
+
diffview.className = "sessiongroup";
|
|
2836
|
+
const summary = document.createElement("summary");
|
|
2837
|
+
summary.textContent = `Latest session diff (${diff.changes.length} changes)`;
|
|
2838
|
+
diffview.append(summary);
|
|
2839
|
+
for (const change of diff.changes) {
|
|
2840
|
+
const line = document.createElement("p");
|
|
2841
|
+
line.textContent = `${change.class} ${change.subject}: ${change.detail}`;
|
|
2842
|
+
line.dataset.class = change.class;
|
|
2843
|
+
diffview.append(line);
|
|
2844
|
+
}
|
|
2845
|
+
sessionsroot.append(diffview);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (sessionsview.restorereview) renderrestorereview(sessionsview.restorereview);
|
|
2849
|
+
}
|
|
2850
|
+
function sessionrow(record, plan) {
|
|
2851
|
+
const row = document.createElement("div");
|
|
2852
|
+
row.className = "sessionrow";
|
|
2853
|
+
row.dataset.selected = sessionsview.diffselection.includes(record.id) ? "true" : "false";
|
|
2854
|
+
const title = document.createElement("p");
|
|
2855
|
+
const badge = document.createElement("span");
|
|
2856
|
+
badge.className = "sessionbadge";
|
|
2857
|
+
badge.dataset.restored = record.restoredat !== void 0 ? "true" : "false";
|
|
2858
|
+
badge.textContent = record.restoredat !== void 0 ? "restored" : record.auto === true ? "auto" : "saved";
|
|
2859
|
+
title.append(`${record.name} \xB7 ${new Date(record.createdat).toLocaleString()} \xB7 ${record.tabs.length} tabs${record.folder !== void 0 ? ` \xB7 folder ${record.folder}` : ""}${record.tags.length > 0 ? ` \xB7 ${record.tags.join(", ")}` : ""}`, badge);
|
|
2860
|
+
row.append(title);
|
|
2861
|
+
const actions = document.createElement("div");
|
|
2862
|
+
actions.className = "actions";
|
|
2863
|
+
actions.append(button("Restore under review", async () => {
|
|
2864
|
+
sessionsview.restorereview = record;
|
|
2865
|
+
await refresh();
|
|
2866
|
+
}, record.sectionsexpired === true));
|
|
2867
|
+
actions.append(" ", button(sessionsview.diffselection.includes(record.id) ? "Unselect diff" : "Select diff", async () => {
|
|
2868
|
+
sessionsview.diffselection = sessionsview.diffselection.includes(record.id) ? sessionsview.diffselection.filter((id) => id !== record.id) : [...sessionsview.diffselection, record.id].slice(-2);
|
|
2869
|
+
await refresh();
|
|
2870
|
+
}));
|
|
2871
|
+
actions.append(" ", button("Export reviewed file", async () => {
|
|
2872
|
+
const result = await request({ kind: "exportsessionfile", ids: [record.id] });
|
|
2873
|
+
status(`Exported the session file of ${result.bytes} bytes through the download flow.`);
|
|
2874
|
+
}));
|
|
2875
|
+
const capturestep = plan?.state === "approved" ? plan.steps.find((step) => step.kind === "capturesession") : void 0;
|
|
2876
|
+
if (capturestep) actions.append(" ", button("Run reviewed snapshot step", async () => {
|
|
2877
|
+
const result = await request({ kind: "execute", stepid: capturestep.id });
|
|
2878
|
+
status(result.summary);
|
|
2879
|
+
await refresh();
|
|
2880
|
+
}));
|
|
2881
|
+
row.append(actions);
|
|
2882
|
+
return row;
|
|
2883
|
+
}
|
|
2884
|
+
function renderrestorereview(record) {
|
|
2885
|
+
if (!sessionsroot) return;
|
|
2886
|
+
const review = document.createElement("div");
|
|
2887
|
+
review.className = "sessionrow";
|
|
2888
|
+
const title = document.createElement("p");
|
|
2889
|
+
title.textContent = `Restore review of ${record.name}: ${record.tabs.length} tabs, ${record.tabs.reduce((total, tab) => total + tab.forms.length, 0)} captured form fields, ${record.captures.length} linked captures.`;
|
|
2890
|
+
review.append(title);
|
|
2891
|
+
for (const tab of record.tabs) {
|
|
2892
|
+
const line = document.createElement("p");
|
|
2893
|
+
line.textContent = `Tab ${tab.index}: ${tab.title || tab.url} \xB7 ${tab.forms.length} form fields \xB7 scroll ${tab.scrollx},${tab.scrolly}`;
|
|
2894
|
+
review.append(line);
|
|
2895
|
+
}
|
|
2896
|
+
for (const entry of record.storage) {
|
|
2897
|
+
const line = document.createElement("p");
|
|
2898
|
+
line.textContent = `Local storage of ${entry.origin}: ${entry.keys.length} keys captured.`;
|
|
2899
|
+
review.append(line);
|
|
2900
|
+
}
|
|
2901
|
+
for (const entry of record.cookies) {
|
|
2902
|
+
const line = document.createElement("p");
|
|
2903
|
+
line.textContent = `Cookies of ${entry.origin}: ${entry.names.length} names captured, values held back.`;
|
|
2904
|
+
review.append(line);
|
|
2905
|
+
}
|
|
2906
|
+
review.append(button("Approve restore", async () => {
|
|
2907
|
+
const result = await request({ kind: "approverestore", sessionid: record.id });
|
|
2908
|
+
sessionsview.restorereview = void 0;
|
|
2909
|
+
status(`Restored ${result.restored} tabs${result.skippedorigins.length > 0 ? `; skipped ${result.skippedorigins.join(", ")}` : ""}.`);
|
|
2910
|
+
await refresh();
|
|
2911
|
+
}), " ", button("Cancel restore", async () => {
|
|
2912
|
+
sessionsview.restorereview = void 0;
|
|
2913
|
+
status("Restore cancelled.");
|
|
2914
|
+
await refresh();
|
|
2915
|
+
}));
|
|
2916
|
+
sessionsroot.append(review);
|
|
2917
|
+
}
|
|
2545
2918
|
//# sourceMappingURL=sidepanel.js.map
|