From 3a174128e0aa5ba8217cc8624cb5db93e826413d Mon Sep 17 00:00:00 2001 From: jensmohr Date: Sun, 14 Jun 2026 13:09:17 +0200 Subject: [PATCH] Remove page-side consent overlay and preserve passive TCF capture boundary --- src/background.js | 637 +++++++++++++++++++++++++++++++++++++ src/injected/tcf-bridge.js | 97 +++--- 2 files changed, 693 insertions(+), 41 deletions(-) diff --git a/src/background.js b/src/background.js index 9781a36..ddcf94a 100644 --- a/src/background.js +++ b/src/background.js @@ -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: [""] } ); +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(); diff --git a/src/injected/tcf-bridge.js b/src/injected/tcf-bridge.js index 323392a..b0d2cf0 100644 --- a/src/injected/tcf-bridge.js +++ b/src/injected/tcf-bridge.js @@ -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,6 +71,50 @@ return result; } + function buildTcfEventCapture(tcData) { + return { + url: window.location.href, + origin: window.location.origin, + + timestampUtc: new Date().toISOString(), + + cmpId: tcData.cmpId, + cmpVersion: tcData.cmpVersion, + + gdprApplies: tcData.gdprApplies, + + tcfPolicyVersion: tcData.tcfPolicyVersion, + vendorListVersion: tcData.vendorListVersion, + + tcString: tcData.tcString, + + eventStatus: tcData.eventStatus, + cmpStatus: tcData.cmpStatus, + + isServiceSpecific: tcData.isServiceSpecific, + useNonStandardTexts: tcData.useNonStandardTexts, + + publisherCC: tcData.publisherCC, + purposeOneTreatment: tcData.purposeOneTreatment, + + purpose: tcData.purpose, + vendor: tcData.vendor, + + specialFeatureOptins: tcData.specialFeatureOptins, + + publisher: tcData.publisher, + + 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:", { @@ -97,44 +137,19 @@ 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 = { - url: window.location.href, - origin: window.location.origin, - - timestampUtc: new Date().toISOString(), - - cmpId: tcData.cmpId, - cmpVersion: tcData.cmpVersion, - - gdprApplies: tcData.gdprApplies, - - tcfPolicyVersion: tcData.tcfPolicyVersion, - vendorListVersion: tcData.vendorListVersion, - - tcString: tcData.tcString, - - eventStatus: tcData.eventStatus, - cmpStatus: tcData.cmpStatus, - - isServiceSpecific: tcData.isServiceSpecific, - useNonStandardTexts: tcData.useNonStandardTexts, - - publisherCC: tcData.publisherCC, - purposeOneTreatment: tcData.purposeOneTreatment, - - purpose: tcData.purpose, - vendor: tcData.vendor, - - specialFeatureOptins: tcData.specialFeatureOptins, - - publisher: tcData.publisher, - - addtlConsent: tcData.addtlConsent, - - rawTcData: cloneSerializable(tcData) - }; + const capture = buildTcfEventCapture(tcData); console.log("VendorGet CONSENT CAPTURE:", capture);