Add active tab page snapshots
This commit is contained in:
parent
fa3b9c7778
commit
b20fb98c54
8 changed files with 332 additions and 26 deletions
|
|
@ -44,6 +44,12 @@ The extension posts tab metadata to
|
|||
`http://127.0.0.1:17666/ingest/chrome-extension`. Its normal sync does not read
|
||||
page DOM, cookies, or page content.
|
||||
|
||||
Use `Snapshot active tab` in the popup when Codex needs help reading a page that
|
||||
only your authenticated Chrome session can see. That action captures only the
|
||||
current active tab, records visible text, headings, links, and visible form
|
||||
controls, and writes `state/loki.chrome_page_snapshot.cc`. It does not read
|
||||
cookies, response bodies, password values, or hidden inputs.
|
||||
|
||||
The popup also exposes `Debug sweep`. Chrome will warn that Loki can debug
|
||||
pages because the extension uses `chrome.debugger`. A sweep attaches to each
|
||||
debuggable tab, probes Chrome Debugger Protocol domains, records frame/runtime/
|
||||
|
|
@ -72,6 +78,7 @@ Loki writes:
|
|||
|
||||
- `state/loki.chrome_snapshot.cc`
|
||||
- `state/loki.chrome_debug_probe.cc`
|
||||
- `state/loki.chrome_page_snapshot.cc`
|
||||
- `state/loki.provider_advertisement.cc`
|
||||
- `state/loki.eve_surface.cc`
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ CultMesh/Eve consumers while keeping Chrome installation easy for the operator.
|
|||
## Current Mechanism
|
||||
|
||||
The Chrome extension observes tab metadata with explicit Chrome permissions and
|
||||
posts reports to Loki's localhost ingest endpoint. On command, it also uses
|
||||
`chrome.debugger` to attach to debuggable tabs, probe Chrome Debugger Protocol
|
||||
domains, and detach. Chrome can also expose DevTools metadata on
|
||||
posts reports to Loki's localhost ingest endpoint. On explicit operator command
|
||||
from the popup, it can capture the active tab's visible page text, headings,
|
||||
links, and visible form controls for Codex-assisted navigation. On command, it
|
||||
also uses `chrome.debugger` to attach to debuggable tabs, probe Chrome Debugger
|
||||
Protocol domains, and detach. Chrome can also expose DevTools metadata on
|
||||
`127.0.0.1:{port}` when started with `--remote-debugging-port`. Loki normalizes
|
||||
these sources into one snapshot and writes typed witness documents from that
|
||||
state.
|
||||
|
|
@ -18,6 +20,7 @@ state.
|
|||
```mermaid
|
||||
flowchart LR
|
||||
Extension["Chrome extension"] --> Ingest["extension ingest"]
|
||||
Snapshot["active page snapshot"] --> Ingest
|
||||
Debugger["chrome.debugger sweep"] --> Ingest
|
||||
Chrome["Chrome CDP endpoint"] --> Observe["observe CDP"]
|
||||
Config["Loki config"] --> Ingest
|
||||
|
|
@ -35,6 +38,8 @@ flowchart LR
|
|||
- CDP reachability is observed state, not assumed capability.
|
||||
- Extension reachability is observed state, not assumed capability.
|
||||
- Debug sweeps are explicit observations, not continuous hidden attachment.
|
||||
- Page snapshots are explicit active-tab observations, not continuous page
|
||||
surveillance.
|
||||
- Chrome owns page reality; Loki owns only the observation snapshot.
|
||||
- `.cc` witnesses are the durable local state surface.
|
||||
- Eve/CultUI output is derived from the same snapshot as the witness.
|
||||
|
|
@ -45,12 +50,12 @@ flowchart LR
|
|||
1. Add CultCacheTS-backed binary `.cc` persistence behind the current writer.
|
||||
2. Add CultNet/CultMesh publication of the same documents.
|
||||
3. Add command handling only through `gamecult.eve.command.v1`.
|
||||
4. Add consentful CDP actions, starting with read-only page metadata and
|
||||
operator-approved screenshot capture.
|
||||
4. Add operator-approved screenshot capture.
|
||||
|
||||
## Command Boundary
|
||||
|
||||
Loki currently advertises no executable tab commands. This is deliberate. A
|
||||
daemon that can evaluate JavaScript inside live user tabs is holding a sharp
|
||||
tool; it needs an explicit command document, audit log, origin, target tab id,
|
||||
and denial path before it should act.
|
||||
Loki currently advertises no executable tab commands. Page snapshot capture is
|
||||
extension-owned and popup-triggered: the operator clicks once, the extension
|
||||
captures the active tab, posts the typed observation, and detaches. A future
|
||||
daemon-originated command path still needs an explicit command document, audit
|
||||
log, origin, target tab id, and denial path before it should act.
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ dl {
|
|||
|
||||
dl div {
|
||||
display: grid;
|
||||
grid-template-columns: 72px 1fr;
|
||||
grid-template-columns: 88px 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,11 +30,16 @@
|
|||
<dt>Debug</dt>
|
||||
<dd id="debug">-</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Page</dt>
|
||||
<dd id="page-snapshot">-</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<p id="error" class="error" hidden></p>
|
||||
|
||||
<button id="sync" type="button">Sync now</button>
|
||||
<button id="snapshot-active-tab" type="button">Snapshot active tab</button>
|
||||
<button id="debug-sweep" type="button">Debug sweep</button>
|
||||
</main>
|
||||
<script src="popup.js" type="module"></script>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ const tabsEl = document.querySelector("#tabs");
|
|||
const lastSyncEl = document.querySelector("#last-sync");
|
||||
const endpointEl = document.querySelector("#endpoint");
|
||||
const debugEl = document.querySelector("#debug");
|
||||
const pageSnapshotEl = document.querySelector("#page-snapshot");
|
||||
const errorEl = document.querySelector("#error");
|
||||
const syncButton = document.querySelector("#sync");
|
||||
const snapshotActiveTabButton = document.querySelector("#snapshot-active-tab");
|
||||
const debugSweepButton = document.querySelector("#debug-sweep");
|
||||
|
||||
syncButton.addEventListener("click", async () => {
|
||||
|
|
@ -21,6 +23,13 @@ debugSweepButton.addEventListener("click", async () => {
|
|||
debugSweepButton.disabled = false;
|
||||
});
|
||||
|
||||
snapshotActiveTabButton.addEventListener("click", async () => {
|
||||
snapshotActiveTabButton.disabled = true;
|
||||
await chrome.runtime.sendMessage({ type: "loki.snapshot-active-tab" });
|
||||
await render();
|
||||
snapshotActiveTabButton.disabled = false;
|
||||
});
|
||||
|
||||
await render();
|
||||
|
||||
async function render() {
|
||||
|
|
@ -33,6 +42,7 @@ async function render() {
|
|||
stateEl.className = "state";
|
||||
tabsEl.textContent = "-";
|
||||
debugEl.textContent = "-";
|
||||
pageSnapshotEl.textContent = "-";
|
||||
lastSyncEl.textContent = "-";
|
||||
errorEl.hidden = true;
|
||||
return;
|
||||
|
|
@ -42,6 +52,7 @@ async function render() {
|
|||
stateEl.className = `state ${status.ok ? "ok" : "bad"}`;
|
||||
tabsEl.textContent = String(status.tabCount ?? 0);
|
||||
debugEl.textContent = formatDebugProbe(status.debugProbe);
|
||||
pageSnapshotEl.textContent = formatPageSnapshot(status.pageSnapshot);
|
||||
lastSyncEl.textContent = status.lastSyncAt ? new Date(status.lastSyncAt).toLocaleTimeString() : "-";
|
||||
|
||||
if (status.error) {
|
||||
|
|
@ -57,3 +68,11 @@ function formatDebugProbe(debugProbe) {
|
|||
const summary = debugProbe.summary ?? {};
|
||||
return `${summary.attachedTabCount ?? 0}/${summary.attemptedTabCount ?? 0} attached`;
|
||||
}
|
||||
|
||||
function formatPageSnapshot(pageSnapshot) {
|
||||
if (!pageSnapshot) return "not captured";
|
||||
if (pageSnapshot.error) return pageSnapshot.error;
|
||||
const textLength = pageSnapshot.content?.visibleTextLength ?? 0;
|
||||
const controlCount = pageSnapshot.forms?.controlCount ?? 0;
|
||||
return `${textLength} chars, ${controlCount} controls`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||
debugSweep().then(sendResponse);
|
||||
return true;
|
||||
}
|
||||
if (message?.type === "loki.snapshot-active-tab") {
|
||||
snapshotActiveTab().then(sendResponse);
|
||||
return true;
|
||||
}
|
||||
if (message?.type === "loki.get-status") {
|
||||
getStatus().then(sendResponse);
|
||||
return true;
|
||||
|
|
@ -63,10 +67,11 @@ async function syncTabs(reason, extra = {}) {
|
|||
activeTabCount: tabs.filter((tab) => tab.active).length,
|
||||
},
|
||||
redaction: {
|
||||
pageContent: "not-read",
|
||||
pageContent: extra.pageSnapshot ? "explicit-active-tab-visible-text" : "not-read",
|
||||
cookies: "not-read",
|
||||
tabMetadata: "title-url-favicon-window-state",
|
||||
debugProbe: extra.debugProbe ? "runtime-frame-performance-security-metadata" : "not-run",
|
||||
pageSnapshot: extra.pageSnapshot ? extra.pageSnapshot.redaction : "not-run",
|
||||
},
|
||||
...extra,
|
||||
};
|
||||
|
|
@ -89,6 +94,7 @@ async function syncTabs(reason, extra = {}) {
|
|||
lastSyncAt: new Date().toISOString(),
|
||||
tabCount: tabs.length,
|
||||
debugProbe: responseBody.debugProbe ?? null,
|
||||
pageSnapshot: responseBody.pageSnapshot ?? null,
|
||||
activeSource: responseBody.activeSource ?? "unknown",
|
||||
error: null,
|
||||
};
|
||||
|
|
@ -103,6 +109,7 @@ async function syncTabs(reason, extra = {}) {
|
|||
lastSyncAt: new Date().toISOString(),
|
||||
tabCount: 0,
|
||||
debugProbe: null,
|
||||
pageSnapshot: null,
|
||||
activeSource: "none",
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
|
|
@ -111,6 +118,164 @@ async function syncTabs(reason, extra = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
async function snapshotActiveTab() {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
const startedAt = new Date().toISOString();
|
||||
if (!tab || !Number.isInteger(tab.id)) {
|
||||
return syncTabs("active-page-snapshot", {
|
||||
pageSnapshot: failedPageSnapshot(startedAt, "No active tab is available."),
|
||||
});
|
||||
}
|
||||
|
||||
const pageSnapshot = await capturePageSnapshot(tab, startedAt);
|
||||
return syncTabs("active-page-snapshot", { pageSnapshot });
|
||||
}
|
||||
|
||||
async function capturePageSnapshot(tab, startedAt) {
|
||||
const target = { tabId: tab.id };
|
||||
const pageSnapshot = {
|
||||
schema: "gamecult.loki.chrome_page_snapshot.v0",
|
||||
capturedAt: startedAt,
|
||||
completedAt: null,
|
||||
tab: projectTab(tab),
|
||||
attached: false,
|
||||
attachError: null,
|
||||
error: null,
|
||||
content: null,
|
||||
forms: null,
|
||||
redaction: {
|
||||
trigger: "operator-popup-click",
|
||||
scope: "active-tab-only",
|
||||
pageContent: "visible-text-and-page-structure",
|
||||
cookies: "not-read",
|
||||
passwordValues: "redacted",
|
||||
hiddenInputs: "omitted",
|
||||
responseBodies: "not-read",
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await chromeDebuggerAttach(target, "1.3");
|
||||
pageSnapshot.attached = true;
|
||||
await chromeDebuggerSendCommand(target, "Runtime.enable");
|
||||
const result = await chromeDebuggerSendCommand(target, "Runtime.evaluate", {
|
||||
expression: pageSnapshotExpression(),
|
||||
returnByValue: true,
|
||||
awaitPromise: true,
|
||||
});
|
||||
const value = result?.result?.value;
|
||||
if (!value || typeof value !== "object") {
|
||||
throw new Error("Page snapshot did not return an object.");
|
||||
}
|
||||
pageSnapshot.content = value.content ?? null;
|
||||
pageSnapshot.forms = value.forms ?? null;
|
||||
} catch (error) {
|
||||
pageSnapshot.error = error instanceof Error ? error.message : String(error);
|
||||
if (!pageSnapshot.attached) pageSnapshot.attachError = pageSnapshot.error;
|
||||
} finally {
|
||||
pageSnapshot.completedAt = new Date().toISOString();
|
||||
if (pageSnapshot.attached) {
|
||||
await chromeDebuggerDetach(target).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
return pageSnapshot;
|
||||
}
|
||||
|
||||
function failedPageSnapshot(startedAt, error) {
|
||||
return {
|
||||
schema: "gamecult.loki.chrome_page_snapshot.v0",
|
||||
capturedAt: startedAt,
|
||||
completedAt: new Date().toISOString(),
|
||||
tab: null,
|
||||
attached: false,
|
||||
attachError: error,
|
||||
error,
|
||||
content: null,
|
||||
forms: null,
|
||||
redaction: {
|
||||
trigger: "operator-popup-click",
|
||||
scope: "active-tab-only",
|
||||
pageContent: "not-read",
|
||||
cookies: "not-read",
|
||||
passwordValues: "not-read",
|
||||
hiddenInputs: "not-read",
|
||||
responseBodies: "not-read",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function pageSnapshotExpression() {
|
||||
return `(() => {
|
||||
const limit = (value, max) => String(value ?? "").replace(/\\s+/g, " ").trim().slice(0, max);
|
||||
const selectorFor = (element) => {
|
||||
if (!element) return null;
|
||||
if (element.id) return "#" + CSS.escape(element.id);
|
||||
const name = element.getAttribute("name");
|
||||
if (name) return element.tagName.toLowerCase() + "[name=" + JSON.stringify(name) + "]";
|
||||
return element.tagName.toLowerCase();
|
||||
};
|
||||
const labelFor = (control) => {
|
||||
const explicit = control.id ? document.querySelector("label[for=" + JSON.stringify(control.id) + "]") : null;
|
||||
const implicit = control.closest("label");
|
||||
const aria = control.getAttribute("aria-label") || control.getAttribute("title") || control.getAttribute("placeholder");
|
||||
return limit(explicit?.innerText || implicit?.innerText || aria || "", 300);
|
||||
};
|
||||
const controls = Array.from(document.querySelectorAll("input, select, textarea, button"))
|
||||
.filter((control) => {
|
||||
const type = (control.getAttribute("type") || control.tagName).toLowerCase();
|
||||
return type !== "hidden" && type !== "password";
|
||||
})
|
||||
.slice(0, 300)
|
||||
.map((control) => {
|
||||
const tag = control.tagName.toLowerCase();
|
||||
const type = (control.getAttribute("type") || tag).toLowerCase();
|
||||
const option = tag === "select" ? control.selectedOptions?.[0] : null;
|
||||
const value = type === "checkbox" || type === "radio"
|
||||
? String(Boolean(control.checked))
|
||||
: tag === "select"
|
||||
? limit(option?.innerText || option?.label || "", 500)
|
||||
: limit(control.value || "", 500);
|
||||
return {
|
||||
tag,
|
||||
type,
|
||||
label: labelFor(control),
|
||||
selector: selectorFor(control),
|
||||
name: control.getAttribute("name"),
|
||||
id: control.id || null,
|
||||
disabled: Boolean(control.disabled),
|
||||
required: Boolean(control.required),
|
||||
value,
|
||||
};
|
||||
});
|
||||
const links = Array.from(document.links).slice(0, 200).map((link) => ({
|
||||
text: limit(link.innerText || link.textContent || "", 300),
|
||||
href: link.href,
|
||||
}));
|
||||
const headings = Array.from(document.querySelectorAll("h1,h2,h3,h4,h5,h6")).slice(0, 80).map((heading) => ({
|
||||
level: heading.tagName.toLowerCase(),
|
||||
text: limit(heading.innerText || heading.textContent || "", 500),
|
||||
}));
|
||||
const visibleText = limit(document.body?.innerText || "", 60000);
|
||||
return {
|
||||
content: {
|
||||
href: location.href,
|
||||
title: document.title,
|
||||
readyState: document.readyState,
|
||||
language: document.documentElement.lang || null,
|
||||
visibleText,
|
||||
visibleTextLength: visibleText.length,
|
||||
headings,
|
||||
links,
|
||||
},
|
||||
forms: {
|
||||
controlCount: controls.length,
|
||||
controls,
|
||||
},
|
||||
};
|
||||
})()`;
|
||||
}
|
||||
|
||||
async function debugSweep() {
|
||||
const startedAt = new Date().toISOString();
|
||||
const tabs = await chrome.tabs.query({});
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ function normalizeExtensionObservation(payload, config) {
|
|||
maxAgeMs: config.intervalMs * 3,
|
||||
},
|
||||
debugProbe: normalizeDebugProbe(payload.debugProbe),
|
||||
pageSnapshot: normalizePageSnapshot(payload.pageSnapshot),
|
||||
tabs,
|
||||
};
|
||||
}
|
||||
|
|
@ -166,6 +167,16 @@ function normalizeDebugProbe(debugProbe) {
|
|||
};
|
||||
}
|
||||
|
||||
function normalizePageSnapshot(pageSnapshot) {
|
||||
if (!pageSnapshot || pageSnapshot.schema !== "gamecult.loki.chrome_page_snapshot.v0") return null;
|
||||
return {
|
||||
...pageSnapshot,
|
||||
providerId,
|
||||
serviceId,
|
||||
ingestedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeExtensionTab(tab) {
|
||||
return {
|
||||
id: String(tab.id ?? ""),
|
||||
|
|
@ -234,6 +245,7 @@ function buildSnapshot(config, cdpObservation, extensionObservation) {
|
|||
cdp,
|
||||
extension,
|
||||
debugProbe: extensionObservation?.debugProbe ?? null,
|
||||
pageSnapshot: extensionObservation?.pageSnapshot ?? null,
|
||||
tabs,
|
||||
summary: {
|
||||
tabCount: tabs.length,
|
||||
|
|
@ -241,14 +253,17 @@ function buildSnapshot(config, cdpObservation, extensionObservation) {
|
|||
activeTabCount: tabs.filter((tab) => tab.active).length,
|
||||
debugProbeAttachedTabCount: extensionObservation?.debugProbe?.summary?.attachedTabCount ?? 0,
|
||||
debugProbeAttemptedTabCount: extensionObservation?.debugProbe?.summary?.attemptedTabCount ?? 0,
|
||||
pageSnapshotVisibleTextLength: extensionObservation?.pageSnapshot?.content?.visibleTextLength ?? 0,
|
||||
pageSnapshotFormControlCount: extensionObservation?.pageSnapshot?.forms?.controlCount ?? 0,
|
||||
},
|
||||
redaction: {
|
||||
urlQuery: activeObservation ? "preserved" : "not-observed",
|
||||
titles: activeObservation ? "preserved" : "not-observed",
|
||||
pageContent: "not-read",
|
||||
pageContent: extensionObservation?.pageSnapshot ? "explicit-active-tab-visible-text" : "not-read",
|
||||
cookies: "not-read",
|
||||
source: activeObservation?.source ?? "none",
|
||||
debugProbe: extensionObservation?.debugProbe ? extensionObservation.debugProbe.redaction : "not-run",
|
||||
pageSnapshot: extensionObservation?.pageSnapshot ? extensionObservation.pageSnapshot.redaction : "not-run",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -287,18 +302,24 @@ function unreachableSnapshot(config, error) {
|
|||
error: "no extension report received",
|
||||
},
|
||||
tabs: [],
|
||||
summary: {
|
||||
tabCount: 0,
|
||||
inspectableTabCount: 0,
|
||||
activeTabCount: 0,
|
||||
},
|
||||
redaction: {
|
||||
urlQuery: "not-observed",
|
||||
titles: "not-observed",
|
||||
pageContent: "not-read",
|
||||
cookies: "not-read",
|
||||
source: "none",
|
||||
},
|
||||
pageSnapshot: null,
|
||||
debugProbe: null,
|
||||
summary: {
|
||||
tabCount: 0,
|
||||
inspectableTabCount: 0,
|
||||
activeTabCount: 0,
|
||||
pageSnapshotVisibleTextLength: 0,
|
||||
pageSnapshotFormControlCount: 0,
|
||||
},
|
||||
redaction: {
|
||||
urlQuery: "not-observed",
|
||||
titles: "not-observed",
|
||||
pageContent: "not-read",
|
||||
cookies: "not-read",
|
||||
source: "none",
|
||||
pageSnapshot: "not-run",
|
||||
debugProbe: "not-run",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -356,6 +377,14 @@ function buildProviderAdvertisement(snapshot) {
|
|||
cultMeshAddress: "asgard.localhost.loki.chrome/state/debug-probe",
|
||||
portable: true,
|
||||
},
|
||||
{
|
||||
schema: "gamecult.loki.chrome_page_snapshot.v0",
|
||||
owner: providerId,
|
||||
authority: "operator-approved-observation",
|
||||
storage: "cultcache-cc",
|
||||
cultMeshAddress: "asgard.localhost.loki.chrome/state/page-snapshot",
|
||||
portable: true,
|
||||
},
|
||||
{
|
||||
schema: "gamecult.eve.surface.v1",
|
||||
owner: providerId,
|
||||
|
|
@ -399,6 +428,17 @@ function buildProviderAdvertisement(snapshot) {
|
|||
updatedAt: snapshot.debugProbe?.ingestedAt ?? null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "loki.chrome.page_snapshot",
|
||||
kind: "cc-export",
|
||||
path: "state/loki.chrome_page_snapshot.cc",
|
||||
schemas: ["gamecult.loki.chrome_page_snapshot.v0"],
|
||||
redaction: snapshot.pageSnapshot?.redaction ?? "not-run",
|
||||
freshness: {
|
||||
state: snapshot.pageSnapshot ? "fresh" : "missing",
|
||||
updatedAt: snapshot.pageSnapshot?.ingestedAt ?? null,
|
||||
},
|
||||
},
|
||||
],
|
||||
surfaces: [
|
||||
{
|
||||
|
|
@ -424,8 +464,8 @@ function buildProviderAdvertisement(snapshot) {
|
|||
{
|
||||
kind: "chrome-extension-ingest",
|
||||
address: snapshot.extension.ingestEndpoint,
|
||||
carries: ["chrome-extension-tab-metadata"],
|
||||
note: "Preferred drop-in Chrome path; extension observes tab metadata and can run explicit chrome.debugger sweeps.",
|
||||
carries: ["chrome-extension-tab-metadata", "explicit-active-page-snapshot"],
|
||||
note: "Preferred drop-in Chrome path; extension observes tab metadata and can run explicit operator-triggered page snapshots and chrome.debugger sweeps.",
|
||||
},
|
||||
{
|
||||
kind: "local-cultcache-witness",
|
||||
|
|
@ -435,6 +475,7 @@ function buildProviderAdvertisement(snapshot) {
|
|||
"state/loki.provider_advertisement.cc",
|
||||
"state/loki.eve_surface.cc",
|
||||
"state/loki.chrome_debug_probe.cc",
|
||||
"state/loki.chrome_page_snapshot.cc",
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -496,6 +537,14 @@ function buildEveSurface(snapshot) {
|
|||
: "not run",
|
||||
snapshot.debugProbe ? "ok" : "muted",
|
||||
),
|
||||
metric(
|
||||
"page-snapshot",
|
||||
"Page snapshot",
|
||||
snapshot.pageSnapshot
|
||||
? `${snapshot.summary.pageSnapshotVisibleTextLength} chars`
|
||||
: "not captured",
|
||||
snapshot.pageSnapshot ? "ok" : "muted",
|
||||
),
|
||||
{
|
||||
id: "loki.chrome.extension-endpoint",
|
||||
kind: "inspector.kv",
|
||||
|
|
@ -614,6 +663,9 @@ async function publishSnapshot(config, snapshot) {
|
|||
snapshot.debugProbe
|
||||
? writeTypedDocument(join(config.stateDir, "loki.chrome_debug_probe.cc"), snapshot.debugProbe)
|
||||
: Promise.resolve(),
|
||||
snapshot.pageSnapshot
|
||||
? writeTypedDocument(join(config.stateDir, "loki.chrome_page_snapshot.cc"), snapshot.pageSnapshot)
|
||||
: Promise.resolve(),
|
||||
]);
|
||||
|
||||
return { advertisement, surface };
|
||||
|
|
@ -674,6 +726,7 @@ function startExtensionIngestServer(config) {
|
|||
activeSource: snapshot.activeSource,
|
||||
tabs: snapshot.summary.tabCount,
|
||||
debugProbe: snapshot.debugProbe ? snapshot.debugProbe : null,
|
||||
pageSnapshot: snapshot.pageSnapshot ? snapshot.pageSnapshot : null,
|
||||
observedAt: snapshot.observedAt,
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -74,6 +74,51 @@ try {
|
|||
runtimeEvaluation: "fixture",
|
||||
},
|
||||
},
|
||||
pageSnapshot: {
|
||||
schema: "gamecult.loki.chrome_page_snapshot.v0",
|
||||
capturedAt: new Date().toISOString(),
|
||||
completedAt: new Date().toISOString(),
|
||||
tab: {
|
||||
id: 100,
|
||||
windowId: 1,
|
||||
index: 0,
|
||||
active: true,
|
||||
highlighted: true,
|
||||
pinned: false,
|
||||
audible: false,
|
||||
muted: false,
|
||||
incognito: false,
|
||||
title: "Portal das Financas",
|
||||
url: "https://sitfiscal.portaldasfinancas.gov.pt/geral/siteMap",
|
||||
favIconUrl: null,
|
||||
},
|
||||
attached: true,
|
||||
attachError: null,
|
||||
error: null,
|
||||
content: {
|
||||
href: "https://sitfiscal.portaldasfinancas.gov.pt/geral/siteMap",
|
||||
title: "Portal das Financas",
|
||||
readyState: "complete",
|
||||
language: "pt",
|
||||
visibleText: "Entregar declaracao IRS Consultar pagamentos",
|
||||
visibleTextLength: 43,
|
||||
headings: [{ level: "h1", text: "Portal das Financas" }],
|
||||
links: [{ text: "Consultar pagamentos", href: "https://sitfiscal.portaldasfinancas.gov.pt/pagamentos" }],
|
||||
},
|
||||
forms: {
|
||||
controlCount: 1,
|
||||
controls: [{ tag: "button", type: "button", label: "", selector: "button", name: null, id: null, disabled: false, required: false, value: "" }],
|
||||
},
|
||||
redaction: {
|
||||
trigger: "operator-popup-click",
|
||||
scope: "active-tab-only",
|
||||
pageContent: "visible-text-and-page-structure",
|
||||
cookies: "not-read",
|
||||
passwordValues: "redacted",
|
||||
hiddenInputs: "omitted",
|
||||
responseBodies: "not-read",
|
||||
},
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
id: 100,
|
||||
|
|
@ -99,6 +144,7 @@ try {
|
|||
|
||||
const snapshotText = await readFile(join(stateDir, "loki.chrome_snapshot.cc"), "utf8");
|
||||
const debugProbeText = await readFile(join(stateDir, "loki.chrome_debug_probe.cc"), "utf8");
|
||||
const pageSnapshotText = await readFile(join(stateDir, "loki.chrome_page_snapshot.cc"), "utf8");
|
||||
if (!snapshotText.includes('"activeSource": "chrome-extension"')) {
|
||||
throw new Error("snapshot did not choose chrome-extension as active source");
|
||||
}
|
||||
|
|
@ -108,6 +154,12 @@ try {
|
|||
if (!debugProbeText.includes('"schema": "gamecult.loki.chrome_debug_probe.v0"')) {
|
||||
throw new Error("debug probe witness was not written");
|
||||
}
|
||||
if (!pageSnapshotText.includes('"schema": "gamecult.loki.chrome_page_snapshot.v0"')) {
|
||||
throw new Error("page snapshot witness was not written");
|
||||
}
|
||||
if (!pageSnapshotText.includes("Entregar declaracao IRS")) {
|
||||
throw new Error("page snapshot did not preserve visible page text");
|
||||
}
|
||||
|
||||
console.log("extension ingest smoke passed");
|
||||
} finally {
|
||||
|
|
|
|||
Reference in a new issue