Restructure VG-Observe into focused explorer views
Dieser Commit ist enthalten in:
+860
-16
@@ -5,6 +5,13 @@ console.log("VG-Observe background loaded");
|
||||
const OFFICIAL_IAB_GVL_URL =
|
||||
"https://vendor-list.consensu.org/v3/vendor-list.json";
|
||||
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";
|
||||
|
||||
let isAutoGvlCheckRunning = false;
|
||||
let lastAutoGvlCheckStartedAt = null;
|
||||
let latestGvlUpdateStatus = null;
|
||||
|
||||
browser.runtime.onMessage.addListener((message, sender) =>
|
||||
handleVendorGetMessage(message, sender)
|
||||
@@ -15,6 +22,8 @@ browser.webRequest.onBeforeRequest.addListener(
|
||||
{ urls: ["<all_urls>"] }
|
||||
);
|
||||
|
||||
void runStartupGvlAutoUpdateCheck();
|
||||
|
||||
async function handleVendorGetMessage(message, sender) {
|
||||
if (!message) {
|
||||
return null;
|
||||
@@ -48,6 +57,26 @@ async function handleVendorGetMessage(message, sender) {
|
||||
return handleGetEvidenceRetentionStatusMessage();
|
||||
}
|
||||
|
||||
if (message.type === "get_latest_gvl_update_status") {
|
||||
return handleGetLatestGvlUpdateStatusMessage();
|
||||
}
|
||||
|
||||
if (message.type === "list_gvl_snapshots") {
|
||||
return handleListGvlSnapshotsMessage();
|
||||
}
|
||||
|
||||
if (message.type === "get_gvl_snapshot_summary") {
|
||||
return handleGetGvlSnapshotSummaryMessage(message);
|
||||
}
|
||||
|
||||
if (message.type === "get_latest_consent_state") {
|
||||
return handleGetLatestConsentStateMessage();
|
||||
}
|
||||
|
||||
if (message.type === "list_recent_consent_states") {
|
||||
return handleListRecentConsentStatesMessage();
|
||||
}
|
||||
|
||||
if (message.type === "purge_unlocked_evidence_records") {
|
||||
return handlePurgeUnlockedEvidenceRecordsMessage();
|
||||
}
|
||||
@@ -140,6 +169,349 @@ async function handleGetEvidenceRetentionStatusMessage() {
|
||||
};
|
||||
}
|
||||
|
||||
async function handleGetLatestGvlUpdateStatusMessage() {
|
||||
const db = await openVendorGetDb();
|
||||
const latestSnapshot = await getLatestGvlSnapshotByVendorListVersion(db);
|
||||
|
||||
if (latestGvlUpdateStatus) {
|
||||
return {
|
||||
success: true,
|
||||
status: {
|
||||
...latestGvlUpdateStatus,
|
||||
latestLocalVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
latestLocalSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
latestLocalFetchedAt: latestSnapshot?.fetchedAt ?? null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const throttleState = await getAutoGvlCheckThrottleState();
|
||||
const throttleDecision = shouldThrottleAutoGvlCheck(
|
||||
throttleState?.lastAutoGvlCheckAt ?? null
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: {
|
||||
checkedAt: null,
|
||||
previousVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
currentVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
previousSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
currentSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
latestLocalVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
latestLocalSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
latestLocalFetchedAt: latestSnapshot?.fetchedAt ?? null,
|
||||
lastAutoGvlCheckAt: throttleState?.lastAutoGvlCheckAt ?? null,
|
||||
nextAllowedAutoCheckAt: throttleDecision.nextAllowedAutoCheckAt,
|
||||
result: "not_checked_since_background_start",
|
||||
message: "Noch kein automatischer GVL-Update-Check seit Background-Start."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function handleListGvlSnapshotsMessage() {
|
||||
const db = await openVendorGetDb();
|
||||
const snapshots = await listRecentGvlSnapshots(db, 25);
|
||||
const snapshotsWithEvents = await Promise.all(
|
||||
snapshots.map(async (snapshot) => {
|
||||
const event = await getAnyGvlSnapshotEventBySha256(db, snapshot.sha256);
|
||||
|
||||
return {
|
||||
vendorListVersion: snapshot.vendorListVersion ?? null,
|
||||
sha256: snapshot.sha256 ?? null,
|
||||
fetchedAt: snapshot.fetchedAt ?? null,
|
||||
sourceUrl: snapshot.sourceUrl ?? null,
|
||||
eventType: event?.eventType ?? null,
|
||||
eventCapturedAt: event?.capturedAt ?? null
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
gvlSnapshots: snapshotsWithEvents
|
||||
};
|
||||
}
|
||||
|
||||
async function handleGetGvlSnapshotSummaryMessage(message) {
|
||||
const db = await openVendorGetDb();
|
||||
const payload = message?.payload ?? {};
|
||||
const snapshot = await getGvlSnapshotByIdentifier(db, {
|
||||
sha256: payload.sha256 ?? null,
|
||||
vendorListVersion: payload.vendorListVersion ?? null
|
||||
});
|
||||
|
||||
if (!snapshot) {
|
||||
return {
|
||||
success: false,
|
||||
error: "gvl_snapshot_not_found"
|
||||
};
|
||||
}
|
||||
|
||||
const vendorListVersion = snapshot.vendorListVersion ?? null;
|
||||
const event = await getAnyGvlSnapshotEventBySha256(db, snapshot.sha256);
|
||||
const counts = await countGvlNormalizedRecordsForVersion(
|
||||
db,
|
||||
vendorListVersion
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
summary: {
|
||||
vendorListVersion,
|
||||
sha256: snapshot.sha256 ?? null,
|
||||
fetchedAt: snapshot.fetchedAt ?? null,
|
||||
sourceUrl: snapshot.sourceUrl ?? null,
|
||||
eventType: event?.eventType ?? null,
|
||||
eventCapturedAt: event?.capturedAt ?? null,
|
||||
vendorCount: snapshot.vendorCount ?? counts.vendorCount,
|
||||
purposeCount: snapshot.purposeCount ?? counts.purposeCount,
|
||||
specialPurposeCount: counts.specialPurposeCount,
|
||||
featureCount: counts.featureCount,
|
||||
specialFeatureCount: counts.specialFeatureCount,
|
||||
dataCategoryCount: counts.dataCategoryCount,
|
||||
vendorRelationshipCount: counts.vendorRelationshipCount,
|
||||
technicalFields: {
|
||||
snapshotStore: "gvl_snapshots",
|
||||
vendorListVersion: "vendorListVersion",
|
||||
sha256: "sha256",
|
||||
fetchedAt: "fetchedAt",
|
||||
sourceUrl: "sourceUrl"
|
||||
},
|
||||
diagnostics: {
|
||||
eventDiagnostics: event?.diagnostics ?? null
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function handleGetLatestConsentStateMessage() {
|
||||
const db = await openVendorGetDb();
|
||||
const latestStateOrNull = await getLatestConsentState(db);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
consentState: latestStateOrNull
|
||||
};
|
||||
}
|
||||
|
||||
async function handleListRecentConsentStatesMessage() {
|
||||
const db = await openVendorGetDb();
|
||||
const consentStates = await listRecentConsentStates(db, 25);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
consentStates
|
||||
};
|
||||
}
|
||||
|
||||
function getLatestConsentState(db) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(["consent_states"], "readonly");
|
||||
const statesStore = tx.objectStore("consent_states");
|
||||
const lastSeenAtIndex = statesStore.index("lastSeenAt");
|
||||
const cursorRequest = lastSeenAtIndex.openCursor(null, "prev");
|
||||
let latestStateOrNull = null;
|
||||
|
||||
cursorRequest.onerror = () => reject(cursorRequest.error);
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result;
|
||||
|
||||
if (cursor) {
|
||||
latestStateOrNull = cursor.value;
|
||||
}
|
||||
};
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(latestStateOrNull);
|
||||
});
|
||||
}
|
||||
|
||||
function listRecentConsentStates(db, limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const consentStates = [];
|
||||
const tx = db.transaction(["consent_states"], "readonly");
|
||||
const statesStore = tx.objectStore("consent_states");
|
||||
const lastSeenAtIndex = statesStore.index("lastSeenAt");
|
||||
const cursorRequest = lastSeenAtIndex.openCursor(null, "prev");
|
||||
|
||||
cursorRequest.onerror = () => reject(cursorRequest.error);
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result;
|
||||
|
||||
if (!cursor || consentStates.length >= limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
consentStates.push(cursor.value);
|
||||
|
||||
if (consentStates.length < limit) {
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(consentStates);
|
||||
});
|
||||
}
|
||||
|
||||
function listRecentGvlSnapshots(db, limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const snapshots = [];
|
||||
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
|
||||
const snapshotsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlSnapshots);
|
||||
const vendorListVersionIndex = snapshotsStore.index("vendorListVersion");
|
||||
const cursorRequest = vendorListVersionIndex.openCursor(null, "prev");
|
||||
|
||||
cursorRequest.onerror = () => reject(cursorRequest.error);
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result;
|
||||
|
||||
if (!cursor || snapshots.length >= limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
snapshots.push(cursor.value);
|
||||
|
||||
if (snapshots.length < limit) {
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(snapshots);
|
||||
});
|
||||
}
|
||||
|
||||
function getAnyGvlSnapshotEventBySha256(db, sha256) {
|
||||
if (!sha256) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(
|
||||
[VENDORGET_STORE_NAMES.gvlSnapshotEvents],
|
||||
"readonly"
|
||||
);
|
||||
const eventsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlSnapshotEvents);
|
||||
const sha256Index = eventsStore.index("sha256");
|
||||
const getRequest = sha256Index.get(sha256);
|
||||
let eventOrNull = null;
|
||||
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
getRequest.onsuccess = () => {
|
||||
eventOrNull = getRequest.result ?? null;
|
||||
};
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(eventOrNull);
|
||||
});
|
||||
}
|
||||
|
||||
async function getGvlSnapshotByIdentifier(db, { sha256, vendorListVersion }) {
|
||||
if (sha256) {
|
||||
return getGvlSnapshotBySha256(db, sha256);
|
||||
}
|
||||
|
||||
if (vendorListVersion !== null && vendorListVersion !== undefined) {
|
||||
return getGvlSnapshotByVendorListVersion(db, vendorListVersion);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getGvlSnapshotBySha256(db, sha256) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
|
||||
const snapshotsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlSnapshots);
|
||||
const getRequest = snapshotsStore.get(sha256);
|
||||
let snapshotOrNull = null;
|
||||
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
getRequest.onsuccess = () => {
|
||||
snapshotOrNull = getRequest.result ?? null;
|
||||
};
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(snapshotOrNull);
|
||||
});
|
||||
}
|
||||
|
||||
function getGvlSnapshotByVendorListVersion(db, vendorListVersion) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
|
||||
const snapshotsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlSnapshots);
|
||||
const vendorListVersionIndex = snapshotsStore.index("vendorListVersion");
|
||||
const getRequest = vendorListVersionIndex.get(vendorListVersion);
|
||||
let snapshotOrNull = null;
|
||||
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
getRequest.onsuccess = () => {
|
||||
snapshotOrNull = getRequest.result ?? null;
|
||||
};
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(snapshotOrNull);
|
||||
});
|
||||
}
|
||||
|
||||
function countGvlNormalizedRecordsForVersion(db, vendorListVersion) {
|
||||
if (vendorListVersion === null || vendorListVersion === undefined) {
|
||||
return Promise.resolve({
|
||||
vendorCount: 0,
|
||||
purposeCount: 0,
|
||||
specialPurposeCount: 0,
|
||||
featureCount: 0,
|
||||
specialFeatureCount: 0,
|
||||
dataCategoryCount: 0,
|
||||
vendorRelationshipCount: 0
|
||||
});
|
||||
}
|
||||
|
||||
const countDefinitions = [
|
||||
["vendorCount", VENDORGET_STORE_NAMES.gvlVendors],
|
||||
["purposeCount", VENDORGET_STORE_NAMES.gvlPurposes],
|
||||
["specialPurposeCount", VENDORGET_STORE_NAMES.gvlSpecialPurposes],
|
||||
["featureCount", VENDORGET_STORE_NAMES.gvlFeatures],
|
||||
["specialFeatureCount", VENDORGET_STORE_NAMES.gvlSpecialFeatures],
|
||||
["dataCategoryCount", VENDORGET_STORE_NAMES.gvlDataCategories],
|
||||
[
|
||||
"vendorRelationshipCount",
|
||||
VENDORGET_STORE_NAMES.gvlVendorRelationships
|
||||
]
|
||||
];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const counts = {};
|
||||
const tx = db.transaction(
|
||||
countDefinitions.map((definition) => definition[1]),
|
||||
"readonly"
|
||||
);
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(counts);
|
||||
|
||||
for (const [countName, storeName] of countDefinitions) {
|
||||
const store = tx.objectStore(storeName);
|
||||
const vendorListVersionIndex = store.index("vendorListVersion");
|
||||
const countRequest = vendorListVersionIndex.count(
|
||||
IDBKeyRange.only(vendorListVersion)
|
||||
);
|
||||
|
||||
countRequest.onsuccess = () => {
|
||||
counts[countName] = countRequest.result;
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function handlePurgeUnlockedEvidenceRecordsMessage() {
|
||||
const db = await openVendorGetDb();
|
||||
|
||||
@@ -190,26 +562,13 @@ function isGvlImportCandidate(value) {
|
||||
|
||||
async function handleFetchOfficialGvlMessage() {
|
||||
try {
|
||||
const response = await fetch(OFFICIAL_IAB_GVL_URL, {
|
||||
method: "GET",
|
||||
cache: "no-store"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
error: "official_gvl_fetch_failed",
|
||||
responseStatus: response.status
|
||||
};
|
||||
}
|
||||
|
||||
const rawJson = await response.json();
|
||||
const { rawJson, responseStatus } = await fetchOfficialGvlJson();
|
||||
|
||||
if (!isGvlImportCandidate(rawJson)) {
|
||||
return {
|
||||
success: false,
|
||||
error: "invalid_gvl_json",
|
||||
responseStatus: response.status
|
||||
responseStatus: responseStatus
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,7 +578,7 @@ async function handleFetchOfficialGvlMessage() {
|
||||
fetchedAt: new Date().toISOString(),
|
||||
diagnostics: {
|
||||
ingestionSource: "official_iab_fetch",
|
||||
responseStatus: response.status
|
||||
responseStatus: responseStatus
|
||||
}
|
||||
});
|
||||
|
||||
@@ -239,6 +598,491 @@ async function handleFetchOfficialGvlMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchOfficialGvlJson() {
|
||||
const response = await fetch(OFFICIAL_IAB_GVL_URL, {
|
||||
method: "GET",
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
"Cache-Control": "no-cache",
|
||||
Pragma: "no-cache"
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error("official_gvl_fetch_failed");
|
||||
|
||||
error.responseStatus = response.status;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
rawJson: await response.json(),
|
||||
responseStatus: response.status
|
||||
};
|
||||
}
|
||||
|
||||
async function runStartupGvlAutoUpdateCheck() {
|
||||
if (isAutoGvlCheckRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAutoGvlCheckRunning = true;
|
||||
|
||||
const throttleState = await getAutoGvlCheckThrottleState();
|
||||
const throttleDecision = shouldThrottleAutoGvlCheck(
|
||||
throttleState?.lastAutoGvlCheckAt ?? null
|
||||
);
|
||||
|
||||
if (throttleDecision.throttled) {
|
||||
await handleThrottledStartupGvlAutoUpdateCheck(
|
||||
throttleState,
|
||||
throttleDecision
|
||||
);
|
||||
isAutoGvlCheckRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
lastAutoGvlCheckStartedAt = new Date().toISOString();
|
||||
await storeAutoGvlCheckThrottleState({
|
||||
lastAutoGvlCheckAt: lastAutoGvlCheckStartedAt,
|
||||
lastAutoGvlCheckResult: "started"
|
||||
});
|
||||
|
||||
const startedStatus = {
|
||||
checkedAt: lastAutoGvlCheckStartedAt,
|
||||
previousVendorListVersion: null,
|
||||
currentVendorListVersion: null,
|
||||
previousSnapshotSha256: null,
|
||||
currentSnapshotSha256: null,
|
||||
latestLocalVendorListVersion: null,
|
||||
latestLocalSnapshotSha256: null,
|
||||
latestLocalFetchedAt: null,
|
||||
lastAutoGvlCheckAt: lastAutoGvlCheckStartedAt,
|
||||
nextAllowedAutoCheckAt: getNextAllowedAutoGvlCheckAt(
|
||||
lastAutoGvlCheckStartedAt
|
||||
),
|
||||
result: "started",
|
||||
message: "Automatischer GVL-Update-Check gestartet."
|
||||
};
|
||||
|
||||
latestGvlUpdateStatus = startedStatus;
|
||||
|
||||
try {
|
||||
const db = await openVendorGetDb();
|
||||
const previousSnapshot = await getLatestGvlSnapshotByVendorListVersion(db);
|
||||
const previousVendorListVersion =
|
||||
previousSnapshot?.vendorListVersion ?? null;
|
||||
const previousSnapshotSha256 = previousSnapshot?.sha256 ?? null;
|
||||
const previousFetchedAt = previousSnapshot?.fetchedAt ?? null;
|
||||
|
||||
latestGvlUpdateStatus = {
|
||||
checkedAt: lastAutoGvlCheckStartedAt,
|
||||
previousVendorListVersion,
|
||||
currentVendorListVersion: null,
|
||||
previousSnapshotSha256,
|
||||
currentSnapshotSha256: null,
|
||||
latestLocalVendorListVersion: previousVendorListVersion,
|
||||
latestLocalSnapshotSha256: previousSnapshotSha256,
|
||||
latestLocalFetchedAt: previousFetchedAt,
|
||||
lastAutoGvlCheckAt: lastAutoGvlCheckStartedAt,
|
||||
nextAllowedAutoCheckAt: getNextAllowedAutoGvlCheckAt(
|
||||
lastAutoGvlCheckStartedAt
|
||||
),
|
||||
result: "started",
|
||||
message: "Automatischer GVL-Update-Check gestartet."
|
||||
};
|
||||
|
||||
await recordGvlAutoUpdateEvent(db, {
|
||||
eventType: "gvl_auto_update_check_started",
|
||||
checkedAt: lastAutoGvlCheckStartedAt,
|
||||
previousSnapshot,
|
||||
currentVendorListVersion: null,
|
||||
currentSnapshotSha256: null,
|
||||
result: "started"
|
||||
});
|
||||
|
||||
const { rawJson, responseStatus } = await fetchOfficialGvlJson();
|
||||
|
||||
if (!isGvlImportCandidate(rawJson)) {
|
||||
throw new Error("invalid_gvl_json");
|
||||
}
|
||||
|
||||
const currentVendorListVersion = rawJson.vendorListVersion ?? null;
|
||||
const currentSnapshotSha256 =
|
||||
await VendorGetGvlService.calculateGvlSnapshotSha256(rawJson);
|
||||
const newVersionDetected = isNewerGvlVendorListVersion(
|
||||
currentVendorListVersion,
|
||||
previousVendorListVersion
|
||||
);
|
||||
|
||||
const ingestResult = await VendorGetGvlService.ingestGvlSnapshot(
|
||||
db,
|
||||
rawJson,
|
||||
{
|
||||
sourceUrl: OFFICIAL_IAB_GVL_URL,
|
||||
fetchedAt: new Date().toISOString(),
|
||||
diagnostics: {
|
||||
ingestionSource: "official_iab_auto_update",
|
||||
responseStatus: responseStatus,
|
||||
updateCheckSource: GVL_AUTO_UPDATE_SOURCE,
|
||||
checkedAt: lastAutoGvlCheckStartedAt,
|
||||
previousVendorListVersion: previousVendorListVersion,
|
||||
previousSnapshotSha256: previousSnapshotSha256,
|
||||
previousFetchedAt: previousFetchedAt,
|
||||
currentVendorListVersion: currentVendorListVersion,
|
||||
currentSnapshotSha256: currentSnapshotSha256,
|
||||
result: newVersionDetected ? "new_version_detected" : "no_change"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let normalizationSummary = null;
|
||||
|
||||
if (!ingestResult.alreadyKnown && newVersionDetected) {
|
||||
normalizationSummary = await normalizeGvlSnapshotWithExistingPipeline(
|
||||
ingestResult.snapshot
|
||||
);
|
||||
}
|
||||
|
||||
const result = buildGvlAutoUpdateResult({
|
||||
newVersionDetected,
|
||||
alreadyKnown: ingestResult.alreadyKnown
|
||||
});
|
||||
|
||||
const status = {
|
||||
checkedAt: lastAutoGvlCheckStartedAt,
|
||||
previousVendorListVersion,
|
||||
currentVendorListVersion,
|
||||
previousSnapshotSha256,
|
||||
currentSnapshotSha256,
|
||||
latestLocalVendorListVersion: ingestResult.snapshot.vendorListVersion,
|
||||
latestLocalSnapshotSha256: ingestResult.snapshot.sha256,
|
||||
latestLocalFetchedAt: ingestResult.snapshot.fetchedAt ?? null,
|
||||
lastAutoGvlCheckAt: lastAutoGvlCheckStartedAt,
|
||||
nextAllowedAutoCheckAt: getNextAllowedAutoGvlCheckAt(
|
||||
lastAutoGvlCheckStartedAt
|
||||
),
|
||||
result,
|
||||
message: buildGvlAutoUpdateMessage(result),
|
||||
normalizationSummary
|
||||
};
|
||||
|
||||
latestGvlUpdateStatus = status;
|
||||
await storeAutoGvlCheckThrottleState({
|
||||
lastAutoGvlCheckAt: lastAutoGvlCheckStartedAt,
|
||||
lastAutoGvlCheckResult: result
|
||||
});
|
||||
|
||||
await recordGvlAutoUpdateEvent(db, {
|
||||
eventType: buildGvlAutoUpdateEventType(result),
|
||||
checkedAt: lastAutoGvlCheckStartedAt,
|
||||
previousSnapshot,
|
||||
currentVendorListVersion,
|
||||
currentSnapshotSha256,
|
||||
currentSnapshot: ingestResult.snapshot,
|
||||
result,
|
||||
normalizationSummary
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("VG-Observe automatic official GVL update check failed", error);
|
||||
|
||||
const checkedAt = lastAutoGvlCheckStartedAt ?? new Date().toISOString();
|
||||
const errorMessage = error?.message ?? String(error);
|
||||
|
||||
latestGvlUpdateStatus = {
|
||||
checkedAt,
|
||||
previousVendorListVersion: latestGvlUpdateStatus?.previousVendorListVersion ?? null,
|
||||
currentVendorListVersion: latestGvlUpdateStatus?.currentVendorListVersion ?? null,
|
||||
previousSnapshotSha256: latestGvlUpdateStatus?.previousSnapshotSha256 ?? null,
|
||||
currentSnapshotSha256: latestGvlUpdateStatus?.currentSnapshotSha256 ?? null,
|
||||
latestLocalVendorListVersion:
|
||||
latestGvlUpdateStatus?.latestLocalVendorListVersion ?? null,
|
||||
latestLocalSnapshotSha256:
|
||||
latestGvlUpdateStatus?.latestLocalSnapshotSha256 ?? null,
|
||||
latestLocalFetchedAt: latestGvlUpdateStatus?.latestLocalFetchedAt ?? null,
|
||||
lastAutoGvlCheckAt: lastAutoGvlCheckStartedAt,
|
||||
nextAllowedAutoCheckAt: getNextAllowedAutoGvlCheckAt(
|
||||
lastAutoGvlCheckStartedAt
|
||||
),
|
||||
result: "error",
|
||||
message: "Auto-Check fehlgeschlagen",
|
||||
error: errorMessage
|
||||
};
|
||||
await storeAutoGvlCheckThrottleState({
|
||||
lastAutoGvlCheckAt: lastAutoGvlCheckStartedAt,
|
||||
lastAutoGvlCheckResult: "error"
|
||||
});
|
||||
|
||||
try {
|
||||
const db = await openVendorGetDb();
|
||||
const previousSnapshot = await getLatestGvlSnapshotByVendorListVersion(db);
|
||||
|
||||
await recordGvlAutoUpdateEvent(db, {
|
||||
eventType: "gvl_auto_update_error",
|
||||
checkedAt,
|
||||
previousSnapshot,
|
||||
currentVendorListVersion: null,
|
||||
currentSnapshotSha256: null,
|
||||
result: "error",
|
||||
error: errorMessage
|
||||
});
|
||||
} catch (eventError) {
|
||||
console.warn("VG-Observe automatic GVL error event failed", eventError);
|
||||
}
|
||||
} finally {
|
||||
isAutoGvlCheckRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleThrottledStartupGvlAutoUpdateCheck(
|
||||
throttleState,
|
||||
throttleDecision
|
||||
) {
|
||||
const checkedAt = new Date().toISOString();
|
||||
|
||||
try {
|
||||
const db = await openVendorGetDb();
|
||||
const latestSnapshot = await getLatestGvlSnapshotByVendorListVersion(db);
|
||||
|
||||
latestGvlUpdateStatus = {
|
||||
checkedAt,
|
||||
previousVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
currentVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
previousSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
currentSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
latestLocalVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
latestLocalSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
latestLocalFetchedAt: latestSnapshot?.fetchedAt ?? null,
|
||||
lastAutoGvlCheckAt: throttleState?.lastAutoGvlCheckAt ?? null,
|
||||
nextAllowedAutoCheckAt: throttleDecision.nextAllowedAutoCheckAt,
|
||||
result: "throttled",
|
||||
message: "Auto-Check wegen 24h-Throttling übersprungen."
|
||||
};
|
||||
|
||||
await recordGvlAutoUpdateEvent(db, {
|
||||
eventType: "gvl_auto_update_throttled",
|
||||
checkedAt,
|
||||
previousSnapshot: latestSnapshot,
|
||||
currentVendorListVersion: latestSnapshot?.vendorListVersion ?? null,
|
||||
currentSnapshotSha256: latestSnapshot?.sha256 ?? null,
|
||||
result: "throttled",
|
||||
throttleDiagnostics: {
|
||||
lastAutoGvlCheckAt: throttleState?.lastAutoGvlCheckAt ?? null,
|
||||
throttleMs: AUTO_GVL_CHECK_THROTTLE_MS,
|
||||
nextAllowedAutoCheckAt: throttleDecision.nextAllowedAutoCheckAt
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("VG-Observe automatic GVL throttled event failed", error);
|
||||
|
||||
latestGvlUpdateStatus = {
|
||||
checkedAt,
|
||||
previousVendorListVersion: null,
|
||||
currentVendorListVersion: null,
|
||||
previousSnapshotSha256: null,
|
||||
currentSnapshotSha256: null,
|
||||
latestLocalVendorListVersion: null,
|
||||
latestLocalSnapshotSha256: null,
|
||||
latestLocalFetchedAt: null,
|
||||
lastAutoGvlCheckAt: throttleState?.lastAutoGvlCheckAt ?? null,
|
||||
nextAllowedAutoCheckAt: throttleDecision.nextAllowedAutoCheckAt,
|
||||
result: "throttled",
|
||||
message:
|
||||
"Auto-Check wegen 24h-Throttling übersprungen; Status-Event konnte nicht geschrieben werden."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getAutoGvlCheckThrottleState() {
|
||||
try {
|
||||
const storedValue = await browser.storage.local.get(
|
||||
AUTO_GVL_CHECK_STORAGE_KEY
|
||||
);
|
||||
|
||||
return storedValue[AUTO_GVL_CHECK_STORAGE_KEY] ?? {};
|
||||
} catch (error) {
|
||||
console.warn("VG-Observe automatic GVL throttle state unavailable", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function storeAutoGvlCheckThrottleState(state) {
|
||||
try {
|
||||
await browser.storage.local.set({
|
||||
[AUTO_GVL_CHECK_STORAGE_KEY]: state
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("VG-Observe automatic GVL throttle state write failed", error);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldThrottleAutoGvlCheck(lastAutoGvlCheckAt) {
|
||||
if (!lastAutoGvlCheckAt) {
|
||||
return {
|
||||
throttled: false,
|
||||
nextAllowedAutoCheckAt: null
|
||||
};
|
||||
}
|
||||
|
||||
const lastCheckTime = Date.parse(lastAutoGvlCheckAt);
|
||||
|
||||
if (Number.isNaN(lastCheckTime)) {
|
||||
return {
|
||||
throttled: false,
|
||||
nextAllowedAutoCheckAt: null
|
||||
};
|
||||
}
|
||||
|
||||
const nextAllowedTime = lastCheckTime + AUTO_GVL_CHECK_THROTTLE_MS;
|
||||
const now = Date.now();
|
||||
|
||||
return {
|
||||
throttled: now < nextAllowedTime,
|
||||
nextAllowedAutoCheckAt: new Date(nextAllowedTime).toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
function getNextAllowedAutoGvlCheckAt(lastAutoGvlCheckAt) {
|
||||
if (!lastAutoGvlCheckAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastCheckTime = Date.parse(lastAutoGvlCheckAt);
|
||||
|
||||
if (Number.isNaN(lastCheckTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Date(lastCheckTime + AUTO_GVL_CHECK_THROTTLE_MS).toISOString();
|
||||
}
|
||||
|
||||
function getLatestGvlSnapshotByVendorListVersion(db) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
|
||||
const snapshotsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlSnapshots);
|
||||
const vendorListVersionIndex = snapshotsStore.index("vendorListVersion");
|
||||
const cursorRequest = vendorListVersionIndex.openCursor(null, "prev");
|
||||
let latestSnapshotOrNull = null;
|
||||
|
||||
cursorRequest.onerror = () => reject(cursorRequest.error);
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result;
|
||||
|
||||
if (cursor) {
|
||||
latestSnapshotOrNull = cursor.value;
|
||||
}
|
||||
};
|
||||
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
tx.oncomplete = () => resolve(latestSnapshotOrNull);
|
||||
});
|
||||
}
|
||||
|
||||
function isNewerGvlVendorListVersion(currentVersion, previousVersion) {
|
||||
if (currentVersion === null || currentVersion === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (previousVersion === null || previousVersion === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Number(currentVersion) > Number(previousVersion);
|
||||
}
|
||||
|
||||
async function normalizeGvlSnapshotWithExistingPipeline(snapshot) {
|
||||
return {
|
||||
vendors: await normalizeGvlVendorsFromSnapshot(snapshot),
|
||||
catalogs: await normalizeGvlCatalogsFromSnapshot(snapshot),
|
||||
vendorRelationships:
|
||||
await normalizeGvlVendorRelationshipsFromSnapshot(snapshot)
|
||||
};
|
||||
}
|
||||
|
||||
function buildGvlAutoUpdateResult({ newVersionDetected, alreadyKnown }) {
|
||||
if (newVersionDetected && !alreadyKnown) {
|
||||
return "stored";
|
||||
}
|
||||
|
||||
if (newVersionDetected && alreadyKnown) {
|
||||
return "already_known";
|
||||
}
|
||||
|
||||
if (!newVersionDetected && !alreadyKnown) {
|
||||
return "stored";
|
||||
}
|
||||
|
||||
return "no_change";
|
||||
}
|
||||
|
||||
function buildGvlAutoUpdateEventType(result) {
|
||||
if (result === "stored") {
|
||||
return "gvl_auto_update_stored";
|
||||
}
|
||||
|
||||
if (result === "already_known") {
|
||||
return "gvl_auto_update_detected";
|
||||
}
|
||||
|
||||
return "gvl_auto_update_no_change";
|
||||
}
|
||||
|
||||
function buildGvlAutoUpdateMessage(result) {
|
||||
if (result === "stored") {
|
||||
return "Neue offizielle IAB-Europe-Vendorliste gespeichert.";
|
||||
}
|
||||
|
||||
if (result === "already_known") {
|
||||
return "Offizielle IAB-Europe-Vendorliste war bereits lokal bekannt.";
|
||||
}
|
||||
|
||||
return "Keine neuere offizielle IAB-Europe-Vendorliste gefunden.";
|
||||
}
|
||||
|
||||
function recordGvlAutoUpdateEvent(
|
||||
db,
|
||||
{
|
||||
eventType,
|
||||
checkedAt,
|
||||
previousSnapshot,
|
||||
currentVendorListVersion,
|
||||
currentSnapshotSha256,
|
||||
currentSnapshot,
|
||||
result,
|
||||
normalizationSummary,
|
||||
error,
|
||||
throttleDiagnostics
|
||||
}
|
||||
) {
|
||||
return VendorGetGvlService.recordGvlSnapshotEvent(db, {
|
||||
eventType,
|
||||
capturedAt: checkedAt,
|
||||
vendorListVersion:
|
||||
currentVendorListVersion ?? previousSnapshot?.vendorListVersion ?? null,
|
||||
sha256: currentSnapshotSha256 ?? previousSnapshot?.sha256 ?? null,
|
||||
sourceUrl: OFFICIAL_IAB_GVL_URL,
|
||||
diagnostics: {
|
||||
updateCheckSource: GVL_AUTO_UPDATE_SOURCE,
|
||||
checkedAt,
|
||||
previousVendorListVersion: previousSnapshot?.vendorListVersion ?? null,
|
||||
currentVendorListVersion: currentVendorListVersion ?? null,
|
||||
previousSnapshotSha256: previousSnapshot?.sha256 ?? null,
|
||||
currentSnapshotSha256: currentSnapshotSha256 ?? null,
|
||||
previousFetchedAt: previousSnapshot?.fetchedAt ?? null,
|
||||
currentFetchedAt: currentSnapshot?.fetchedAt ?? null,
|
||||
result,
|
||||
vendorCountBefore: previousSnapshot?.vendorCount ?? null,
|
||||
vendorCountAfter: currentSnapshot?.vendorCount ?? null,
|
||||
purposeCountBefore: previousSnapshot?.purposeCount ?? null,
|
||||
purposeCountAfter: currentSnapshot?.purposeCount ?? null,
|
||||
normalizationSummary: normalizationSummary ?? null,
|
||||
error: error ?? null,
|
||||
...(throttleDiagnostics ?? {})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildConsentStateV1(rawCapture, sender, latestPingData) {
|
||||
const decodedTcString = decodeTcStringCoreMetadata(rawCapture?.tcString ?? null);
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren