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 AUTO_GVL_CHECK_THROTTLE_MS = 24 * 60 * 60 * 1000;
|
||||
const AUTO_GVL_CHECK_STORAGE_KEY = "vendorgetAutoGvlUpdateStatus";
|
||||
const CONSENT_CAPTURE_SESSION_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
|
||||
let isAutoGvlCheckRunning = false;
|
||||
let lastAutoGvlCheckStartedAt = null;
|
||||
let latestGvlUpdateStatus = null;
|
||||
const consentCaptureSessions = new Map();
|
||||
|
||||
browser.runtime.onMessage.addListener((message, sender) =>
|
||||
handleVendorGetMessage(message, sender)
|
||||
@@ -22,6 +24,24 @@ browser.webRequest.onBeforeRequest.addListener(
|
||||
{ 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");
|
||||
|
||||
async function handleVendorGetMessage(message, sender) {
|
||||
@@ -113,6 +133,10 @@ async function handleVendorGetMessage(message, sender) {
|
||||
return handleGetLatestConsentStateMessage();
|
||||
}
|
||||
|
||||
if (message.type === "get_consent_evidence_chain") {
|
||||
return handleGetConsentEvidenceChainMessage(message);
|
||||
}
|
||||
|
||||
if (message.type === "list_recent_consent_states") {
|
||||
return handleListRecentConsentStatesMessage();
|
||||
}
|
||||
@@ -166,6 +190,11 @@ async function handleVendorGetMessage(message, sender) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventName === "tcf_pre_consent_event") {
|
||||
await handlePreConsentTcfEvent(message.payload.payload, sender);
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventName !== "consent_capture") {
|
||||
console.log("VG-Observe ignored event", message);
|
||||
return;
|
||||
@@ -188,6 +217,8 @@ async function handleVendorGetMessage(message, sender) {
|
||||
stableStringify(consentState.fingerprintSource)
|
||||
);
|
||||
|
||||
const captureSessionId = getActiveConsentCaptureSessionId(sender);
|
||||
|
||||
rememberLatestConsentState(consentState);
|
||||
|
||||
const result = await persistConsentState(
|
||||
@@ -195,6 +226,17 @@ async function handleVendorGetMessage(message, sender) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
const db = await openVendorGetDb();
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(["consent_states"], "readonly");
|
||||
@@ -1814,6 +2098,225 @@ function getNextAllowedAutoGvlCheckAt(lastAutoGvlCheckAt) {
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
|
||||
@@ -2018,6 +2521,9 @@ function buildConsentStateV1(rawCapture, sender, latestPingData) {
|
||||
addtlConsent: rawCapture?.addtlConsent ?? null
|
||||
},
|
||||
|
||||
rawTcString: rawCapture?.rawTcString ?? null,
|
||||
rawTcData: rawCapture?.rawTcData ?? null,
|
||||
|
||||
purposes: {
|
||||
consents: rawCapture?.purpose?.consents ?? {},
|
||||
legitimateInterests: rawCapture?.purpose?.legitimateInterests ?? {}
|
||||
@@ -2093,6 +2599,10 @@ async function persistConsentState(consentState, rawTcData) {
|
||||
existingState.lastSeenAt = now;
|
||||
existingState.seenCount = (existingState.seenCount ?? 1) + 1;
|
||||
existingState.updatedAt = now;
|
||||
existingState.rawTcString =
|
||||
existingState.rawTcString ?? consentState.rawTcString ?? null;
|
||||
existingState.rawTcData =
|
||||
existingState.rawTcData ?? consentState.rawTcData ?? null;
|
||||
|
||||
statesStore.put(existingState);
|
||||
|
||||
@@ -2104,6 +2614,7 @@ async function persistConsentState(consentState, rawTcData) {
|
||||
stateFingerprint: consentState.stateFingerprint,
|
||||
page: consentState.page,
|
||||
rawEventName: "consent_capture",
|
||||
rawTcString: consentState.rawTcString ?? null,
|
||||
rawTcData: rawTcData,
|
||||
diagnostics: consentState.diagnostics
|
||||
});
|
||||
@@ -2138,6 +2649,7 @@ async function persistConsentState(consentState, rawTcData) {
|
||||
stateFingerprint: consentState.stateFingerprint,
|
||||
page: consentState.page,
|
||||
rawEventName: "consent_capture",
|
||||
rawTcString: consentState.rawTcString ?? null,
|
||||
rawTcData: rawTcData,
|
||||
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) {
|
||||
const db = await openVendorGetDb();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
function cloneSerializable(value, seen) {
|
||||
if (value === undefined) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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 = {};
|
||||
|
||||
Object.keys(value).forEach(function (key) {
|
||||
@@ -75,31 +71,8 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
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 === "useractioncomplete") {
|
||||
|
||||
const capture = {
|
||||
function buildTcfEventCapture(tcData) {
|
||||
return {
|
||||
url: window.location.href,
|
||||
origin: window.location.origin,
|
||||
|
||||
@@ -133,8 +106,50 @@
|
||||
|
||||
addtlConsent: tcData.addtlConsent,
|
||||
|
||||
rawTcString: {
|
||||
tcString: tcData.tcString,
|
||||
addtlConsent: tcData.addtlConsent
|
||||
},
|
||||
|
||||
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);
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren