@wenathlan/extension 1.1.35 → 1.1.37
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 +7 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +490 -7
- package/dist/index.js.map +3 -3
- package/dist/memory.d.ts +77 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +29 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +38 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +221 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1494 -15
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +585 -5
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +36 -2
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +390 -2
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,33 @@
|
|
|
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"]);
|
|
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"]);
|
|
5
|
+
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
6
|
+
|
|
7
|
+
// extension/tabscommand.ts
|
|
8
|
+
function switcherlist(tabs, recency, filter) {
|
|
9
|
+
const needle = filter.trim().toLowerCase();
|
|
10
|
+
const matches = needle ? tabs.filter((tab) => tab.title.toLowerCase().includes(needle) || tab.url.toLowerCase().includes(needle)) : [...tabs];
|
|
11
|
+
const lastrun = new Map(recency.map((entry) => [entry.tabid, entry.at]));
|
|
12
|
+
return [...matches].sort((left, right) => {
|
|
13
|
+
const leftat = lastrun.get(left.tabid) ?? -1;
|
|
14
|
+
const rightat = lastrun.get(right.tabid) ?? -1;
|
|
15
|
+
if (leftat !== rightat) return rightat - leftat;
|
|
16
|
+
return left.index - right.index;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// extension/pageforms.ts
|
|
21
|
+
function cardmask(value) {
|
|
22
|
+
const trimmed = value.trim();
|
|
23
|
+
if (/^\d[\d\s-]{11,18}$/.test(trimmed)) {
|
|
24
|
+
const compact = trimmed.replace(/[\s-]/g, "");
|
|
25
|
+
const last = compact.slice(-4);
|
|
26
|
+
return `${"\u2022".repeat(Math.max(0, compact.length - 4))}${last}`;
|
|
27
|
+
}
|
|
28
|
+
return "\u2022".repeat(trimmed.length);
|
|
29
|
+
}
|
|
30
|
+
|
|
1
31
|
// extension/sidepanel.ts
|
|
2
32
|
var objective = document.querySelector("#objective");
|
|
3
33
|
var localbutton = document.querySelector("#localplan");
|
|
@@ -16,6 +46,8 @@ var bannersroot = document.querySelector("#banners");
|
|
|
16
46
|
var selectorsroot = document.querySelector("#selectors");
|
|
17
47
|
var trailroot = document.querySelector("#trail");
|
|
18
48
|
var navigationroot = document.querySelector("#navigation");
|
|
49
|
+
var tabswindowsroot = document.querySelector("#tabswindows");
|
|
50
|
+
var formsroot = document.querySelector("#forms");
|
|
19
51
|
var statusnode = document.querySelector("#status");
|
|
20
52
|
var progressnode = document.querySelector("#planprogress");
|
|
21
53
|
var capabilitiestext = document.querySelector("#capabilitiestext");
|
|
@@ -48,7 +80,7 @@ function button(label, action, disabled = false) {
|
|
|
48
80
|
element.addEventListener("click", () => action().catch((error) => status(error instanceof Error ? error.message : String(error), true)));
|
|
49
81
|
return element;
|
|
50
82
|
}
|
|
51
|
-
var previewkinds = ["focus", "inspect", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "clickpoint", "clicktext", "clickaria", "clickname", "resolvexpath", "deriveselector", "fingerprintsection"];
|
|
83
|
+
var previewkinds = ["focus", "inspect", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "clickpoint", "clicktext", "clickaria", "clickname", "resolvexpath", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword"];
|
|
52
84
|
var topictags = [
|
|
53
85
|
{ topic: "pointer", kinds: ["movepointer", "clickpoint", "shiftclick", "clicktext", "clickaria", "clickname", "pierceshadow"] },
|
|
54
86
|
{ topic: "typing", kinds: ["typetime", "appendtext", "setvalue", "typeedit", "submitsearch"] },
|
|
@@ -61,7 +93,9 @@ var topictags = [
|
|
|
61
93
|
{ topic: "observation", kinds: ["a11ytree", "readvisible", "readertree", "readoutline", "readselection", "readopengraph", "readlang", "detectlanguage", "listshadow", "listframes", "readscrollpos"] },
|
|
62
94
|
{ topic: "detection", kinds: ["detectlists", "detecttables", "detectinfinitescroll", "detectvirtual", "detectlazy", "detectsticky", "detectscrolllock", "countpages", "classifypage", "fingerprintsection"] },
|
|
63
95
|
{ topic: "watch", kinds: ["watchmutate", "watchbanner", "watchfocus", "waitquiet", "readjson", "diffsnapshots", "deriveselector"] },
|
|
64
|
-
{ topic: "navigation", kinds: ["openlink", "openprivate", "reloadcache", "stopnav", "waitload", "waiturl", "followlink", "spanav", "spawait", "rewritequery", "setfragment", "navlist", "navprofile", "detecthttp", "readredirects", "readfinalurl", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "trailaudit", "pausenav", "navintent", "navrate", "openclipboard", "checksafe", "batchopen"] }
|
|
96
|
+
{ topic: "navigation", kinds: ["openlink", "openprivate", "reloadcache", "stopnav", "waitload", "waiturl", "followlink", "spanav", "spawait", "rewritequery", "setfragment", "navlist", "navprofile", "detecthttp", "readredirects", "readfinalurl", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "trailaudit", "pausenav", "navintent", "navrate", "openclipboard", "checksafe", "batchopen"] },
|
|
97
|
+
{ topic: "tabs", kinds: ["querytabs", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "watchtab", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "savelayout", "restorelayout", "findclones", "searchtabs", "badgetab", "attachmeta", "listaudio", "reopenrun", "snapshotsession"] },
|
|
98
|
+
{ topic: "forms", kinds: ["fillform", "filllabel", "fillplaceholder", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "submitform", "readerrors", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "handoffcaptcha", "fillcard", "fillcode", "consentpassword", "skiphoneypot", "detectlogin", "detecttemplate"] }
|
|
65
99
|
];
|
|
66
100
|
function steptopic(kind) {
|
|
67
101
|
return topictags.find((tag) => tag.kinds.includes(kind))?.topic;
|
|
@@ -579,6 +613,358 @@ function rendernavigation(context) {
|
|
|
579
613
|
navigationroot.append(list);
|
|
580
614
|
}
|
|
581
615
|
}
|
|
616
|
+
function rendertabswindows(context) {
|
|
617
|
+
if (!tabswindowsroot) return;
|
|
618
|
+
tabswindowsroot.replaceChildren();
|
|
619
|
+
const tabs = context.tabs ?? [];
|
|
620
|
+
const badges = context.badges ?? [];
|
|
621
|
+
const metas = context.tabmetas ?? [];
|
|
622
|
+
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
623
|
+
const gauge = context.tasktabgauge ?? { used: 0, ceiling: void 0, over: false };
|
|
624
|
+
const budget = document.createElement("p");
|
|
625
|
+
budget.className = gauge.over ? "bannercard" : "";
|
|
626
|
+
budget.textContent = `task tab budget: ${gauge.used} tab${gauge.used === 1 ? "" : "s"} with active tasks${gauge.ceiling !== void 0 ? ` of the user configured ceiling ${gauge.ceiling}` : " with no user ceiling configured"}${gauge.over ? " \u2014 over the reviewed budget" : ""}`;
|
|
627
|
+
tabswindowsroot.append(budget);
|
|
628
|
+
const ceilinginput = document.createElement("input");
|
|
629
|
+
ceilinginput.type = "number";
|
|
630
|
+
ceilinginput.min = "0";
|
|
631
|
+
ceilinginput.placeholder = gauge.ceiling !== void 0 ? String(gauge.ceiling) : "no ceiling";
|
|
632
|
+
ceilinginput.setAttribute("aria-label", "concurrent task tab ceiling");
|
|
633
|
+
const ceilingbutton = button("Save task tab ceiling", async () => {
|
|
634
|
+
await request({ kind: "settasktabceiling", ceiling: ceilinginput.value === "" ? void 0 : Number(ceilinginput.value) });
|
|
635
|
+
status(`Task tab ceiling saved as ${ceilinginput.value === "" ? "no ceiling" : ceilinginput.value}; the value stays a user choice.`);
|
|
636
|
+
await refresh();
|
|
637
|
+
});
|
|
638
|
+
tabswindowsroot.append(ceilinginput, " ", ceilingbutton);
|
|
639
|
+
const switcherheading = document.createElement("p");
|
|
640
|
+
switcherheading.textContent = "quick switcher (ordered by recency, filter by title or url):";
|
|
641
|
+
tabswindowsroot.append(switcherheading);
|
|
642
|
+
const filterinput = document.createElement("input");
|
|
643
|
+
filterinput.type = "search";
|
|
644
|
+
filterinput.placeholder = "filter open tabs";
|
|
645
|
+
filterinput.setAttribute("aria-label", "quick switcher filter");
|
|
646
|
+
const switchlist = document.createElement("ul");
|
|
647
|
+
const renderswitchlist = () => {
|
|
648
|
+
switchlist.replaceChildren();
|
|
649
|
+
const ordered = switcherlist(tabs, [], filterinput.value).slice(0, 10);
|
|
650
|
+
for (const tab of ordered) {
|
|
651
|
+
const item = document.createElement("li");
|
|
652
|
+
const jump = document.createElement("button");
|
|
653
|
+
jump.type = "button";
|
|
654
|
+
const badge = badges.find((entry) => entry.tabid === tab.tabid);
|
|
655
|
+
const meta = metas.find((entry) => entry.tabid === tab.tabid);
|
|
656
|
+
jump.textContent = `${tab.title || tab.url}${tab.pinned ? " \u{1F4CC}" : ""}${tab.audible || tab.muted ? ` ${tab.muted ? "\u{1F507}" : "\u{1F50A}"}` : ""}${badge ? ` [${badge.label}]` : ""}${meta && meta.labels.length > 0 ? ` (${meta.labels.join(", ")})` : ""}`;
|
|
657
|
+
jump.addEventListener("click", () => request({ kind: "jumptotab", tabid: tab.tabid }).then(() => status(`Jumped to tab ${tab.tabid}.`)).catch((error) => status(error instanceof Error ? error.message : String(error), true)));
|
|
658
|
+
item.append(jump);
|
|
659
|
+
switchlist.append(item);
|
|
660
|
+
}
|
|
661
|
+
if (ordered.length === 0) {
|
|
662
|
+
const empty = document.createElement("li");
|
|
663
|
+
empty.textContent = "no open tab matches the filter";
|
|
664
|
+
switchlist.append(empty);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
filterinput.addEventListener("input", renderswitchlist);
|
|
668
|
+
tabswindowsroot.append(filterinput, switchlist);
|
|
669
|
+
renderswitchlist();
|
|
670
|
+
const searchinput = document.createElement("input");
|
|
671
|
+
searchinput.type = "search";
|
|
672
|
+
searchinput.placeholder = "search across open tabs by title and url";
|
|
673
|
+
searchinput.setAttribute("aria-label", "searchtabs text");
|
|
674
|
+
const searchresults = document.createElement("ul");
|
|
675
|
+
const searchbutton = button("Run searchtabs", async () => {
|
|
676
|
+
const result = await request({ kind: "tabsearch", text: searchinput.value });
|
|
677
|
+
searchresults.replaceChildren();
|
|
678
|
+
for (const tab of result.matches.slice(0, 10)) {
|
|
679
|
+
const item = document.createElement("li");
|
|
680
|
+
const jump = document.createElement("button");
|
|
681
|
+
jump.type = "button";
|
|
682
|
+
jump.textContent = `${tab.title || tab.url} \xB7 ${tab.url}`;
|
|
683
|
+
jump.addEventListener("click", () => request({ kind: "jumptotab", tabid: tab.tabid }).then(() => status(`Jumped to tab ${tab.tabid}.`)).catch((error) => status(error instanceof Error ? error.message : String(error), true)));
|
|
684
|
+
item.append(jump);
|
|
685
|
+
searchresults.append(item);
|
|
686
|
+
}
|
|
687
|
+
status(`searchtabs matched ${result.matches.length} open tab${result.matches.length === 1 ? "" : "s"}.`);
|
|
688
|
+
});
|
|
689
|
+
tabswindowsroot.append(searchinput, " ", searchbutton, searchresults);
|
|
690
|
+
const clones = context.clones ?? [];
|
|
691
|
+
for (const clone of clones.slice(0, 3)) {
|
|
692
|
+
const warning = document.createElement("p");
|
|
693
|
+
warning.className = "bannercard";
|
|
694
|
+
warning.textContent = `duplicate tab warning: ${clone.tabids.length} open tabs share the url ${clone.url} (tabs ${clone.tabids.join(", ")})`;
|
|
695
|
+
tabswindowsroot.append(warning);
|
|
696
|
+
}
|
|
697
|
+
const groups = context.tabgroups ?? [];
|
|
698
|
+
if (groups.length > 0) {
|
|
699
|
+
const groupsheading = document.createElement("p");
|
|
700
|
+
groupsheading.textContent = "tab groups with colors and collapse states:";
|
|
701
|
+
tabswindowsroot.append(groupsheading);
|
|
702
|
+
const grouplist = document.createElement("ul");
|
|
703
|
+
for (const group of groups.slice(0, 6)) {
|
|
704
|
+
const item = document.createElement("li");
|
|
705
|
+
item.textContent = `${group.name} \xB7 ${group.color} \xB7 ${group.collapsed ? "collapsed" : "expanded"} \xB7 ${group.tabids.length} member tab${group.tabids.length === 1 ? "" : "s"}`;
|
|
706
|
+
grouplist.append(item);
|
|
707
|
+
}
|
|
708
|
+
tabswindowsroot.append(grouplist);
|
|
709
|
+
}
|
|
710
|
+
const windows = context.windows ?? [];
|
|
711
|
+
if (windows.length > 0) {
|
|
712
|
+
const windowsheading = document.createElement("p");
|
|
713
|
+
windowsheading.textContent = "windows with layouts and bounds:";
|
|
714
|
+
tabswindowsroot.append(windowsheading);
|
|
715
|
+
const windowlist = document.createElement("ul");
|
|
716
|
+
for (const item of windows.slice(0, 6)) {
|
|
717
|
+
const entry = document.createElement("li");
|
|
718
|
+
entry.textContent = `window ${item.windowid} \xB7 ${item.state} \xB7 bounds ${item.left}\xD7${item.top} ${item.width}\xD7${item.height}${item.incognito ? " \xB7 incognito, grants not inherited" : ""}${item.focused ? " \xB7 focused" : ""}`;
|
|
719
|
+
const closebutton = button("Close window", async () => {
|
|
720
|
+
const tasktabids = context.progress?.tasktabs ?? [];
|
|
721
|
+
const tabsoftask = (context.tabs ?? []).filter((tab) => tab.windowid === item.windowid && tasktabids.includes(tab.tabid));
|
|
722
|
+
const reviewed = tabsoftask.length > 1 ? window.confirm(`This window holds ${tabsoftask.length} task tabs. Close it anyway under explicit review?`) : true;
|
|
723
|
+
await request({ kind: "closewindow", windowid: item.windowid, reviewed });
|
|
724
|
+
status(`Closed window ${item.windowid}.`);
|
|
725
|
+
await refresh();
|
|
726
|
+
});
|
|
727
|
+
entry.append(" ", closebutton);
|
|
728
|
+
windowlist.append(entry);
|
|
729
|
+
}
|
|
730
|
+
tabswindowsroot.append(windowlist);
|
|
731
|
+
}
|
|
732
|
+
const layoutcontrols = document.createElement("p");
|
|
733
|
+
const layoutname = document.createElement("input");
|
|
734
|
+
layoutname.type = "text";
|
|
735
|
+
layoutname.placeholder = "layout name";
|
|
736
|
+
layoutname.setAttribute("aria-label", "layout name");
|
|
737
|
+
const savebutton = button("Save layout", async () => {
|
|
738
|
+
if (!active) {
|
|
739
|
+
status("Layout save stays inside an active session.", true);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
await request({ kind: "savelayout", name: layoutname.value });
|
|
743
|
+
status(`Saved the tab layout ${layoutname.value}.`);
|
|
744
|
+
await refresh();
|
|
745
|
+
});
|
|
746
|
+
const restorebutton = button("Restore layout", async () => {
|
|
747
|
+
if (!active) {
|
|
748
|
+
status("Layout restore stays inside an active session.", true);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
const result = await request({ kind: "restorelayout", name: layoutname.value });
|
|
752
|
+
status(`Restored the tab layout ${layoutname.value}: ${result.reopened} tab${result.reopened === 1 ? "" : "s"} reopened.`);
|
|
753
|
+
await refresh();
|
|
754
|
+
});
|
|
755
|
+
layoutcontrols.append(layoutname, " ", savebutton, " ", restorebutton);
|
|
756
|
+
tabswindowsroot.append(layoutcontrols);
|
|
757
|
+
const layouts = context.layouts ?? [];
|
|
758
|
+
if (layouts.length > 0) {
|
|
759
|
+
const layoutlist = document.createElement("ul");
|
|
760
|
+
for (const layout of layouts.slice(0, 4)) {
|
|
761
|
+
const item = document.createElement("li");
|
|
762
|
+
item.textContent = `${layout.name} \xB7 ${layout.tabs.length} tab${layout.tabs.length === 1 ? "" : "s"} \xB7 ${layout.groups.length} group${layout.groups.length === 1 ? "" : "s"} \xB7 ${layout.windows.length} window bound${layout.windows.length === 1 ? "" : "s"} \xB7 saved ${new Date(layout.savedat).toLocaleString()}`;
|
|
763
|
+
layoutlist.append(item);
|
|
764
|
+
}
|
|
765
|
+
tabswindowsroot.append(layoutlist);
|
|
766
|
+
}
|
|
767
|
+
const snapshots = context.snapshots ?? [];
|
|
768
|
+
if (snapshots.length > 0) {
|
|
769
|
+
const snapcard = document.createElement("div");
|
|
770
|
+
snapcard.className = "bannercard";
|
|
771
|
+
const snaptitle = document.createElement("p");
|
|
772
|
+
snaptitle.textContent = `session snapshot card: ${snapshots.length} snapshot${snapshots.length === 1 ? "" : "s"} stored`;
|
|
773
|
+
snapcard.append(snaptitle);
|
|
774
|
+
for (const snapshot of snapshots.slice(0, 3)) {
|
|
775
|
+
const row = document.createElement("p");
|
|
776
|
+
row.textContent = `${snapshot.layout.tabs.length} tabs \xB7 captured ${new Date(snapshot.capturedat).toLocaleString()}`;
|
|
777
|
+
const restore = button("Restore snapshot", async () => {
|
|
778
|
+
const result = await request({ kind: "restoresnapshot", id: snapshot.id });
|
|
779
|
+
status(`Restored the session snapshot: ${result.reopened} tab${result.reopened === 1 ? "" : "s"} reopened.`);
|
|
780
|
+
await refresh();
|
|
781
|
+
});
|
|
782
|
+
row.append(" ", restore);
|
|
783
|
+
snapcard.append(row);
|
|
784
|
+
}
|
|
785
|
+
tabswindowsroot.append(snapcard);
|
|
786
|
+
}
|
|
787
|
+
const controlcard = document.createElement("div");
|
|
788
|
+
controlcard.className = "bannercard";
|
|
789
|
+
const controlstate = context.controltab;
|
|
790
|
+
const completed = context.progress?.completedsteps.length ?? 0;
|
|
791
|
+
const total = context.plan?.steps.length ?? 0;
|
|
792
|
+
controlcard.textContent = `pinned control tab feed: ${controlstate?.enabled ? `open as tab ${controlstate.tabid} with the live task status ${completed} of ${total} reviewed steps executed` : "disabled"}${context.plan ? ` \xB7 ${context.plan.state}` : " \xB7 no plan"}`;
|
|
793
|
+
const controlbutton = button(controlstate?.enabled ? "Close pinned control tab" : "Open pinned control tab", async () => {
|
|
794
|
+
await request({ kind: "controltab", enabled: !controlstate?.enabled });
|
|
795
|
+
status(controlstate?.enabled ? "The pinned control tab was closed." : "The pinned control tab was opened with the live task feed.");
|
|
796
|
+
await refresh();
|
|
797
|
+
});
|
|
798
|
+
controlcard.append(" ", controlbutton);
|
|
799
|
+
tabswindowsroot.append(controlcard);
|
|
800
|
+
}
|
|
801
|
+
function renderforms(context) {
|
|
802
|
+
if (!formsroot) return;
|
|
803
|
+
formsroot.replaceChildren();
|
|
804
|
+
const outcomes = context.outcomes ?? [];
|
|
805
|
+
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
806
|
+
const opencaptcha = (context.captchas ?? []).find((handoff) => !handoff.resolved);
|
|
807
|
+
if (opencaptcha) {
|
|
808
|
+
const card = document.createElement("div");
|
|
809
|
+
card.className = "bannercard";
|
|
810
|
+
card.textContent = `Captcha handoff open on ${opencaptcha.origin}: control is yours and the plan waits until you resolve it.`;
|
|
811
|
+
card.append(" ", button("Captcha resolved", async () => {
|
|
812
|
+
await request({ kind: "resolvecaptcha" });
|
|
813
|
+
status("Captcha handoff resolved; the plan continues.");
|
|
814
|
+
await refresh();
|
|
815
|
+
}));
|
|
816
|
+
formsroot.append(card);
|
|
817
|
+
}
|
|
818
|
+
const detections = context.detections ?? [];
|
|
819
|
+
if (detections.length > 0) {
|
|
820
|
+
const badges = document.createElement("p");
|
|
821
|
+
badges.textContent = `template badges: ${detections.slice(0, 6).map((record) => `${record.kind} on ${record.origin}${record.markers.length > 0 ? ` (${record.markers.join(", ")})` : ""}`).join(" \xB7 ")}`;
|
|
822
|
+
formsroot.append(badges);
|
|
823
|
+
}
|
|
824
|
+
const mapoutcome = [...outcomes].reverse().find((outcome) => outcome.details?.report !== void 0 && outcome.details?.count !== void 0);
|
|
825
|
+
const skippedselectors = new Set(outcomes.flatMap((outcome) => Array.isArray(outcome.details?.skipped) ? outcome.details?.skipped : []).map((trap) => trap.selector));
|
|
826
|
+
if (mapoutcome) {
|
|
827
|
+
const report = mapoutcome.details?.report;
|
|
828
|
+
const heading = document.createElement("p");
|
|
829
|
+
heading.textContent = `form map${report.form ? ` of ${report.form}` : ""}: ${report.fields.length} detected field${report.fields.length === 1 ? "" : "s"} with their kinds${skippedselectors.size > 0 ? `; ${skippedselectors.size} honeypot field${skippedselectors.size === 1 ? "" : "s"} highlighted as skipped` : ""}`;
|
|
830
|
+
formsroot.append(heading);
|
|
831
|
+
const list = document.createElement("ul");
|
|
832
|
+
for (const field of report.fields.slice(0, 10)) {
|
|
833
|
+
const item = document.createElement("li");
|
|
834
|
+
const skipped = skippedselectors.has(field.selector);
|
|
835
|
+
item.textContent = `${field.label || field.selector} \xB7 ${field.kind}${field.matched ? "" : " \xB7 unmatched"}${skipped ? " \xB7 honeypot, skipped" : ""}`;
|
|
836
|
+
list.append(item);
|
|
837
|
+
}
|
|
838
|
+
formsroot.append(list);
|
|
839
|
+
}
|
|
840
|
+
const valuesoutcome = [...outcomes].reverse().find((outcome) => Array.isArray(outcome.details?.values) && outcome.details?.locale !== void 0);
|
|
841
|
+
if (valuesoutcome) {
|
|
842
|
+
const values = valuesoutcome.details?.values;
|
|
843
|
+
const locale = typeof valuesoutcome.details?.locale === "string" ? valuesoutcome.details.locale : "en";
|
|
844
|
+
const seed = typeof valuesoutcome.details?.seed === "number" ? valuesoutcome.details.seed : 1;
|
|
845
|
+
const heading = document.createElement("p");
|
|
846
|
+
heading.textContent = `generated values (locale ${locale}, seed ${seed}) with a regenerate button per field:`;
|
|
847
|
+
formsroot.append(heading);
|
|
848
|
+
const list = document.createElement("ul");
|
|
849
|
+
for (const entry of values.slice(0, 10)) {
|
|
850
|
+
const item = document.createElement("li");
|
|
851
|
+
item.textContent = `${entry.label} \xB7 ${entry.kind} \xB7 ${entry.value}`;
|
|
852
|
+
item.append(" ", button("Regenerate", async () => {
|
|
853
|
+
const regenerated = await request({ kind: "regeneratevalue", field: entry.kind, locale, seed: seed + 1 });
|
|
854
|
+
status(`Regenerated ${entry.label}: ${regenerated.value}.`);
|
|
855
|
+
}));
|
|
856
|
+
list.append(item);
|
|
857
|
+
}
|
|
858
|
+
formsroot.append(list);
|
|
859
|
+
}
|
|
860
|
+
const cardoutcome = [...outcomes].reverse().find((outcome) => Array.isArray(outcome.details?.segments));
|
|
861
|
+
if (cardoutcome) {
|
|
862
|
+
const segments = cardoutcome.details?.segments;
|
|
863
|
+
const cardline = document.createElement("p");
|
|
864
|
+
cardline.textContent = `card fill segments (masked): ${segments.map((segment) => `${segment.label} ${cardmask(segment.masked)}`).join(" \xB7 ")}`;
|
|
865
|
+
formsroot.append(cardline);
|
|
866
|
+
}
|
|
867
|
+
const profiles = context.profiles ?? [];
|
|
868
|
+
if (profiles.length > 0) {
|
|
869
|
+
const heading = document.createElement("p");
|
|
870
|
+
heading.textContent = "saved form profiles with origin grants:";
|
|
871
|
+
formsroot.append(heading);
|
|
872
|
+
const list = document.createElement("ul");
|
|
873
|
+
for (const profile of profiles.slice(0, 6)) {
|
|
874
|
+
const item = document.createElement("li");
|
|
875
|
+
item.textContent = `${profile.name} \xB7 ${profile.fields.length} field${profile.fields.length === 1 ? "" : "s"} \xB7 grants ${profile.grants.join(", ")} \xB7 saved ${new Date(profile.savedat).toLocaleString()}`;
|
|
876
|
+
item.append(" ", button("Apply", async () => {
|
|
877
|
+
const applied = await request({ kind: "applyprofile", name: profile.name });
|
|
878
|
+
pickhint(`profile hint: ${profile.name} with ${applied.profile.fields.length} reviewed field entries`);
|
|
879
|
+
}), " ", button("Remove", async () => {
|
|
880
|
+
await request({ kind: "removeprofile", name: profile.name });
|
|
881
|
+
status(`Form profile ${profile.name} removed.`);
|
|
882
|
+
await refresh();
|
|
883
|
+
}));
|
|
884
|
+
list.append(item);
|
|
885
|
+
}
|
|
886
|
+
formsroot.append(list);
|
|
887
|
+
}
|
|
888
|
+
const pending = (context.tickets ?? []).filter((ticket) => ticket.approved === void 0);
|
|
889
|
+
for (const ticket of pending) {
|
|
890
|
+
const card = document.createElement("div");
|
|
891
|
+
card.className = "bannercard";
|
|
892
|
+
const title = document.createElement("p");
|
|
893
|
+
title.textContent = `asksubmit for form ${ticket.form || "the reviewed form"} \xB7 values hash ${ticket.valueshash}`;
|
|
894
|
+
card.append(title);
|
|
895
|
+
const askoutcome = [...outcomes].reverse().find((outcome) => Array.isArray(outcome.details?.values) && outcome.details?.ticket !== void 0);
|
|
896
|
+
const values = askoutcome?.details?.values;
|
|
897
|
+
if (values) {
|
|
898
|
+
const diff = document.createElement("ul");
|
|
899
|
+
for (const entry of values.slice(0, 10)) {
|
|
900
|
+
const item = document.createElement("li");
|
|
901
|
+
item.textContent = `${entry.label}: ${entry.value}`;
|
|
902
|
+
diff.append(item);
|
|
903
|
+
}
|
|
904
|
+
card.append(diff);
|
|
905
|
+
} else {
|
|
906
|
+
const note = document.createElement("p");
|
|
907
|
+
note.textContent = "The full values diff appears here once the asksubmit step reads the form.";
|
|
908
|
+
card.append(note);
|
|
909
|
+
}
|
|
910
|
+
card.append(button("Approve submission", async () => {
|
|
911
|
+
await request({ kind: "approvesubmit", id: ticket.id, approved: true });
|
|
912
|
+
status("Submission approved; the reviewed submitform step may run.");
|
|
913
|
+
await refresh();
|
|
914
|
+
}), " ", button("Decline", async () => {
|
|
915
|
+
await request({ kind: "approvesubmit", id: ticket.id, approved: false });
|
|
916
|
+
status("Submission declined.");
|
|
917
|
+
await refresh();
|
|
918
|
+
}));
|
|
919
|
+
formsroot.append(card);
|
|
920
|
+
}
|
|
921
|
+
const wizards = context.wizards?.wizards ?? [];
|
|
922
|
+
if (wizards.length > 0) {
|
|
923
|
+
const wizard = wizards[0];
|
|
924
|
+
const heading = document.createElement("p");
|
|
925
|
+
const indicators = Array.from({ length: wizard.steps }, (_, index) => `${index < wizard.index ? wizard.completed[index] ? "\u2713" : "\xB7" : "\u25CB"}`).join(" ");
|
|
926
|
+
heading.textContent = `wizard progress: step ${Math.min(wizard.index + 1, wizard.steps)} of ${wizard.steps} ${indicators}`;
|
|
927
|
+
formsroot.append(heading);
|
|
928
|
+
}
|
|
929
|
+
const picks = context.wizards?.picks ?? [];
|
|
930
|
+
if (picks.length > 0) {
|
|
931
|
+
const pickline = document.createElement("p");
|
|
932
|
+
pickline.textContent = `typeahead picks: ${picks.slice(0, 6).map((pick) => `"${pick.pick}" for "${pick.query}"`).join(" \xB7 ")}`;
|
|
933
|
+
formsroot.append(pickline);
|
|
934
|
+
}
|
|
935
|
+
const reports = context.errorreports ?? [];
|
|
936
|
+
if (reports.length > 0) {
|
|
937
|
+
const heading = document.createElement("p");
|
|
938
|
+
heading.textContent = "inline error reports with field refs for correction loops:";
|
|
939
|
+
formsroot.append(heading);
|
|
940
|
+
const list = document.createElement("ul");
|
|
941
|
+
for (const report of reports.slice(0, 3)) {
|
|
942
|
+
const item = document.createElement("li");
|
|
943
|
+
item.textContent = `${report.form || "the reviewed form"}: ${report.errors.map((error) => `${error.field} \u2014 ${error.message}`).join("; ") || "no message"}`;
|
|
944
|
+
list.append(item);
|
|
945
|
+
}
|
|
946
|
+
formsroot.append(list);
|
|
947
|
+
}
|
|
948
|
+
const codecard = document.createElement("div");
|
|
949
|
+
codecard.className = "bannercard";
|
|
950
|
+
const codetitle = document.createElement("p");
|
|
951
|
+
codetitle.textContent = active ? "one time code entry (stored behind the consent gate of the active session):" : "one time code entry needs an active session first.";
|
|
952
|
+
codecard.append(codetitle);
|
|
953
|
+
if (active) {
|
|
954
|
+
const codeinput = document.createElement("input");
|
|
955
|
+
codeinput.type = "text";
|
|
956
|
+
codeinput.inputMode = "numeric";
|
|
957
|
+
codeinput.placeholder = context.codeentry ? "a reviewed code is stored" : "one time code";
|
|
958
|
+
codeinput.setAttribute("aria-label", "one time code");
|
|
959
|
+
const storebutton = button("Store reviewed code", async () => {
|
|
960
|
+
await request({ kind: "storecode", code: codeinput.value });
|
|
961
|
+
status("The reviewed one time code is stored behind the consent gate.");
|
|
962
|
+
await refresh();
|
|
963
|
+
});
|
|
964
|
+
codecard.append(codeinput, " ", storebutton);
|
|
965
|
+
}
|
|
966
|
+
formsroot.append(codecard);
|
|
967
|
+
}
|
|
582
968
|
function rendercapabilities(report) {
|
|
583
969
|
if (!capabilitiestext) return;
|
|
584
970
|
if (!report) {
|
|
@@ -624,6 +1010,8 @@ async function refresh() {
|
|
|
624
1010
|
renderselectors(context.selectors ?? []);
|
|
625
1011
|
rendertrail(context.trail?.trail ?? []);
|
|
626
1012
|
rendernavigation(context);
|
|
1013
|
+
rendertabswindows(context);
|
|
1014
|
+
renderforms(context);
|
|
627
1015
|
renderaudit(context.audit);
|
|
628
1016
|
rendercapabilities(context.capabilities);
|
|
629
1017
|
if (context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
|