diff --git a/src/background.js b/src/background.js index 39108da..1b90a19 100644 --- a/src/background.js +++ b/src/background.js @@ -672,8 +672,23 @@ async function fetchOfficialGvlJson() { throw error; } + const rawBody = await response.text(); + const fetchedAt = new Date().toISOString(); + const contentType = response.headers.get("Content-Type"); + const rawGvlSha256 = await VendorGetGvlService.calculateRawGvlSha256(rawBody); + const db = await openVendorGetDb(); + + await VendorGetGvlService.storeGvlRawEvidenceIfNew(db, { + rawGvlSha256, + sourceUrl: OFFICIAL_IAB_GVL_URL, + fetchedAt, + httpStatus: response.status, + contentType, + rawBody + }); + return { - rawJson: await response.json(), + rawJson: JSON.parse(rawBody), responseStatus: response.status }; } diff --git a/src/background/gvl-service.js b/src/background/gvl-service.js index f54eca5..916626c 100644 --- a/src/background/gvl-service.js +++ b/src/background/gvl-service.js @@ -9,6 +9,47 @@ async function calculateGvlSnapshotSha256(rawJson) { .join(""); } +async function calculateRawGvlSha256(rawBody) { + const data = new TextEncoder().encode(rawBody); + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + + return hashArray + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); +} + +function storeGvlRawEvidenceIfNew(db, rawEvidence) { + return new Promise((resolve, reject) => { + const tx = db.transaction(["gvl_raw_evidence"], "readwrite"); + const rawEvidenceStore = tx.objectStore("gvl_raw_evidence"); + const getRequest = rawEvidenceStore.get(rawEvidence.rawGvlSha256); + let result = null; + + getRequest.onerror = () => reject(getRequest.error); + + getRequest.onsuccess = () => { + if (getRequest.result) { + result = { + stored: false, + rawGvlSha256: rawEvidence.rawGvlSha256 + }; + return; + } + + rawEvidenceStore.add(rawEvidence); + + result = { + stored: true, + rawGvlSha256: rawEvidence.rawGvlSha256 + }; + }; + + tx.onerror = () => reject(tx.error); + tx.oncomplete = () => resolve(result); + }); +} + async function buildGvlSnapshotRecord(rawJson, sourceUrl, fetchedAt) { const gvlJson = normalizeGvlSnapshotValueForMetadata(rawJson); @@ -149,6 +190,8 @@ function countObjectEntries(value) { globalThis.VendorGetGvlService = { calculateGvlSnapshotSha256, + calculateRawGvlSha256, + storeGvlRawEvidenceIfNew, buildGvlSnapshotRecord, storeGvlSnapshotIfNew, recordGvlSnapshotEvent, diff --git a/src/popup/popup.css b/src/popup/popup.css index a9cd5d7..bc9afb1 100644 --- a/src/popup/popup.css +++ b/src/popup/popup.css @@ -175,3 +175,45 @@ button:disabled { cursor: default; opacity: 0.65; } + +.confirm-modal { + position: fixed; + inset: 0; + display: grid; + place-items: center; + padding: 14px; + background: rgba(15, 23, 42, 0.72); +} + +.confirm-modal[hidden] { + display: none; +} + +.confirm-modal-panel { + display: grid; + gap: 10px; + width: min(100%, 320px); + padding: 12px; + border: 1px solid #475569; + border-radius: 6px; + color: #e5edf5; + background: #111827; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); +} + +.confirm-modal-panel h2 { + margin: 0; + font-size: 14px; +} + +.confirm-modal-panel p { + margin: 0; + font-size: 12px; + line-height: 1.4; + color: #cbd5e1; +} + +.confirm-modal-actions { + display: grid; + gap: 8px; +} diff --git a/src/popup/popup.html b/src/popup/popup.html index f4aa9d5..477e5bf 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -66,11 +66,39 @@ +
Status wird geladen
+ + diff --git a/src/popup/popup.js b/src/popup/popup.js index 31f9834..7f25f9d 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -16,6 +16,18 @@ const evidenceDashboardButton = document.getElementById( const evidenceExportJsonButton = document.getElementById( "evidence-export-json-button" ); +const evidencePurgeUnlockedButton = document.getElementById( + "evidence-purge-unlocked-button" +); +const evidencePurgeConfirmModal = document.getElementById( + "evidence-purge-confirm-modal" +); +const evidencePurgeCancelButton = document.getElementById( + "evidence-purge-cancel-button" +); +const evidencePurgeConfirmButton = document.getElementById( + "evidence-purge-confirm-button" +); const evidenceExportJsonStatus = document.getElementById( "evidence-export-json-status" ); @@ -69,6 +81,9 @@ document.addEventListener("DOMContentLoaded", async () => { }); evidenceExportJsonButton.addEventListener("click", exportEvidenceJsonFile); + evidencePurgeUnlockedButton.addEventListener("click", openPurgeConfirmModal); + evidencePurgeCancelButton.addEventListener("click", closePurgeConfirmModal); + evidencePurgeConfirmButton.addEventListener("click", purgeUnlockedEvidence); }); async function renderSettings() { @@ -143,6 +158,48 @@ function renderEvidenceRetentionMessage(message) { evidenceRetentionStatus.textContent = message; } +function openPurgeConfirmModal() { + evidencePurgeConfirmModal.hidden = false; + evidencePurgeCancelButton.focus(); +} + +function closePurgeConfirmModal() { + evidencePurgeConfirmModal.hidden = true; + evidencePurgeUnlockedButton.focus(); +} + +async function purgeUnlockedEvidence() { + closePurgeConfirmModal(); + evidencePurgeUnlockedButton.disabled = true; + renderEvidenceRetentionMessage("Ungesperrte Evidence-Daten werden gelöscht..."); + + try { + const result = await browser.runtime.sendMessage({ + type: "purge_unlocked_evidence_records" + }); + + if (!result?.success) { + throw new Error(result?.error ?? "purge_unlocked_evidence_records_failed"); + } + + await renderEvidenceRetentionStatus(); + renderEvidenceRetentionMessage(buildPurgeUnlockedSuccessMessage(result)); + } catch (error) { + renderEvidenceRetentionMessage("Löschen fehlgeschlagen"); + console.warn("VendorGet-IV unlocked evidence purge failed", error); + } finally { + evidencePurgeUnlockedButton.disabled = false; + } +} + +function buildPurgeUnlockedSuccessMessage(result) { + if (Number.isFinite(result.deletedCount)) { + return `Ungesperrte Evidence-Daten gelöscht: ${result.deletedCount} Records`; + } + + return "Ungesperrte Evidence-Daten gelöscht"; +} + async function exportEvidenceJsonFile() { evidenceExportJsonButton.disabled = true; renderEvidenceExportJsonMessage("Export läuft…");