const REQUEST_DEDUPE_WINDOW_MS = 60 * 1000; const REQUEST_CORRELATION_WINDOW_MS = 5 * 60 * 1000; const OBSERVED_REQUEST_RECORDING_SOURCE = "vendorget_background_mirror"; const observedRequestFingerprints = new Map(); async function handleObservedRequest(details) { if (!(await isRequestMonitoringEnabled())) { return; } if (!hasConsentQueryParam(details.url)) { return; } if (isEvidenceWriteSuspended()) { console.info("VendorGet-IV evidence write skipped: maintenance mode"); return; } // The browser request is the primary observation; VG-IV records a mirror copy // with its own recordedAt timestamp for provenance. const requestFingerprint = await buildObservedRequestFingerprint(details); const requestFingerprintSource = buildObservedRequestFingerprintSource(details); const observedAt = new Date(details.timeStamp).toISOString(); const recordedAt = new Date().toISOString(); const correlation = buildObservedRequestCorrelation( details.tabId, details.frameId, observedAt ); const now = Date.now(); const existingFingerprint = observedRequestFingerprints.get(requestFingerprint); const observedRequest = { schemaVersion: 1, requestFingerprint: requestFingerprint, requestFingerprintSource: requestFingerprintSource, recordedAt: recordedAt, recordingSource: OBSERVED_REQUEST_RECORDING_SOURCE, firstSeenAt: observedAt, lastSeenAt: observedAt, seenCount: 1, request: { url: details.url, origin: requestFingerprintSource.origin, pathname: requestFingerprintSource.pathname, method: details.method, type: details.type, thirdParty: details.thirdParty }, consentParams: { gdpr: requestFingerprintSource.consentParams.gdpr, gdpr_consent: requestFingerprintSource.consentParams.gdpr_consent, addtlConsent: requestFingerprintSource.consentParams.addtlConsent }, context: { tabId: details.tabId, frameId: details.frameId }, correlation: correlation }; if ( existingFingerprint && now - existingFingerprint.lastSeenAt < REQUEST_DEDUPE_WINDOW_MS ) { existingFingerprint.lastSeenAt = now; existingFingerprint.seenCount += 1; await persistObservedRequest(observedRequest); return; } const seenCount = existingFingerprint ? existingFingerprint.seenCount + 1 : 1; observedRequestFingerprints.set(requestFingerprint, { lastSeenAt: now, seenCount: seenCount }); await persistObservedRequest(observedRequest); const normalizedObject = { observedAt: observedAt, recordedAt: recordedAt, requestId: details.requestId, tabId: details.tabId, frameId: details.frameId, url: details.url, method: details.method, type: details.type, thirdParty: details.thirdParty, requestFingerprint: requestFingerprint, seenCount: seenCount, requestFingerprintSource: requestFingerprintSource }; console.log("VendorGet-IV observed request", normalizedObject); } function hasConsentQueryParam(url) { try { const params = new URL(url).searchParams; return ( params.has("gdpr") || params.has("gdpr_consent") || params.has("addtlConsent") ); } catch (error) { return false; } } function buildObservedRequestCorrelation(tabId, frameId, observedAt) { const latestConsentState = getLatestConsentStateForRequest(tabId, frameId); if (!latestConsentState) { return buildEmptyObservedRequestCorrelation(); } const requestTime = Date.parse(observedAt); const consentTime = Date.parse(latestConsentState.capturedAt); if (!Number.isFinite(requestTime) || !Number.isFinite(consentTime)) { return buildEmptyObservedRequestCorrelation(); } const deltaMs = requestTime - consentTime; if (deltaMs < 0 || deltaMs > REQUEST_CORRELATION_WINDOW_MS) { return buildEmptyObservedRequestCorrelation(); } return { stateFingerprint: latestConsentState.stateFingerprint, deltaMs: deltaMs, method: "latest_in_memory_same_tab_frame", windowMs: REQUEST_CORRELATION_WINDOW_MS }; } function buildEmptyObservedRequestCorrelation() { return { stateFingerprint: null, deltaMs: null, method: null, windowMs: REQUEST_CORRELATION_WINDOW_MS }; }