Remove page-side consent overlay and preserve passive TCF capture boundary
Dieser Commit ist enthalten in:
@@ -8,10 +8,12 @@ const EVIDENCE_RECORDING_SOURCE = "vendorget_background_mirror";
|
|||||||
const GVL_AUTO_UPDATE_SOURCE = "extension_startup";
|
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;
|
||||||
|
|
||||||
let isAutoGvlCheckRunning = false;
|
let isAutoGvlCheckRunning = false;
|
||||||
let lastAutoGvlCheckStartedAt = null;
|
let lastAutoGvlCheckStartedAt = null;
|
||||||
let latestGvlUpdateStatus = null;
|
let latestGvlUpdateStatus = null;
|
||||||
|
const consentCaptureSessions = new Map();
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((message, sender) =>
|
browser.runtime.onMessage.addListener((message, sender) =>
|
||||||
handleVendorGetMessage(message, sender)
|
handleVendorGetMessage(message, sender)
|
||||||
@@ -22,6 +24,24 @@ browser.webRequest.onBeforeRequest.addListener(
|
|||||||
{ urls: ["<all_urls>"] }
|
{ urls: ["<all_urls>"] }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
browser.tabs.onRemoved.addListener((tabId) => {
|
||||||
|
cleanupConsentCaptureSessionsForTab(tabId, "tab_removed");
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||||
|
if (changeInfo.url) {
|
||||||
|
cleanupConsentCaptureSessionsForTab(tabId, "page_changed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (browser.runtime.onSuspend) {
|
||||||
|
browser.runtime.onSuspend.addListener(() => {
|
||||||
|
consentCaptureSessions.forEach((session) => {
|
||||||
|
cleanupIncompleteConsentCaptureSession(session, "browser_end");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.info("GVL auto update disabled; use manual sync");
|
console.info("GVL auto update disabled; use manual sync");
|
||||||
|
|
||||||
async function handleVendorGetMessage(message, sender) {
|
async function handleVendorGetMessage(message, sender) {
|
||||||
@@ -113,6 +133,10 @@ async function handleVendorGetMessage(message, sender) {
|
|||||||
return handleGetLatestConsentStateMessage();
|
return handleGetLatestConsentStateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.type === "get_consent_evidence_chain") {
|
||||||
|
return handleGetConsentEvidenceChainMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
if (message.type === "list_recent_consent_states") {
|
if (message.type === "list_recent_consent_states") {
|
||||||
return handleListRecentConsentStatesMessage();
|
return handleListRecentConsentStatesMessage();
|
||||||
}
|
}
|
||||||
@@ -166,6 +190,11 @@ async function handleVendorGetMessage(message, sender) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventName === "tcf_pre_consent_event") {
|
||||||
|
await handlePreConsentTcfEvent(message.payload.payload, sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (eventName !== "consent_capture") {
|
if (eventName !== "consent_capture") {
|
||||||
console.log("VG-Observe ignored event", message);
|
console.log("VG-Observe ignored event", message);
|
||||||
return;
|
return;
|
||||||
@@ -188,6 +217,8 @@ async function handleVendorGetMessage(message, sender) {
|
|||||||
stableStringify(consentState.fingerprintSource)
|
stableStringify(consentState.fingerprintSource)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const captureSessionId = getActiveConsentCaptureSessionId(sender);
|
||||||
|
|
||||||
rememberLatestConsentState(consentState);
|
rememberLatestConsentState(consentState);
|
||||||
|
|
||||||
const result = await persistConsentState(
|
const result = await persistConsentState(
|
||||||
@@ -195,6 +226,17 @@ async function handleVendorGetMessage(message, sender) {
|
|||||||
message.payload.payload?.rawTcData ?? null
|
message.payload.payload?.rawTcData ?? null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (captureSessionId) {
|
||||||
|
await persistConsentExecutionEvent({
|
||||||
|
captureSessionId,
|
||||||
|
consentState,
|
||||||
|
rawCapture: message.payload.payload,
|
||||||
|
sender
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
completeConsentCaptureSession(sender);
|
||||||
|
|
||||||
console.log("VG-Observe consent state persisted", result);
|
console.log("VG-Observe consent state persisted", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,6 +1003,79 @@ async function handleGetLatestConsentStateMessage() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleGetConsentEvidenceChainMessage(message) {
|
||||||
|
const stateFingerprint = normalizeStateFingerprint(
|
||||||
|
message?.payload?.stateFingerprint
|
||||||
|
);
|
||||||
|
const db = await openVendorGetDb();
|
||||||
|
const consentState = stateFingerprint
|
||||||
|
? await getConsentStateByFingerprint(db, stateFingerprint)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!consentState) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "consent_state_not_found",
|
||||||
|
stateFingerprint
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const consentEvents = await listConsentEventsByStateFingerprint(
|
||||||
|
db,
|
||||||
|
stateFingerprint
|
||||||
|
);
|
||||||
|
const observedRequests =
|
||||||
|
await listObservedRequestsByCorrelationStateFingerprint(
|
||||||
|
db,
|
||||||
|
stateFingerprint
|
||||||
|
);
|
||||||
|
const gvlContext = await buildConsentEvidenceChainGvlContext(
|
||||||
|
db,
|
||||||
|
consentState
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
stateFingerprint,
|
||||||
|
chain: {
|
||||||
|
consentState: {
|
||||||
|
record: consentState,
|
||||||
|
source: {
|
||||||
|
role: "primary_observation",
|
||||||
|
store: VENDORGET_STORE_NAMES.consentStates,
|
||||||
|
keyPath: "stateFingerprint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
consentEvents: {
|
||||||
|
records: consentEvents,
|
||||||
|
source: {
|
||||||
|
role: "observation_events",
|
||||||
|
store: VENDORGET_STORE_NAMES.consentEvents,
|
||||||
|
index: "stateFingerprint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
observedRequests: {
|
||||||
|
records: observedRequests,
|
||||||
|
source: {
|
||||||
|
role: "correlated_primary_observations",
|
||||||
|
store: VENDORGET_STORE_NAMES.observedRequests,
|
||||||
|
index: "correlationStateFingerprint",
|
||||||
|
field: "correlation.stateFingerprint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gvlContext,
|
||||||
|
assembly: {
|
||||||
|
role: "technical_assembly",
|
||||||
|
method: "indexeddb_readonly_existing_records",
|
||||||
|
writes: false,
|
||||||
|
newDataSources: false,
|
||||||
|
correlationRecalculated: false,
|
||||||
|
interpretation: "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function handleListRecentConsentStatesMessage() {
|
async function handleListRecentConsentStatesMessage() {
|
||||||
const db = await openVendorGetDb();
|
const db = await openVendorGetDb();
|
||||||
const consentStates = await listRecentConsentStates(db, 25);
|
const consentStates = await listRecentConsentStates(db, 25);
|
||||||
@@ -981,6 +1096,175 @@ async function handleListRecentObservedRequestsMessage() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeStateFingerprint(value) {
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
|
||||||
|
return trimmedValue ? trimmedValue : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConsentStateByFingerprint(db, stateFingerprint) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction([VENDORGET_STORE_NAMES.consentStates], "readonly");
|
||||||
|
const statesStore = tx.objectStore(VENDORGET_STORE_NAMES.consentStates);
|
||||||
|
const getRequest = statesStore.get(stateFingerprint);
|
||||||
|
let consentStateOrNull = null;
|
||||||
|
|
||||||
|
getRequest.onerror = () => reject(getRequest.error);
|
||||||
|
getRequest.onsuccess = () => {
|
||||||
|
consentStateOrNull = getRequest.result ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
|
tx.oncomplete = () => resolve(consentStateOrNull);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function listConsentEventsByStateFingerprint(db, stateFingerprint) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const consentEvents = [];
|
||||||
|
const tx = db.transaction([VENDORGET_STORE_NAMES.consentEvents], "readonly");
|
||||||
|
const eventsStore = tx.objectStore(VENDORGET_STORE_NAMES.consentEvents);
|
||||||
|
const stateFingerprintIndex = eventsStore.index("stateFingerprint");
|
||||||
|
const cursorRequest = stateFingerprintIndex.openCursor(
|
||||||
|
IDBKeyRange.only(stateFingerprint)
|
||||||
|
);
|
||||||
|
|
||||||
|
cursorRequest.onerror = () => reject(cursorRequest.error);
|
||||||
|
cursorRequest.onsuccess = () => {
|
||||||
|
const cursor = cursorRequest.result;
|
||||||
|
|
||||||
|
if (!cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
consentEvents.push(cursor.value);
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
|
tx.oncomplete = () => {
|
||||||
|
resolve(consentEvents.sort(compareConsentEvidenceEventRecords));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function listObservedRequestsByCorrelationStateFingerprint(
|
||||||
|
db,
|
||||||
|
stateFingerprint
|
||||||
|
) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const observedRequests = [];
|
||||||
|
const tx = db.transaction(
|
||||||
|
[VENDORGET_STORE_NAMES.observedRequests],
|
||||||
|
"readonly"
|
||||||
|
);
|
||||||
|
const requestsStore = tx.objectStore(VENDORGET_STORE_NAMES.observedRequests);
|
||||||
|
const stateFingerprintIndex = requestsStore.index(
|
||||||
|
"correlationStateFingerprint"
|
||||||
|
);
|
||||||
|
const cursorRequest = stateFingerprintIndex.openCursor(
|
||||||
|
IDBKeyRange.only(stateFingerprint)
|
||||||
|
);
|
||||||
|
|
||||||
|
cursorRequest.onerror = () => reject(cursorRequest.error);
|
||||||
|
cursorRequest.onsuccess = () => {
|
||||||
|
const cursor = cursorRequest.result;
|
||||||
|
|
||||||
|
if (!cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observedRequests.push(cursor.value);
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
|
tx.oncomplete = () => {
|
||||||
|
resolve(observedRequests.sort(compareConsentEvidenceRequestRecords));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildConsentEvidenceChainGvlContext(db, consentState) {
|
||||||
|
const vendorListVersion = consentState?.gvl?.vendorListVersion ?? null;
|
||||||
|
const snapshot =
|
||||||
|
vendorListVersion !== null && vendorListVersion !== undefined
|
||||||
|
? await getGvlSnapshotByVendorListVersion(db, vendorListVersion)
|
||||||
|
: null;
|
||||||
|
const normalizedCounts = await countGvlNormalizedRecordsForVersion(
|
||||||
|
db,
|
||||||
|
vendorListVersion
|
||||||
|
);
|
||||||
|
const provenance = snapshot ? getGvlEvidenceProvenanceState(snapshot) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
vendorListVersion,
|
||||||
|
snapshot,
|
||||||
|
normalizedCounts,
|
||||||
|
provenance,
|
||||||
|
source: {
|
||||||
|
role: "reference_context",
|
||||||
|
snapshotStore: VENDORGET_STORE_NAMES.gvlSnapshots,
|
||||||
|
snapshotIndex: "vendorListVersion",
|
||||||
|
normalizedStores: [
|
||||||
|
VENDORGET_STORE_NAMES.gvlVendors,
|
||||||
|
VENDORGET_STORE_NAMES.gvlPurposes,
|
||||||
|
VENDORGET_STORE_NAMES.gvlSpecialPurposes,
|
||||||
|
VENDORGET_STORE_NAMES.gvlFeatures,
|
||||||
|
VENDORGET_STORE_NAMES.gvlSpecialFeatures,
|
||||||
|
VENDORGET_STORE_NAMES.gvlDataCategories,
|
||||||
|
VENDORGET_STORE_NAMES.gvlVendorRelationships
|
||||||
|
],
|
||||||
|
normalizedIndex: "vendorListVersion"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareConsentEvidenceEventRecords(left, right) {
|
||||||
|
return (
|
||||||
|
compareNullableIsoDate(left?.capturedAt, right?.capturedAt) ||
|
||||||
|
compareNullableIsoDate(left?.recordedAt, right?.recordedAt) ||
|
||||||
|
toConsentEvidenceComparableNumber(left?.id) -
|
||||||
|
toConsentEvidenceComparableNumber(right?.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareConsentEvidenceRequestRecords(left, right) {
|
||||||
|
return (
|
||||||
|
compareNullableIsoDate(left?.firstSeenAt, right?.firstSeenAt) ||
|
||||||
|
compareNullableIsoDate(left?.lastSeenAt, right?.lastSeenAt) ||
|
||||||
|
String(left?.requestFingerprint ?? "").localeCompare(
|
||||||
|
String(right?.requestFingerprint ?? "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareNullableIsoDate(left, right) {
|
||||||
|
return (
|
||||||
|
toConsentEvidenceComparableTime(left) -
|
||||||
|
toConsentEvidenceComparableTime(right)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toConsentEvidenceComparableTime(value) {
|
||||||
|
const timestamp = Date.parse(value ?? "");
|
||||||
|
|
||||||
|
return Number.isNaN(timestamp) ? 0 : timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toConsentEvidenceComparableNumber(value) {
|
||||||
|
const numberValue = Number(value);
|
||||||
|
|
||||||
|
return Number.isFinite(numberValue) ? numberValue : 0;
|
||||||
|
}
|
||||||
|
|
||||||
function getLatestConsentState(db) {
|
function getLatestConsentState(db) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction(["consent_states"], "readonly");
|
const tx = db.transaction(["consent_states"], "readonly");
|
||||||
@@ -1814,6 +2098,225 @@ function getNextAllowedAutoGvlCheckAt(lastAutoGvlCheckAt) {
|
|||||||
return new Date(lastCheckTime + AUTO_GVL_CHECK_THROTTLE_MS).toISOString();
|
return new Date(lastCheckTime + AUTO_GVL_CHECK_THROTTLE_MS).toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConsentCaptureSessionKey(sender) {
|
||||||
|
const tabId = sender?.tab?.id;
|
||||||
|
const frameId = sender?.frameId ?? 0;
|
||||||
|
|
||||||
|
if (tabId === null || tabId === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${tabId}:${frameId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCaptureSessionId() {
|
||||||
|
if (crypto?.randomUUID) {
|
||||||
|
return crypto.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"consent-capture",
|
||||||
|
Date.now().toString(36),
|
||||||
|
Math.random().toString(36).slice(2)
|
||||||
|
].join("-");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildConsentEventPageContext(rawCapture, sender) {
|
||||||
|
return {
|
||||||
|
url: rawCapture?.url ?? sender?.tab?.url ?? sender?.url ?? null,
|
||||||
|
origin: rawCapture?.origin ?? sender?.origin ?? null,
|
||||||
|
tabId: sender?.tab?.id ?? null,
|
||||||
|
frameId: sender?.frameId ?? null,
|
||||||
|
incognito: sender?.tab?.incognito ?? null,
|
||||||
|
cookieStoreId: sender?.tab?.cookieStoreId ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildConsentEventDiagnostics(rawCapture, extraDiagnostics) {
|
||||||
|
return {
|
||||||
|
bridgeTimestampUtc: rawCapture?.timestampUtc ?? null,
|
||||||
|
rawTopLevelKeys: Object.keys(rawCapture?.rawTcData ?? rawCapture ?? {}),
|
||||||
|
...(extraDiagnostics ?? {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function startConsentCaptureSessionTimeout(session) {
|
||||||
|
session.timeoutId = setTimeout(() => {
|
||||||
|
cleanupIncompleteConsentCaptureSession(session, "timeout");
|
||||||
|
}, CONSENT_CAPTURE_SESSION_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePreConsentTcfEvent(rawCapture, sender) {
|
||||||
|
if (isEvidenceWriteSuspended()) {
|
||||||
|
console.info("VG-Observe pre-consent capture skipped: maintenance mode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventStatus = rawCapture?.eventStatus ?? null;
|
||||||
|
|
||||||
|
if (eventStatus !== "cmpuishown" && eventStatus !== "tcloaded") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionKey = getConsentCaptureSessionKey(sender);
|
||||||
|
|
||||||
|
if (!sessionKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let session = consentCaptureSessions.get(sessionKey);
|
||||||
|
|
||||||
|
if (session?.status === "declined" || session?.status === "completed") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session?.status === "active") {
|
||||||
|
await persistProviderAnnouncementEvent({
|
||||||
|
captureSessionId: session.captureSessionId,
|
||||||
|
rawCapture,
|
||||||
|
sender
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
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) {
|
||||||
|
const sessionKey = getConsentCaptureSessionKey(sender);
|
||||||
|
const session = sessionKey ? consentCaptureSessions.get(sessionKey) : null;
|
||||||
|
|
||||||
|
if (!session || session.status !== "active") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.captureSessionId ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function completeConsentCaptureSession(sender) {
|
||||||
|
const sessionKey = getConsentCaptureSessionKey(sender);
|
||||||
|
let session = sessionKey ? consentCaptureSessions.get(sessionKey) : null;
|
||||||
|
|
||||||
|
if (!sessionKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
session = {
|
||||||
|
key: sessionKey,
|
||||||
|
tabId: sender?.tab?.id ?? null,
|
||||||
|
frameId: sender?.frameId ?? null,
|
||||||
|
status: "completed",
|
||||||
|
captureSessionId: null,
|
||||||
|
bufferedPreConsentEvents: [],
|
||||||
|
timeoutId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
consentCaptureSessions.set(sessionKey, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.status = "completed";
|
||||||
|
session.completedAt = new Date().toISOString();
|
||||||
|
session.bufferedPreConsentEvents = [];
|
||||||
|
session.promptInProgress = false;
|
||||||
|
|
||||||
|
if (session.timeoutId) {
|
||||||
|
clearTimeout(session.timeoutId);
|
||||||
|
session.timeoutId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupConsentCaptureSessionsForTab(tabId, reason) {
|
||||||
|
consentCaptureSessions.forEach((session) => {
|
||||||
|
if (session.tabId === tabId) {
|
||||||
|
cleanupIncompleteConsentCaptureSession(session, reason);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupIncompleteConsentCaptureSession(session, reason) {
|
||||||
|
if (session.timeoutId) {
|
||||||
|
clearTimeout(session.timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
consentCaptureSessions.delete(session.key);
|
||||||
|
|
||||||
|
if (!session.captureSessionId || session.status === "completed") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProviderAnnouncementEventsForSession(
|
||||||
|
session.captureSessionId,
|
||||||
|
reason
|
||||||
|
).catch((error) => {
|
||||||
|
console.warn("VG-Observe provider announcement cleanup failed", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
@@ -2018,6 +2521,9 @@ function buildConsentStateV1(rawCapture, sender, latestPingData) {
|
|||||||
addtlConsent: rawCapture?.addtlConsent ?? null
|
addtlConsent: rawCapture?.addtlConsent ?? null
|
||||||
},
|
},
|
||||||
|
|
||||||
|
rawTcString: rawCapture?.rawTcString ?? null,
|
||||||
|
rawTcData: rawCapture?.rawTcData ?? null,
|
||||||
|
|
||||||
purposes: {
|
purposes: {
|
||||||
consents: rawCapture?.purpose?.consents ?? {},
|
consents: rawCapture?.purpose?.consents ?? {},
|
||||||
legitimateInterests: rawCapture?.purpose?.legitimateInterests ?? {}
|
legitimateInterests: rawCapture?.purpose?.legitimateInterests ?? {}
|
||||||
@@ -2093,6 +2599,10 @@ async function persistConsentState(consentState, rawTcData) {
|
|||||||
existingState.lastSeenAt = now;
|
existingState.lastSeenAt = now;
|
||||||
existingState.seenCount = (existingState.seenCount ?? 1) + 1;
|
existingState.seenCount = (existingState.seenCount ?? 1) + 1;
|
||||||
existingState.updatedAt = now;
|
existingState.updatedAt = now;
|
||||||
|
existingState.rawTcString =
|
||||||
|
existingState.rawTcString ?? consentState.rawTcString ?? null;
|
||||||
|
existingState.rawTcData =
|
||||||
|
existingState.rawTcData ?? consentState.rawTcData ?? null;
|
||||||
|
|
||||||
statesStore.put(existingState);
|
statesStore.put(existingState);
|
||||||
|
|
||||||
@@ -2104,6 +2614,7 @@ async function persistConsentState(consentState, rawTcData) {
|
|||||||
stateFingerprint: consentState.stateFingerprint,
|
stateFingerprint: consentState.stateFingerprint,
|
||||||
page: consentState.page,
|
page: consentState.page,
|
||||||
rawEventName: "consent_capture",
|
rawEventName: "consent_capture",
|
||||||
|
rawTcString: consentState.rawTcString ?? null,
|
||||||
rawTcData: rawTcData,
|
rawTcData: rawTcData,
|
||||||
diagnostics: consentState.diagnostics
|
diagnostics: consentState.diagnostics
|
||||||
});
|
});
|
||||||
@@ -2138,6 +2649,7 @@ async function persistConsentState(consentState, rawTcData) {
|
|||||||
stateFingerprint: consentState.stateFingerprint,
|
stateFingerprint: consentState.stateFingerprint,
|
||||||
page: consentState.page,
|
page: consentState.page,
|
||||||
rawEventName: "consent_capture",
|
rawEventName: "consent_capture",
|
||||||
|
rawTcString: consentState.rawTcString ?? null,
|
||||||
rawTcData: rawTcData,
|
rawTcData: rawTcData,
|
||||||
diagnostics: consentState.diagnostics
|
diagnostics: consentState.diagnostics
|
||||||
});
|
});
|
||||||
@@ -2153,6 +2665,131 @@ async function persistConsentState(consentState, rawTcData) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function persistProviderAnnouncementEvent({
|
||||||
|
captureSessionId,
|
||||||
|
rawCapture,
|
||||||
|
sender
|
||||||
|
}) {
|
||||||
|
const db = await openVendorGetDb();
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const capturedAt = rawCapture?.timestampUtc ?? now;
|
||||||
|
|
||||||
|
return addConsentEventRecord(db, {
|
||||||
|
eventType: "provider_announcement",
|
||||||
|
capturedAt,
|
||||||
|
recordedAt: now,
|
||||||
|
recordingSource: EVIDENCE_RECORDING_SOURCE,
|
||||||
|
captureSessionId,
|
||||||
|
stateFingerprint: null,
|
||||||
|
page: buildConsentEventPageContext(rawCapture, sender),
|
||||||
|
rawEventName: rawCapture?.eventStatus ?? null,
|
||||||
|
eventStatus: rawCapture?.eventStatus ?? null,
|
||||||
|
cmpStatus: rawCapture?.cmpStatus ?? null,
|
||||||
|
tcString: rawCapture?.tcString ?? null,
|
||||||
|
addtlConsent: rawCapture?.addtlConsent ?? null,
|
||||||
|
vendorListVersion: rawCapture?.vendorListVersion ?? null,
|
||||||
|
rawTcString: rawCapture?.rawTcString ?? null,
|
||||||
|
rawTcData: rawCapture?.rawTcData ?? null,
|
||||||
|
diagnostics: buildConsentEventDiagnostics(rawCapture, {
|
||||||
|
consentEvidenceRole: "providerAnnouncement"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistConsentExecutionEvent({
|
||||||
|
captureSessionId,
|
||||||
|
consentState,
|
||||||
|
rawCapture,
|
||||||
|
sender
|
||||||
|
}) {
|
||||||
|
const db = await openVendorGetDb();
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const capturedAt = consentState?.capturedAt ?? rawCapture?.timestampUtc ?? now;
|
||||||
|
|
||||||
|
return addConsentEventRecord(db, {
|
||||||
|
eventType: "consent_execution",
|
||||||
|
capturedAt,
|
||||||
|
recordedAt: now,
|
||||||
|
recordingSource: EVIDENCE_RECORDING_SOURCE,
|
||||||
|
captureSessionId,
|
||||||
|
stateFingerprint: consentState?.stateFingerprint ?? null,
|
||||||
|
page: buildConsentEventPageContext(rawCapture, sender),
|
||||||
|
rawEventName: "useractioncomplete",
|
||||||
|
eventStatus: rawCapture?.eventStatus ?? null,
|
||||||
|
cmpStatus: rawCapture?.cmpStatus ?? null,
|
||||||
|
tcString: rawCapture?.tcString ?? null,
|
||||||
|
addtlConsent: rawCapture?.addtlConsent ?? null,
|
||||||
|
vendorListVersion: rawCapture?.vendorListVersion ?? null,
|
||||||
|
rawTcString: rawCapture?.rawTcString ?? null,
|
||||||
|
rawTcData: rawCapture?.rawTcData ?? null,
|
||||||
|
diagnostics: buildConsentEventDiagnostics(rawCapture, {
|
||||||
|
consentEvidenceRole: "consentExecution"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addConsentEventRecord(db, record) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(["consent_events"], "readwrite");
|
||||||
|
const eventsStore = tx.objectStore("consent_events");
|
||||||
|
const request = eventsStore.add(record);
|
||||||
|
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
|
tx.oncomplete = () => resolve(record);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteProviderAnnouncementEventsForSession(
|
||||||
|
captureSessionId,
|
||||||
|
reason
|
||||||
|
) {
|
||||||
|
const db = await openVendorGetDb();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(["consent_events"], "readwrite");
|
||||||
|
const eventsStore = tx.objectStore("consent_events");
|
||||||
|
const eventTypeIndex = eventsStore.index("eventType");
|
||||||
|
const request = eventTypeIndex.openCursor(
|
||||||
|
IDBKeyRange.only("provider_announcement")
|
||||||
|
);
|
||||||
|
let deletedCount = 0;
|
||||||
|
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const cursor = request.result;
|
||||||
|
|
||||||
|
if (!cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.value?.captureSessionId === captureSessionId) {
|
||||||
|
cursor.delete();
|
||||||
|
deletedCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
|
tx.oncomplete = () => {
|
||||||
|
console.info("VG-Observe provider announcement cleanup complete", {
|
||||||
|
captureSessionId,
|
||||||
|
reason,
|
||||||
|
deletedCount
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
captureSessionId,
|
||||||
|
reason,
|
||||||
|
deletedCount
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function persistObservedRequest(observedRequest) {
|
async function persistObservedRequest(observedRequest) {
|
||||||
const db = await openVendorGetDb();
|
const db = await openVendorGetDb();
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
function cloneSerializable(value, seen) {
|
function cloneSerializable(value, seen) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
||||||
@@ -50,10 +50,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
Object.keys(value).forEach(function (key) {
|
Object.keys(value).forEach(function (key) {
|
||||||
@@ -75,31 +71,8 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__tcfapi("ping", 2, function (pingData, pingSuccess) {
|
function buildTcfEventCapture(tcData) {
|
||||||
|
return {
|
||||||
console.log("VendorGet __tcfapi ping:", {
|
|
||||||
success: pingSuccess,
|
|
||||||
data: pingData
|
|
||||||
});
|
|
||||||
|
|
||||||
emitToContentScript("tcf_ping", {
|
|
||||||
success: pingSuccess,
|
|
||||||
data: pingData
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
window.__tcfapi("addEventListener", 2, function (tcData, success) {
|
|
||||||
|
|
||||||
if (!success || !tcData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("VendorGet raw event:", tcData);
|
|
||||||
|
|
||||||
if (tcData.eventStatus === "useractioncomplete") {
|
|
||||||
|
|
||||||
const capture = {
|
|
||||||
url: window.location.href,
|
url: window.location.href,
|
||||||
origin: window.location.origin,
|
origin: window.location.origin,
|
||||||
|
|
||||||
@@ -133,8 +106,50 @@
|
|||||||
|
|
||||||
addtlConsent: tcData.addtlConsent,
|
addtlConsent: tcData.addtlConsent,
|
||||||
|
|
||||||
|
rawTcString: {
|
||||||
|
tcString: tcData.tcString,
|
||||||
|
addtlConsent: tcData.addtlConsent
|
||||||
|
},
|
||||||
|
|
||||||
rawTcData: cloneSerializable(tcData)
|
rawTcData: cloneSerializable(tcData)
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__tcfapi("ping", 2, function (pingData, pingSuccess) {
|
||||||
|
|
||||||
|
console.log("VendorGet __tcfapi ping:", {
|
||||||
|
success: pingSuccess,
|
||||||
|
data: pingData
|
||||||
|
});
|
||||||
|
|
||||||
|
emitToContentScript("tcf_ping", {
|
||||||
|
success: pingSuccess,
|
||||||
|
data: pingData
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
window.__tcfapi("addEventListener", 2, function (tcData, success) {
|
||||||
|
|
||||||
|
if (!success || !tcData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("VendorGet raw event:", tcData);
|
||||||
|
|
||||||
|
if (
|
||||||
|
tcData.eventStatus === "cmpuishown" ||
|
||||||
|
tcData.eventStatus === "tcloaded"
|
||||||
|
) {
|
||||||
|
emitToContentScript(
|
||||||
|
"tcf_pre_consent_event",
|
||||||
|
buildTcfEventCapture(tcData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tcData.eventStatus === "useractioncomplete") {
|
||||||
|
|
||||||
|
const capture = buildTcfEventCapture(tcData);
|
||||||
|
|
||||||
console.log("VendorGet CONSENT CAPTURE:", capture);
|
console.log("VendorGet CONSENT CAPTURE:", capture);
|
||||||
|
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren