Implement passive consent evidence workflow with extension-driven capture

Dieser Commit ist enthalten in:
2026-06-14 18:15:38 +02:00
Ursprung 3a174128e0
Commit 1f10389016
6 geänderte Dateien mit 751 neuen und 79 gelöschten Zeilen
+443 -77
Datei anzeigen
@@ -9,11 +9,13 @@ const GVL_AUTO_UPDATE_SOURCE = "extension_startup";
const AUTO_GVL_CHECK_THROTTLE_MS = 24 * 60 * 60 * 1000; const AUTO_GVL_CHECK_THROTTLE_MS = 24 * 60 * 60 * 1000;
const AUTO_GVL_CHECK_STORAGE_KEY = "vendorgetAutoGvlUpdateStatus"; const AUTO_GVL_CHECK_STORAGE_KEY = "vendorgetAutoGvlUpdateStatus";
const CONSENT_CAPTURE_SESSION_TIMEOUT_MS = 10 * 60 * 1000; const CONSENT_CAPTURE_SESSION_TIMEOUT_MS = 10 * 60 * 1000;
const CONSENT_CAPTURE_BADGE_BLINK_MS = 700;
let isAutoGvlCheckRunning = false; let isAutoGvlCheckRunning = false;
let lastAutoGvlCheckStartedAt = null; let lastAutoGvlCheckStartedAt = null;
let latestGvlUpdateStatus = null; let latestGvlUpdateStatus = null;
const consentCaptureSessions = new Map(); const consentCaptureSessions = new Map();
const consentCaptureBadgeTimers = new Map();
browser.runtime.onMessage.addListener((message, sender) => browser.runtime.onMessage.addListener((message, sender) =>
handleVendorGetMessage(message, sender) handleVendorGetMessage(message, sender)
@@ -30,7 +32,7 @@ browser.tabs.onRemoved.addListener((tabId) => {
browser.tabs.onUpdated.addListener((tabId, changeInfo) => { browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.url) { if (changeInfo.url) {
cleanupConsentCaptureSessionsForTab(tabId, "page_changed"); cleanupConsentCaptureSessionsForTab(tabId, "page_changed", changeInfo.url);
} }
}); });
@@ -133,6 +135,22 @@ async function handleVendorGetMessage(message, sender) {
return handleGetLatestConsentStateMessage(); return handleGetLatestConsentStateMessage();
} }
if (message.type === "get_pre_consent_capture_status") {
return handleGetPreConsentCaptureStatusMessage(message);
}
if (message.type === "start_pre_consent_capture") {
return handleStartPreConsentCaptureMessage(message);
}
if (message.type === "decline_pre_consent_capture") {
return handleDeclinePreConsentCaptureMessage(message);
}
if (message.type === "probe_pre_consent_capture_for_tab") {
return handleProbePreConsentCaptureForTabMessage(message);
}
if (message.type === "get_consent_evidence_chain") { if (message.type === "get_consent_evidence_chain") {
return handleGetConsentEvidenceChainMessage(message); return handleGetConsentEvidenceChainMessage(message);
} }
@@ -1003,6 +1021,178 @@ async function handleGetLatestConsentStateMessage() {
}; };
} }
async function handleGetPreConsentCaptureStatusMessage(message) {
const tabId = normalizeTabId(message?.payload?.tabId);
const session = getOpenConsentCaptureSessionForTab(tabId);
return {
success: true,
capture: session ? buildPreConsentCaptureStatus(session) : null
};
}
async function handleStartPreConsentCaptureMessage(message) {
const tabId = normalizeTabId(message?.payload?.tabId);
const session = getOpenConsentCaptureSessionForTab(tabId);
if (!session || session.status !== "attention") {
return {
success: false,
error: "pre_consent_capture_not_available"
};
}
session.status = "recording";
session.captureSessionId = createCaptureSessionId();
updateConsentCaptureBadge(session);
const bufferedEvents = session.bufferedPreConsentEvents;
session.bufferedPreConsentEvents = [];
for (const bufferedEvent of bufferedEvents) {
await persistProviderAnnouncementEvent({
captureSessionId: session.captureSessionId,
rawCapture: bufferedEvent.rawCapture,
sender: bufferedEvent.sender
});
}
return {
success: true,
capture: buildPreConsentCaptureStatus(session)
};
}
async function handleDeclinePreConsentCaptureMessage(message) {
const tabId = normalizeTabId(message?.payload?.tabId);
const session = getOpenConsentCaptureSessionForTab(tabId);
if (!session) {
return {
success: false,
error: "pre_consent_capture_not_available"
};
}
declineConsentCaptureSession(session);
return {
success: true,
capture: null
};
}
async function handleProbePreConsentCaptureForTabMessage(message) {
const tabId = normalizeTabId(message?.payload?.tabId);
if (tabId === null || !(await isConsentCaptureEnabled())) {
return {
success: true,
capture: null
};
}
if (hasCompletedConsentCaptureSessionForTab(tabId)) {
return {
success: true,
capture: null
};
}
const existingSession = getOpenConsentCaptureSessionForTab(tabId);
if (existingSession) {
return {
success: true,
capture: buildPreConsentCaptureStatus(existingSession)
};
}
let tab = null;
try {
tab = await browser.tabs.get(tabId);
} catch (error) {
return {
success: true,
capture: null
};
}
let probe = null;
try {
probe = await browser.tabs.sendMessage(tabId, {
type: "probe_tcf_state"
});
} catch (error) {
return {
success: true,
capture: null
};
}
const rawCapture = probe?.capture ?? null;
if (!probe?.success || !isProbeOpenPreConsentCapture(rawCapture, probe.source)) {
return {
success: true,
capture: null
};
}
const sender = {
tab,
frameId: 0,
url: tab?.url ?? null
};
const session = startAttentionConsentCaptureSession(rawCapture, sender);
return {
success: true,
capture: session ? buildPreConsentCaptureStatus(session) : null
};
}
function normalizeTabId(value) {
const tabId = Number(value);
return Number.isInteger(tabId) ? tabId : null;
}
function getOpenConsentCaptureSessionForTab(tabId) {
if (tabId === null) {
return null;
}
for (const session of consentCaptureSessions.values()) {
if (
session.tabId === tabId &&
(session.status === "attention" || session.status === "recording")
) {
return session;
}
}
return null;
}
function buildPreConsentCaptureStatus(session) {
return {
captureSessionId: session.captureSessionId ?? null,
status: session.status,
tabId: session.tabId,
frameId: session.frameId,
eventCount: session.bufferedPreConsentEvents?.length ?? 0,
firstEventStatus:
session.bufferedPreConsentEvents?.[0]?.rawCapture?.eventStatus ?? null,
page: session.page ?? null,
detectedAt: session.detectedAt ?? null,
updatedAt: session.updatedAt ?? null
};
}
async function handleGetConsentEvidenceChainMessage(message) { async function handleGetConsentEvidenceChainMessage(message) {
const stateFingerprint = normalizeStateFingerprint( const stateFingerprint = normalizeStateFingerprint(
message?.payload?.stateFingerprint message?.payload?.stateFingerprint
@@ -2142,19 +2332,94 @@ function buildConsentEventDiagnostics(rawCapture, extraDiagnostics) {
function startConsentCaptureSessionTimeout(session) { function startConsentCaptureSessionTimeout(session) {
session.timeoutId = setTimeout(() => { session.timeoutId = setTimeout(() => {
cleanupIncompleteConsentCaptureSession(session, "timeout"); timeoutConsentCaptureSession(session);
}, CONSENT_CAPTURE_SESSION_TIMEOUT_MS); }, CONSENT_CAPTURE_SESSION_TIMEOUT_MS);
} }
function isOpenPreConsentCapture(rawCapture) {
const eventStatus = rawCapture?.eventStatus ?? null;
return eventStatus === "cmpuishown" || eventStatus === "tcloaded";
}
function isProbeOpenPreConsentCapture(rawCapture, source) {
if (source === "content_memory") {
return isOpenPreConsentCapture(rawCapture);
}
const eventStatus = rawCapture?.eventStatus ?? null;
if (eventStatus === "cmpuishown") {
return true;
}
if (eventStatus !== "tcloaded") {
return false;
}
return !rawCapture?.tcString && !rawCapture?.addtlConsent;
}
function startAttentionConsentCaptureSession(rawCapture, sender) {
if (!isOpenPreConsentCapture(rawCapture)) {
return null;
}
const sessionKey = getConsentCaptureSessionKey(sender);
if (!sessionKey) {
return null;
}
if (hasCompletedConsentCaptureSessionForTab(sender?.tab?.id ?? null)) {
return null;
}
let session = consentCaptureSessions.get(sessionKey);
if (session?.status === "aborted" || session?.status === "completed") {
return null;
}
if (session?.status === "recording") {
return session;
}
if (!session) {
session = {
key: sessionKey,
tabId: sender?.tab?.id ?? null,
frameId: sender?.frameId ?? null,
status: "attention",
captureSessionId: null,
bufferedPreConsentEvents: [],
timeoutId: null,
page: buildConsentEventPageContext(rawCapture, sender),
detectedAt: new Date().toISOString(),
updatedAt: null
};
consentCaptureSessions.set(sessionKey, session);
startConsentCaptureSessionTimeout(session);
}
session.updatedAt = new Date().toISOString();
session.bufferedPreConsentEvents.push({
rawCapture,
sender
});
updateConsentCaptureBadge(session);
return session;
}
async function handlePreConsentTcfEvent(rawCapture, sender) { async function handlePreConsentTcfEvent(rawCapture, sender) {
if (isEvidenceWriteSuspended()) { if (isEvidenceWriteSuspended()) {
console.info("VG-Observe pre-consent capture skipped: maintenance mode"); console.info("VG-Observe pre-consent capture skipped: maintenance mode");
return; return;
} }
const eventStatus = rawCapture?.eventStatus ?? null; if (!isOpenPreConsentCapture(rawCapture)) {
if (eventStatus !== "cmpuishown" && eventStatus !== "tcloaded") {
return; return;
} }
@@ -2164,13 +2429,17 @@ async function handlePreConsentTcfEvent(rawCapture, sender) {
return; return;
} }
if (hasCompletedConsentCaptureSessionForTab(sender?.tab?.id ?? null)) {
return;
}
let session = consentCaptureSessions.get(sessionKey); let session = consentCaptureSessions.get(sessionKey);
if (session?.status === "declined" || session?.status === "completed") { if (session?.status === "aborted" || session?.status === "completed") {
return; return;
} }
if (session?.status === "active") { if (session?.status === "recording") {
await persistProviderAnnouncementEvent({ await persistProviderAnnouncementEvent({
captureSessionId: session.captureSessionId, captureSessionId: session.captureSessionId,
rawCapture, rawCapture,
@@ -2179,84 +2448,34 @@ async function handlePreConsentTcfEvent(rawCapture, sender) {
return; return;
} }
if (!session) { startAttentionConsentCaptureSession(rawCapture, sender);
session = {
key: sessionKey,
tabId: sender?.tab?.id ?? null,
frameId: sender?.frameId ?? null,
status: "prompting",
captureSessionId: null,
bufferedPreConsentEvents: [],
timeoutId: null
};
consentCaptureSessions.set(sessionKey, session);
startConsentCaptureSessionTimeout(session);
}
session.bufferedPreConsentEvents.push({
rawCapture,
sender
});
if (session.promptInProgress) {
return;
}
session.promptInProgress = true;
const accepted = await askUserForConsentCapture(sender);
if (!consentCaptureSessions.has(sessionKey)) {
return;
}
session.promptInProgress = false;
if (!accepted) {
session.status = "declined";
session.bufferedPreConsentEvents = [];
return;
}
session.status = "active";
session.captureSessionId = createCaptureSessionId();
const bufferedEvents = session.bufferedPreConsentEvents;
session.bufferedPreConsentEvents = [];
for (const bufferedEvent of bufferedEvents) {
await persistProviderAnnouncementEvent({
captureSessionId: session.captureSessionId,
rawCapture: bufferedEvent.rawCapture,
sender: bufferedEvent.sender
});
}
}
async function askUserForConsentCapture(sender) {
console.info(
"VG-Observe pre-consent provider announcement capture inactive: extension-owned consent prompt is not implemented",
{
tabId: sender?.tab?.id ?? null,
frameId: sender?.frameId ?? null
}
);
return false;
} }
function getActiveConsentCaptureSessionId(sender) { function getActiveConsentCaptureSessionId(sender) {
const sessionKey = getConsentCaptureSessionKey(sender); const sessionKey = getConsentCaptureSessionKey(sender);
const session = sessionKey ? consentCaptureSessions.get(sessionKey) : null; const session = sessionKey ? consentCaptureSessions.get(sessionKey) : null;
if (!session || session.status !== "active") { if (!session || session.status !== "recording") {
return null; return null;
} }
return session.captureSessionId ?? null; return session.captureSessionId ?? null;
} }
function hasCompletedConsentCaptureSessionForTab(tabId) {
if (tabId === null || tabId === undefined) {
return false;
}
for (const session of consentCaptureSessions.values()) {
if (session.tabId === tabId && session.status === "completed") {
return true;
}
}
return false;
}
function completeConsentCaptureSession(sender) { function completeConsentCaptureSession(sender) {
const sessionKey = getConsentCaptureSessionKey(sender); const sessionKey = getConsentCaptureSessionKey(sender);
let session = sessionKey ? consentCaptureSessions.get(sessionKey) : null; let session = sessionKey ? consentCaptureSessions.get(sessionKey) : null;
@@ -2282,30 +2501,57 @@ function completeConsentCaptureSession(sender) {
session.status = "completed"; session.status = "completed";
session.completedAt = new Date().toISOString(); session.completedAt = new Date().toISOString();
session.bufferedPreConsentEvents = []; session.bufferedPreConsentEvents = [];
session.promptInProgress = false;
if (session.timeoutId) { if (session.timeoutId) {
clearTimeout(session.timeoutId); clearTimeout(session.timeoutId);
session.timeoutId = null; session.timeoutId = null;
} }
resetConsentCaptureBadge(session.tabId);
} }
function cleanupConsentCaptureSessionsForTab(tabId, reason) { function cleanupConsentCaptureSessionsForTab(tabId, reason, nextUrl) {
consentCaptureSessions.forEach((session) => { consentCaptureSessions.forEach((session) => {
if (session.tabId === tabId) { if (session.tabId === tabId) {
if (
reason === "page_changed" &&
session.status === "completed" &&
isSameConsentCaptureUrl(session.page?.url, nextUrl)
) {
return;
}
cleanupIncompleteConsentCaptureSession(session, reason); cleanupIncompleteConsentCaptureSession(session, reason);
} }
}); });
} }
function isSameConsentCaptureUrl(left, right) {
if (!left || !right) {
return false;
}
return String(left) === String(right);
}
function cleanupIncompleteConsentCaptureSession(session, reason) { function cleanupIncompleteConsentCaptureSession(session, reason) {
const wasCompleted = session.status === "completed";
if (session.timeoutId) { if (session.timeoutId) {
clearTimeout(session.timeoutId); clearTimeout(session.timeoutId);
} }
consentCaptureSessions.delete(session.key); if (!wasCompleted) {
session.status = "aborted";
session.abortedAt = new Date().toISOString();
session.abortReason = reason;
session.bufferedPreConsentEvents = [];
}
if (!session.captureSessionId || session.status === "completed") { consentCaptureSessions.delete(session.key);
resetConsentCaptureBadge(session.tabId);
if (!session.captureSessionId || wasCompleted) {
return; return;
} }
@@ -2317,6 +2563,126 @@ function cleanupIncompleteConsentCaptureSession(session, reason) {
}); });
} }
function declineConsentCaptureSession(session) {
if (session.timeoutId) {
clearTimeout(session.timeoutId);
session.timeoutId = null;
}
session.status = "aborted";
session.abortedAt = new Date().toISOString();
session.abortReason = "user_declined";
session.bufferedPreConsentEvents = [];
resetConsentCaptureBadge(session.tabId);
}
function timeoutConsentCaptureSession(session) {
if (!consentCaptureSessions.has(session.key)) {
return;
}
if (session.status === "completed") {
return;
}
session.timeoutId = null;
session.status = "aborted";
session.abortedAt = new Date().toISOString();
session.abortReason = "timeout";
session.bufferedPreConsentEvents = [];
resetConsentCaptureBadge(session.tabId);
if (!session.captureSessionId) {
return;
}
deleteProviderAnnouncementEventsForSession(
session.captureSessionId,
"timeout"
).catch((error) => {
console.warn("VG-Observe provider announcement cleanup failed", error);
});
}
function getActionApi() {
return browser.action ?? browser.browserAction ?? null;
}
function updateConsentCaptureBadge(session) {
const actionApi = getActionApi();
const tabId = session?.tabId;
if (!actionApi || tabId === null || tabId === undefined) {
return;
}
const badgeText = session.status === "recording" ? "REC" : "!";
const badgeColor = session.status === "recording" ? "#2563eb" : "#dc2626";
stopConsentCaptureBadgeBlink(tabId);
let visible = true;
setActionBadgeBackgroundColor(actionApi, { tabId, color: badgeColor });
setActionBadgeText(actionApi, { tabId, text: badgeText });
const timerId = setInterval(() => {
visible = !visible;
setActionBadgeText(actionApi, {
tabId,
text: visible ? badgeText : ""
});
}, CONSENT_CAPTURE_BADGE_BLINK_MS);
consentCaptureBadgeTimers.set(tabId, timerId);
}
function resetConsentCaptureBadge(tabId) {
const actionApi = getActionApi();
stopConsentCaptureBadgeBlink(tabId);
if (!actionApi || tabId === null || tabId === undefined) {
return;
}
setActionBadgeText(actionApi, { tabId, text: "" });
}
function stopConsentCaptureBadgeBlink(tabId) {
const timerId = consentCaptureBadgeTimers.get(tabId);
if (!timerId) {
return;
}
clearInterval(timerId);
consentCaptureBadgeTimers.delete(tabId);
}
function setActionBadgeText(actionApi, details) {
try {
const result = actionApi.setBadgeText(details);
if (result?.catch) {
result.catch(() => {});
}
} catch (error) {
// Badge updates are best-effort extension UI only.
}
}
function setActionBadgeBackgroundColor(actionApi, details) {
try {
const result = actionApi.setBadgeBackgroundColor(details);
if (result?.catch) {
result.catch(() => {});
}
} catch (error) {
// Badge updates are best-effort extension UI only.
}
}
function getLatestGvlSnapshotByVendorListVersion(db) { function getLatestGvlSnapshotByVendorListVersion(db) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly"); const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
+78
Datei anzeigen
@@ -1,9 +1,87 @@
console.log("VendorGet content listener loaded:", window.location.href); console.log("VendorGet content listener loaded:", window.location.href);
let latestObservedPreConsentCapture = null;
let latestObservedConsentCompleted = false;
browser.runtime.onMessage.addListener((message) => {
if (message?.type !== "probe_tcf_state") {
return undefined;
}
return probeTcfState();
});
function probeTcfState() {
if (latestObservedConsentCompleted) {
return Promise.resolve({
success: true,
capture: null,
source: "content_completed"
});
}
if (latestObservedPreConsentCapture && !latestObservedConsentCompleted) {
return Promise.resolve({
success: true,
capture: latestObservedPreConsentCapture,
source: "content_memory"
});
}
return new Promise((resolve) => {
const requestId = [
"tcf-probe",
Date.now().toString(36),
Math.random().toString(36).slice(2)
].join("-");
const timeoutId = setTimeout(() => {
window.removeEventListener("VendorGetTcfProbeResponse", handleResponse);
resolve({
success: false,
capture: null,
error: "tcf_probe_timeout"
});
}, 1000);
function handleResponse(event) {
if (event?.detail?.requestId !== requestId) {
return;
}
clearTimeout(timeoutId);
window.removeEventListener("VendorGetTcfProbeResponse", handleResponse);
resolve({
success: event.detail.success === true,
capture: event.detail.capture ?? null,
source: "tcf_get_tc_data"
});
}
window.addEventListener("VendorGetTcfProbeResponse", handleResponse);
window.dispatchEvent(new CustomEvent("VendorGetTcfProbeRequest", {
detail: {
requestId
}
}));
});
}
window.addEventListener("VendorGetFromPage", async (event) => { window.addEventListener("VendorGetFromPage", async (event) => {
console.log("VendorGet message from page:", event.detail); console.log("VendorGet message from page:", event.detail);
if (
event.detail?.eventName === "tcf_pre_consent_event" &&
!latestObservedConsentCompleted
) {
latestObservedPreConsentCapture = event.detail.payload ?? null;
}
if (event.detail?.eventName === "consent_capture") {
latestObservedConsentCompleted = true;
latestObservedPreConsentCapture = null;
}
await browser.runtime.sendMessage({ await browser.runtime.sendMessage({
type: "vendorget_capture", type: "vendorget_capture",
payload: event.detail payload: event.detail
+14
Datei anzeigen
@@ -115,6 +115,20 @@
}; };
} }
window.addEventListener("VendorGetTcfProbeRequest", function (event) {
const requestId = event?.detail?.requestId ?? null;
window.__tcfapi("getTCData", 2, function (tcData, success) {
window.dispatchEvent(new CustomEvent("VendorGetTcfProbeResponse", {
detail: {
requestId: requestId,
success: success,
capture: success && tcData ? buildTcfEventCapture(tcData) : null
}
}));
});
});
window.__tcfapi("ping", 2, function (pingData, pingSuccess) { window.__tcfapi("ping", 2, function (pingData, pingSuccess) {
console.log("VendorGet __tcfapi ping:", { console.log("VendorGet __tcfapi ping:", {
+36
Datei anzeigen
@@ -123,6 +123,42 @@ h1 {
display: none; display: none;
} }
.pre-consent-capture {
display: grid;
gap: 8px;
margin-bottom: 14px;
padding: 10px;
border: 1px solid #6366f1;
border-radius: 6px;
background: #172033;
}
.pre-consent-capture[hidden] {
display: none;
}
.pre-consent-capture h2 {
margin: 0;
font-size: 13px;
}
.pre-consent-capture p {
margin: 0;
font-size: 12px;
line-height: 1.35;
color: #cbd5e1;
}
.pre-consent-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.pre-consent-actions button[hidden] {
display: none;
}
.evidence-counts { .evidence-counts {
display: grid; display: grid;
gap: 6px; gap: 6px;
+25
Datei anzeigen
@@ -29,6 +29,31 @@
</div> </div>
</section> </section>
<section
id="pre-consent-capture"
class="pre-consent-capture"
aria-label="Pre-Consent-Erfassung"
hidden
>
<h2>Consent-Vorgang erfassen?</h2>
<p id="pre-consent-capture-summary">
CMP-Vorgang erkannt.
</p>
<div class="pre-consent-actions">
<button id="pre-consent-capture-decline" type="button">
Nein
</button>
<button id="pre-consent-capture-start" type="button">
Ja
</button>
</div>
<div
id="pre-consent-capture-status"
class="retention-status"
aria-live="polite"
></div>
</section>
<section class="evidence-retention" aria-label="Kurzstatus"> <section class="evidence-retention" aria-label="Kurzstatus">
<h2>Workspace-Kurzstatus</h2> <h2>Workspace-Kurzstatus</h2>
<div id="maintenance-warning" class="maintenance-warning" hidden> <div id="maintenance-warning" class="maintenance-warning" hidden>
+153
Datei anzeigen
@@ -8,6 +8,19 @@ const requestMonitoringStatus = document.getElementById(
); );
const consentCaptureToggle = document.getElementById("consent-capture-toggle"); const consentCaptureToggle = document.getElementById("consent-capture-toggle");
const consentCaptureStatus = document.getElementById("consent-capture-status"); const consentCaptureStatus = document.getElementById("consent-capture-status");
const preConsentCapture = document.getElementById("pre-consent-capture");
const preConsentCaptureSummary = document.getElementById(
"pre-consent-capture-summary"
);
const preConsentCaptureStart = document.getElementById(
"pre-consent-capture-start"
);
const preConsentCaptureDecline = document.getElementById(
"pre-consent-capture-decline"
);
const preConsentCaptureStatus = document.getElementById(
"pre-consent-capture-status"
);
const maintenanceWarning = document.getElementById("maintenance-warning"); const maintenanceWarning = document.getElementById("maintenance-warning");
const evidenceLockedCount = document.getElementById("evidence-locked-count"); const evidenceLockedCount = document.getElementById("evidence-locked-count");
const evidenceDashboardButton = document.getElementById( const evidenceDashboardButton = document.getElementById(
@@ -47,6 +60,7 @@ const evidenceStoreCountCells = {
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
await renderSettings(); await renderSettings();
await renderPreConsentCaptureStatus();
await renderEvidenceMaintenanceStatus(); await renderEvidenceMaintenanceStatus();
await renderEvidenceRetentionStatus(); await renderEvidenceRetentionStatus();
@@ -57,7 +71,13 @@ document.addEventListener("DOMContentLoaded", async () => {
"consentCaptureEnabled", "consentCaptureEnabled",
consentCaptureToggle.checked consentCaptureToggle.checked
); );
if (consentCaptureToggle.checked) {
await probePreConsentCaptureForActiveTab();
}
await renderSettings(); await renderSettings();
await renderPreConsentCaptureStatus();
consentCaptureToggle.disabled = false; consentCaptureToggle.disabled = false;
}); });
@@ -84,6 +104,8 @@ document.addEventListener("DOMContentLoaded", async () => {
evidencePurgeUnlockedButton.addEventListener("click", openPurgeConfirmModal); evidencePurgeUnlockedButton.addEventListener("click", openPurgeConfirmModal);
evidencePurgeCancelButton.addEventListener("click", closePurgeConfirmModal); evidencePurgeCancelButton.addEventListener("click", closePurgeConfirmModal);
evidencePurgeConfirmButton.addEventListener("click", purgeUnlockedEvidence); evidencePurgeConfirmButton.addEventListener("click", purgeUnlockedEvidence);
preConsentCaptureStart.addEventListener("click", startPreConsentCapture);
preConsentCaptureDecline.addEventListener("click", declinePreConsentCapture);
}); });
async function renderSettings() { async function renderSettings() {
@@ -158,6 +180,137 @@ function renderEvidenceRetentionMessage(message) {
evidenceRetentionStatus.textContent = message; evidenceRetentionStatus.textContent = message;
} }
async function renderPreConsentCaptureStatus() {
try {
const activeTab = await getActiveTab();
if (!activeTab?.id) {
renderNoPreConsentCapture();
return;
}
const result = await browser.runtime.sendMessage({
type: "get_pre_consent_capture_status",
payload: {
tabId: activeTab.id
}
});
if (!result?.success) {
throw new Error(result?.error ?? "get_pre_consent_capture_status_failed");
}
renderPreConsentCapture(result.capture);
} catch (error) {
renderNoPreConsentCapture();
console.warn("VendorGet-IV pre-consent status failed", error);
}
}
async function probePreConsentCaptureForActiveTab() {
try {
const activeTab = await getActiveTab();
if (!activeTab?.id) {
return;
}
await browser.runtime.sendMessage({
type: "probe_pre_consent_capture_for_tab",
payload: {
tabId: activeTab.id
}
});
} catch (error) {
console.warn("VendorGet-IV pre-consent probe failed", error);
}
}
function renderPreConsentCapture(capture) {
if (!capture) {
renderNoPreConsentCapture();
return;
}
preConsentCapture.hidden = false;
preConsentCaptureStart.hidden = capture.status !== "attention";
preConsentCaptureDecline.hidden = capture.status !== "attention";
if (capture.status === "recording") {
preConsentCaptureSummary.textContent = "Pre-Consent-Erfassung läuft.";
preConsentCaptureStatus.textContent =
"Provider-Announcement wurde gesichert; warte auf useractioncomplete.";
return;
}
preConsentCaptureSummary.textContent = buildPreConsentCaptureSummary(capture);
preConsentCaptureStatus.textContent = "";
}
function renderNoPreConsentCapture() {
preConsentCapture.hidden = true;
preConsentCaptureSummary.textContent = "";
preConsentCaptureStatus.textContent = "";
preConsentCaptureStart.disabled = false;
preConsentCaptureDecline.disabled = false;
}
function buildPreConsentCaptureSummary(capture) {
const eventLabel = capture.firstEventStatus ?? "TCF-Event";
const eventCount = Number(capture.eventCount ?? 0);
return `${eventLabel} erkannt, ${eventCount} Pre-Consent-Event(s) gepuffert.`;
}
async function startPreConsentCapture() {
await answerPreConsentCapture("start_pre_consent_capture");
}
async function declinePreConsentCapture() {
await answerPreConsentCapture("decline_pre_consent_capture");
}
async function answerPreConsentCapture(messageType) {
preConsentCaptureStart.disabled = true;
preConsentCaptureDecline.disabled = true;
try {
const activeTab = await getActiveTab();
if (!activeTab?.id) {
throw new Error("active_tab_unavailable");
}
const result = await browser.runtime.sendMessage({
type: messageType,
payload: {
tabId: activeTab.id
}
});
if (!result?.success) {
throw new Error(result?.error ?? messageType);
}
renderPreConsentCapture(result.capture);
} catch (error) {
preConsentCaptureStatus.textContent = "Aktion konnte nicht ausgeführt werden";
console.warn("VendorGet-IV pre-consent action failed", error);
} finally {
preConsentCaptureStart.disabled = false;
preConsentCaptureDecline.disabled = false;
}
}
async function getActiveTab() {
const tabs = await browser.tabs.query({
active: true,
currentWindow: true
});
return tabs[0] ?? null;
}
function openPurgeConfirmModal() { function openPurgeConfirmModal() {
evidencePurgeConfirmModal.hidden = false; evidencePurgeConfirmModal.hidden = false;
evidencePurgeCancelButton.focus(); evidencePurgeCancelButton.focus();