"use strict"; const requestLoadedCount = document.getElementById("request-loaded-count"); const requestLatestSeen = document.getElementById("request-latest-seen"); const requestEmpty = document.getElementById("request-empty"); const requestContent = document.getElementById("request-content"); const requestList = document.getElementById("request-list"); const requestDetailSummary = document.getElementById("request-detail-summary"); const requestDetailJson = document.getElementById("request-detail-json"); const requestDetailCorrelation = document.getElementById( "request-detail-correlation" ); const requestDetailFingerprintSource = document.getElementById( "request-detail-fingerprint-source" ); const requestDetailTechnicalFields = document.getElementById( "request-detail-technical-fields" ); let observedRequests = []; let selectedRequestFingerprint = null; document.addEventListener("DOMContentLoaded", async () => { await renderObservedRequests(); }); async function renderObservedRequests() { try { const result = await browser.runtime.sendMessage({ type: "list_recent_observed_requests" }); if (!result?.success) { throw new Error(result?.error ?? "list_recent_observed_requests_failed"); } observedRequests = result.observedRequests ?? []; updateOverview(); if (observedRequests.length === 0) { renderNoObservedRequests(); return; } requestEmpty.hidden = true; requestContent.hidden = false; if (!findObservedRequest(selectedRequestFingerprint)) { selectedRequestFingerprint = observedRequests[0]?.requestFingerprint ?? null; } renderRequestList(); renderSelectedRequest(); } catch (error) { observedRequests = []; updateOverview(); requestEmpty.hidden = false; requestContent.hidden = true; requestEmpty.textContent = "Beobachtete Requests konnten nicht geladen werden."; console.warn("VG-Observe request list failed", error); } } function updateOverview() { requestLoadedCount.textContent = String(observedRequests.length); requestLatestSeen.textContent = formatNullable( observedRequests[0]?.lastSeenAt ?? null ); } function renderNoObservedRequests() { selectedRequestFingerprint = null; requestList.textContent = ""; clearRequestDetails(); requestEmpty.hidden = false; requestContent.hidden = true; requestEmpty.textContent = "Keine beobachteten Requests vorhanden."; } function renderRequestList() { requestList.textContent = ""; for (const observedRequest of observedRequests) { const row = document.createElement("tr"); const isSelected = observedRequest?.requestFingerprint === selectedRequestFingerprint; row.className = isSelected ? "is-selected" : ""; row.tabIndex = 0; row.setAttribute("role", "button"); row.setAttribute("aria-pressed", isSelected ? "true" : "false"); row.addEventListener("click", () => { selectObservedRequest(observedRequest?.requestFingerprint ?? null); }); row.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); selectObservedRequest(observedRequest?.requestFingerprint ?? null); } }); appendListCell(row, formatNullable(observedRequest?.lastSeenAt)); appendListCell( row, formatNullable(observedRequest?.request?.origin), "origin-cell" ); appendListCell( row, formatNullable(observedRequest?.request?.pathname), "path-cell" ); appendListCell(row, formatThirdParty(observedRequest?.request?.thirdParty)); appendListCell(row, formatYesNo(hasConsentParams(observedRequest))); appendListCell(row, formatYesNo(hasConsentCorrelation(observedRequest))); appendListCell(row, formatNullable(observedRequest?.seenCount), "numeric"); requestList.append(row); } } function appendListCell(row, value, className) { const cell = document.createElement("td"); if (className) { cell.className = className; } cell.textContent = value; row.append(cell); } function selectObservedRequest(requestFingerprint) { selectedRequestFingerprint = requestFingerprint; renderRequestList(); renderSelectedRequest(); } function renderSelectedRequest() { const observedRequest = findObservedRequest(selectedRequestFingerprint); clearRequestDetails(); if (!observedRequest) { return; } renderSummaryTable([ ["Letzte Beobachtung", formatNullable(observedRequest?.lastSeenAt)], ["Erste Beobachtung", formatNullable(observedRequest?.firstSeenAt)], ["Empfaenger-Origin", formatNullable(observedRequest?.request?.origin)], ["Pfad", formatNullable(observedRequest?.request?.pathname)], ["Methode", formatNullable(observedRequest?.request?.method)], ["Request-Typ", formatNullable(observedRequest?.request?.type)], ["Third-Party", formatThirdParty(observedRequest?.request?.thirdParty)], ["Consent-Parameter vorhanden", formatYesNo(hasConsentParams(observedRequest))], [ "Consent-Korrelation vorhanden", formatYesNo(hasConsentCorrelation(observedRequest)) ], ["SeenCount", formatNullable(observedRequest?.seenCount)], [ "Request-Fingerprint", formatNullable(observedRequest?.requestFingerprint) ] ]); requestDetailJson.textContent = JSON.stringify(observedRequest, null, 2); requestDetailCorrelation.textContent = JSON.stringify( observedRequest?.correlation ?? null, null, 2 ); requestDetailFingerprintSource.textContent = JSON.stringify( observedRequest?.requestFingerprintSource ?? null, null, 2 ); requestDetailTechnicalFields.textContent = JSON.stringify( buildTechnicalFieldExtract(observedRequest), null, 2 ); } function renderSummaryTable(rows) { const table = document.createElement("table"); const body = document.createElement("tbody"); table.className = "inspector-table"; for (const [label, value] of rows) { const row = document.createElement("tr"); const labelCell = document.createElement("th"); const valueCell = document.createElement("td"); labelCell.scope = "row"; labelCell.textContent = label; valueCell.className = "inspector-value"; valueCell.textContent = value; row.append(labelCell, valueCell); body.append(row); } table.append(body); requestDetailSummary.append(table); } function clearRequestDetails() { requestDetailSummary.textContent = ""; requestDetailJson.textContent = ""; requestDetailCorrelation.textContent = ""; requestDetailFingerprintSource.textContent = ""; requestDetailTechnicalFields.textContent = ""; } function findObservedRequest(requestFingerprint) { if (!requestFingerprint) { return null; } return ( observedRequests.find((observedRequest) => { return observedRequest?.requestFingerprint === requestFingerprint; }) ?? null ); } function hasConsentParams(observedRequest) { const consentParams = observedRequest?.consentParams; if (!consentParams || typeof consentParams !== "object") { return false; } return Object.values(consentParams).some((value) => { return value !== null && value !== undefined && value !== ""; }); } function hasConsentCorrelation(observedRequest) { return Boolean(observedRequest?.correlation?.stateFingerprint); } function buildTechnicalFieldExtract(observedRequest) { return { schemaVersion: observedRequest?.schemaVersion ?? null, recordedAt: observedRequest?.recordedAt ?? null, recordingSource: observedRequest?.recordingSource ?? null, firstSeenAt: observedRequest?.firstSeenAt ?? null, lastSeenAt: observedRequest?.lastSeenAt ?? null, seenCount: observedRequest?.seenCount ?? null, context: observedRequest?.context ?? null, request: observedRequest?.request ?? null, consentParams: observedRequest?.consentParams ?? null }; } function formatNullable(value) { if (value === null || value === undefined || value === "") { return "-"; } return String(value); } function formatThirdParty(value) { if (value === true) { return "ja"; } if (value === false) { return "nein"; } return "unbekannt"; } function formatYesNo(value) { return value ? "ja" : "nein"; }