Improve GVL explorer vendor evidence workflow

Dieser Commit ist enthalten in:
2026-06-08 22:53:01 +02:00
Ursprung 990da710c1
Commit 3fd40348b5
4 geänderte Dateien mit 854 neuen und 21 gelöschten Zeilen
+340 -12
Datei anzeigen
@@ -73,6 +73,18 @@ async function handleVendorGetMessage(message, sender) {
return handleGetGvlSnapshotSummaryMessage(message);
}
if (message.type === "rebuild_gvl_snapshot_normalized_data") {
return handleRebuildGvlSnapshotNormalizedDataMessage(message);
}
if (message.type === "list_gvl_vendors_for_snapshot") {
return handleListGvlVendorsForSnapshotMessage(message);
}
if (message.type === "get_gvl_vendor_detail") {
return handleGetGvlVendorDetailMessage(message);
}
if (message.type === "get_latest_consent_state") {
return handleGetLatestConsentStateMessage();
}
@@ -280,12 +292,15 @@ async function handleGetGvlSnapshotSummaryMessage(message) {
eventType: event?.eventType ?? null,
eventCapturedAt: event?.capturedAt ?? null,
vendorCount: snapshot.vendorCount ?? counts.vendorCount,
snapshotVendorCount: snapshot.vendorCount ?? null,
normalizedVendorCount: counts.vendorCount,
purposeCount: snapshot.purposeCount ?? counts.purposeCount,
specialPurposeCount: counts.specialPurposeCount,
featureCount: counts.featureCount,
specialFeatureCount: counts.specialFeatureCount,
dataCategoryCount: counts.dataCategoryCount,
vendorRelationshipCount: counts.vendorRelationshipCount,
normalizedVendorRelationshipCount: counts.vendorRelationshipCount,
technicalFields: {
snapshotStore: "gvl_snapshots",
vendorListVersion: "vendorListVersion",
@@ -300,6 +315,267 @@ async function handleGetGvlSnapshotSummaryMessage(message) {
};
}
async function handleRebuildGvlSnapshotNormalizedDataMessage(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 normalizationSummary = await normalizeGvlSnapshotWithExistingPipeline(
snapshot
);
const counts = await countGvlNormalizedRecordsForVersion(
db,
snapshot.vendorListVersion ?? null
);
return {
success: true,
snapshotSha256: snapshot.sha256 ?? null,
vendorListVersion: snapshot.vendorListVersion ?? null,
normalizationSummary,
counts
};
}
async function handleListGvlVendorsForSnapshotMessage(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 vendors = await listGvlVendorsForSnapshot(db, snapshot);
return {
success: true,
snapshotSha256: snapshot.sha256 ?? null,
vendorListVersion: snapshot.vendorListVersion ?? null,
vendors
};
}
function listGvlVendorsForSnapshot(db, snapshot) {
return new Promise((resolve, reject) => {
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlVendors], "readonly");
const vendorsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlVendors);
const vendorListVersionIndex = vendorsStore.index("vendorListVersion");
const cursorRequest = vendorListVersionIndex.openCursor(
IDBKeyRange.only(snapshot.vendorListVersion)
);
const vendors = [];
cursorRequest.onerror = () => reject(cursorRequest.error);
cursorRequest.onsuccess = () => {
const cursor = cursorRequest.result;
if (!cursor) {
return;
}
if (cursor.value?.snapshotSha256 === snapshot.sha256) {
vendors.push({
vendorId: cursor.value.vendorId ?? null,
name: cursor.value.name ?? null,
deletedDate: cursor.value.deletedDate ?? null,
snapshotSha256: cursor.value.snapshotSha256 ?? null
});
}
cursor.continue();
};
tx.onerror = () => reject(tx.error);
tx.onabort = () => reject(tx.error);
tx.oncomplete = () => {
resolve(
vendors.sort((left, right) => {
return (
toComparableNumber(left.vendorId) -
toComparableNumber(right.vendorId)
);
})
);
};
});
}
async function handleGetGvlVendorDetailMessage(message) {
const db = await openVendorGetDb();
const vendorId = parseGvlVendorDetailId(message?.payload?.vendorId);
if (vendorId === null) {
return {
success: false,
error: "invalid_vendor_id"
};
}
const vendorRecord = await getLatestGvlVendorByVendorId(db, vendorId);
if (!vendorRecord) {
return {
success: false,
error: "gvl_vendor_not_found"
};
}
const snapshotSha256 = vendorRecord.snapshotSha256 ?? null;
const snapshot = snapshotSha256
? await getGvlSnapshotBySha256(db, snapshotSha256)
: null;
const rawGvlSha256 = snapshot?.rawGvlSha256 ?? null;
const rawEvidence = rawGvlSha256
? await getGvlRawEvidenceBySha256(db, rawGvlSha256)
: null;
return {
success: true,
vendorDetail: {
vendor: vendorRecord,
snapshot: buildGvlVendorDetailSnapshotSummary(snapshot, snapshotSha256),
rawEvidence: buildGvlVendorDetailRawEvidenceSummary(
rawEvidence,
rawGvlSha256
)
}
};
}
function parseGvlVendorDetailId(value) {
const vendorId = Number(value);
if (!Number.isInteger(vendorId) || vendorId <= 0) {
return null;
}
return vendorId;
}
function getLatestGvlVendorByVendorId(db, vendorId) {
return new Promise((resolve, reject) => {
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlVendors], "readonly");
const vendorsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlVendors);
const vendorIdIndex = vendorsStore.index("vendorId");
const cursorRequest = vendorIdIndex.openCursor(IDBKeyRange.only(vendorId));
const vendors = [];
cursorRequest.onerror = () => reject(cursorRequest.error);
cursorRequest.onsuccess = () => {
const cursor = cursorRequest.result;
if (!cursor) {
return;
}
vendors.push(cursor.value);
cursor.continue();
};
tx.onerror = () => reject(tx.error);
tx.onabort = () => reject(tx.error);
tx.oncomplete = () => {
resolve(sortGvlVendorRecordsNewestFirst(vendors)[0] ?? null);
};
});
}
function sortGvlVendorRecordsNewestFirst(vendorRecords) {
return vendorRecords.slice().sort((left, right) => {
const fetchedAtComparison =
toComparableTime(right.snapshotFetchedAt) -
toComparableTime(left.snapshotFetchedAt);
if (fetchedAtComparison !== 0) {
return fetchedAtComparison;
}
return (
toComparableNumber(right.vendorListVersion) -
toComparableNumber(left.vendorListVersion)
);
});
}
function getGvlRawEvidenceBySha256(db, rawGvlSha256) {
return new Promise((resolve, reject) => {
const tx = db.transaction([VENDORGET_STORE_NAMES.gvlRawEvidence], "readonly");
const rawEvidenceStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlRawEvidence);
const getRequest = rawEvidenceStore.get(rawGvlSha256);
let rawEvidenceOrNull = null;
getRequest.onerror = () => reject(getRequest.error);
getRequest.onsuccess = () => {
rawEvidenceOrNull = getRequest.result ?? null;
};
tx.onerror = () => reject(tx.error);
tx.onabort = () => reject(tx.error);
tx.oncomplete = () => resolve(rawEvidenceOrNull);
});
}
function buildGvlVendorDetailSnapshotSummary(snapshot, snapshotSha256) {
if (!snapshot) {
return {
snapshotSha256: snapshotSha256 ?? null,
rawGvlSha256: null,
vendorListVersion: null,
tcfPolicyVersion: null,
fetchedAt: null,
createdAt: null
};
}
return {
snapshotSha256: snapshot.sha256 ?? snapshotSha256 ?? null,
rawGvlSha256: snapshot.rawGvlSha256 ?? null,
vendorListVersion: snapshot.vendorListVersion ?? null,
tcfPolicyVersion: snapshot.tcfPolicyVersion ?? null,
fetchedAt: snapshot.fetchedAt ?? null,
createdAt: snapshot.createdAt ?? null
};
}
function buildGvlVendorDetailRawEvidenceSummary(rawEvidence, rawGvlSha256) {
if (!rawEvidence) {
return {
rawGvlSha256: rawGvlSha256 ?? null,
sourceUrl: null,
fetchedAt: null,
httpStatus: null,
contentType: null,
hasRawBody: false
};
}
return {
rawGvlSha256: rawEvidence.rawGvlSha256 ?? rawGvlSha256 ?? null,
sourceUrl: rawEvidence.sourceUrl ?? null,
fetchedAt: rawEvidence.fetchedAt ?? null,
httpStatus: rawEvidence.httpStatus ?? null,
contentType: rawEvidence.contentType ?? null,
hasRawBody: typeof rawEvidence.rawBody === "string"
};
}
async function handleGetLatestConsentStateMessage() {
const db = await openVendorGetDb();
const latestStateOrNull = await getLatestConsentState(db);
@@ -631,21 +907,55 @@ async function handleFetchOfficialGvlMessage() {
}
const db = await openVendorGetDb();
const result = await VendorGetGvlService.ingestGvlSnapshot(db, rawJson, {
sourceUrl: OFFICIAL_IAB_GVL_URL,
fetchedAt: new Date().toISOString(),
rawGvlSha256: rawGvlSha256,
diagnostics: {
ingestionSource: "official_iab_fetch",
responseStatus: responseStatus
}
});
const currentVendorListVersion = rawJson.vendorListVersion ?? null;
const existingSnapshot = await getGvlSnapshotByVendorListVersion(
db,
currentVendorListVersion
);
const ingestResult = existingSnapshot
? null
: await VendorGetGvlService.ingestGvlSnapshot(db, rawJson, {
sourceUrl: OFFICIAL_IAB_GVL_URL,
fetchedAt: new Date().toISOString(),
rawGvlSha256: rawGvlSha256,
diagnostics: {
ingestionSource: "official_iab_fetch",
responseStatus: responseStatus
}
});
const snapshot = existingSnapshot ?? ingestResult.snapshot;
const completeness = await getGvlSnapshotNormalizedCompleteness(
db,
snapshot
);
let normalizationSummary = null;
let syncStatus = "current_and_locally_available";
if (!existingSnapshot) {
normalizationSummary = await normalizeGvlSnapshotWithExistingPipeline(
snapshot
);
syncStatus = "new_gvl_revision_stored_and_normalized";
} else if (!completeness.complete) {
normalizationSummary = await normalizeGvlSnapshotWithExistingPipeline(
existingSnapshot
);
syncStatus = "known_gvl_rebuilt_from_local_evidence";
}
const counts = await countGvlNormalizedRecordsForVersion(
db,
snapshot.vendorListVersion ?? null
);
return {
success: true,
alreadyKnown: result.alreadyKnown,
vendorListVersion: result.snapshot.vendorListVersion,
sha256: result.snapshot.sha256
alreadyKnown: Boolean(existingSnapshot),
syncStatus,
vendorListVersion: snapshot.vendorListVersion,
sha256: snapshot.sha256,
normalizationSummary,
counts
};
} catch (error) {
console.warn("VG-Observe official GVL fetch failed", error);
@@ -657,6 +967,24 @@ async function handleFetchOfficialGvlMessage() {
}
}
async function getGvlSnapshotNormalizedCompleteness(db, snapshot) {
const counts = await countGvlNormalizedRecordsForVersion(
db,
snapshot?.vendorListVersion ?? null
);
const expectedVendorCount = Number(snapshot?.vendorCount ?? 0);
const hasVendors =
expectedVendorCount > 0
? counts.vendorCount >= expectedVendorCount
: counts.vendorCount > 0;
const hasVendorRelationships = counts.vendorRelationshipCount > 0;
return {
complete: hasVendors && hasVendorRelationships,
counts
};
}
async function fetchOfficialGvlJson() {
const response = await fetch(OFFICIAL_IAB_GVL_URL, {
method: "GET",