Improve GVL explorer usability and evidence workflows

Dieser Commit ist enthalten in:
2026-06-10 18:00:44 +02:00
Ursprung f8a23ba643
Commit eb70f0ce81
6 geänderte Dateien mit 1090 neuen und 22 gelöschten Zeilen
+320
Datei anzeigen
@@ -29,6 +29,22 @@ async function handleVendorGetMessage(message, sender) {
return null;
}
if (message.type === "export_gvl_evidence_json") {
return handleExportGvlEvidenceJsonMessage();
}
if (message.type === "export_gvl_revision_evidence_json") {
return handleExportGvlRevisionEvidenceJsonMessage(message);
}
if (message.type === "verify_gvl_revision_evidence_json") {
return handleVerifyGvlRevisionEvidenceJsonMessage(message);
}
if (message.type === "import_gvl_evidence_json") {
return handleImportGvlEvidenceJsonMessage(message);
}
if (message.type === "gvl_import_json") {
return handleGvlImportJsonMessage(message);
}
@@ -196,6 +212,59 @@ async function handleExportEvidenceJsonMessage() {
};
}
async function handleExportGvlEvidenceJsonMessage() {
return {
success: true,
export: await exportVendorGetGvlEvidenceJson()
};
}
async function handleExportGvlRevisionEvidenceJsonMessage(message) {
try {
return {
success: true,
export: await exportVendorGetGvlRevisionEvidenceJson(
message?.payload?.snapshotSha256 ?? null
)
};
} catch (error) {
return {
success: false,
error: error?.message ?? String(error)
};
}
}
async function handleVerifyGvlRevisionEvidenceJsonMessage(message) {
try {
return {
success: true,
verification: await verifyVendorGetGvlRevisionEvidenceJson(
message?.payload?.export ?? null
)
};
} catch (error) {
return {
success: false,
error: error?.message ?? String(error)
};
}
}
async function handleImportGvlEvidenceJsonMessage(message) {
try {
return {
success: true,
import: await importVendorGetGvlEvidenceJson(message?.payload?.export)
};
} catch (error) {
return {
success: false,
error: error?.message ?? String(error)
};
}
}
async function handleGetLatestGvlUpdateStatusMessage() {
const db = await openVendorGetDb();
const latestSnapshot = await getLatestGvlSnapshotByVendorListVersion(db);
@@ -445,11 +514,13 @@ async function handleGetGvlVendorDetailMessage(message) {
const rawEvidence = rawGvlSha256
? await getGvlRawEvidenceBySha256(db, rawGvlSha256)
: null;
const gvlInfo = await getGvlVendorDetailGvlInfo(db, vendorRecord);
return {
success: true,
vendorDetail: {
vendor: vendorRecord,
gvlInfo,
snapshot: buildGvlVendorDetailSnapshotSummary(snapshot, snapshotSha256),
rawEvidence: buildGvlVendorDetailRawEvidenceSummary(
rawEvidence,
@@ -459,6 +530,255 @@ async function handleGetGvlVendorDetailMessage(message) {
};
}
async function getGvlVendorDetailGvlInfo(db, vendorRecord) {
const vendorListVersion = vendorRecord?.vendorListVersion ?? null;
const vendorId = vendorRecord?.vendorId ?? null;
if (vendorListVersion === null || vendorId === null) {
return buildEmptyGvlVendorDetailGvlInfo();
}
const relationships = await getGvlVendorRelationshipsForVendor(
db,
vendorListVersion,
vendorId
);
const catalogs = await getGvlVendorDetailCatalogs(db, vendorListVersion);
const rawVendor = vendorRecord?.rawVendor ?? {};
return {
purposes: buildGvlVendorDetailCatalogList(
rawVendor.purposes,
relationships.purpose,
catalogs.purposes
),
legIntPurposes: buildGvlVendorDetailCatalogList(
rawVendor.legIntPurposes,
relationships.legIntPurpose,
catalogs.purposes
),
flexiblePurposes: buildGvlVendorDetailCatalogList(
rawVendor.flexiblePurposes,
relationships.flexiblePurpose,
catalogs.purposes
),
specialPurposes: buildGvlVendorDetailCatalogList(
rawVendor.specialPurposes,
relationships.specialPurpose,
catalogs.specialPurposes
),
features: buildGvlVendorDetailCatalogList(
rawVendor.features,
relationships.feature,
catalogs.features
),
specialFeatures: buildGvlVendorDetailCatalogList(
rawVendor.specialFeatures,
relationships.specialFeature,
catalogs.specialFeatures
),
dataDeclaration: buildGvlVendorDetailCatalogList(
rawVendor.dataDeclaration,
[],
catalogs.dataCategories
),
dataCategoriesTextAvailable: catalogs.dataCategories.size > 0,
dataRetention: rawVendor.dataRetention ?? null,
overflow: rawVendor.overflow ?? null
};
}
function buildEmptyGvlVendorDetailGvlInfo() {
return {
purposes: [],
legIntPurposes: [],
flexiblePurposes: [],
specialPurposes: [],
features: [],
specialFeatures: [],
dataDeclaration: [],
dataCategoriesTextAvailable: false,
dataRetention: null,
overflow: null
};
}
function getGvlVendorRelationshipsForVendor(db, vendorListVersion, vendorId) {
return new Promise((resolve, reject) => {
const storeName = VENDORGET_STORE_NAMES.gvlVendorRelationships;
const tx = db.transaction([storeName], "readonly");
const relationshipsStore = tx.objectStore(storeName);
const vendorIdIndex = relationshipsStore.index("vendorId");
const cursorRequest = vendorIdIndex.openCursor(IDBKeyRange.only(vendorId));
const relationships = {};
cursorRequest.onerror = () => reject(cursorRequest.error);
cursorRequest.onsuccess = () => {
const cursor = cursorRequest.result;
if (!cursor) {
return;
}
const record = cursor.value;
if (record?.vendorListVersion === vendorListVersion) {
const relationshipType = record.relationshipType ?? "unknown";
if (!relationships[relationshipType]) {
relationships[relationshipType] = [];
}
relationships[relationshipType].push(record.relatedId);
}
cursor.continue();
};
tx.onerror = () => reject(tx.error);
tx.onabort = () => reject(tx.error);
tx.oncomplete = () => resolve(relationships);
});
}
async function getGvlVendorDetailCatalogs(db, vendorListVersion) {
const [
purposes,
specialPurposes,
features,
specialFeatures,
dataCategories
] = await Promise.all([
getGvlCatalogMapForVersion(
db,
VENDORGET_STORE_NAMES.gvlPurposes,
vendorListVersion
),
getGvlCatalogMapForVersion(
db,
VENDORGET_STORE_NAMES.gvlSpecialPurposes,
vendorListVersion
),
getGvlCatalogMapForVersion(
db,
VENDORGET_STORE_NAMES.gvlFeatures,
vendorListVersion
),
getGvlCatalogMapForVersion(
db,
VENDORGET_STORE_NAMES.gvlSpecialFeatures,
vendorListVersion
),
getGvlCatalogMapForVersion(
db,
VENDORGET_STORE_NAMES.gvlDataCategories,
vendorListVersion
)
]);
return {
purposes,
specialPurposes,
features,
specialFeatures,
dataCategories
};
}
function getGvlCatalogMapForVersion(db, storeName, vendorListVersion) {
return new Promise((resolve, reject) => {
const tx = db.transaction([storeName], "readonly");
const catalogStore = tx.objectStore(storeName);
const vendorListVersionIndex = catalogStore.index("vendorListVersion");
const cursorRequest = vendorListVersionIndex.openCursor(
IDBKeyRange.only(vendorListVersion)
);
const catalogMap = new Map();
cursorRequest.onerror = () => reject(cursorRequest.error);
cursorRequest.onsuccess = () => {
const cursor = cursorRequest.result;
if (!cursor) {
return;
}
const record = cursor.value;
const catalogId = getGvlCatalogRecordId(record);
if (catalogId !== null) {
catalogMap.set(catalogId, record);
}
cursor.continue();
};
tx.onerror = () => reject(tx.error);
tx.onabort = () => reject(tx.error);
tx.oncomplete = () => resolve(catalogMap);
});
}
function getGvlCatalogRecordId(record) {
const catalogId =
record?.catalogId ??
record?.purposeId ??
record?.specialPurposeId ??
record?.featureId ??
record?.specialFeatureId ??
record?.dataCategoryId ??
null;
if (catalogId === null || catalogId === undefined) {
return null;
}
const numericId = Number(catalogId);
return Number.isFinite(numericId) ? numericId : null;
}
function buildGvlVendorDetailCatalogList(rawIds, relationshipIds, catalogMap) {
const ids = mergeGvlVendorDetailIds(rawIds, relationshipIds);
return ids.map((id) => {
const catalogRecord = catalogMap.get(id) ?? null;
return {
id,
name: catalogRecord?.name ?? null,
description: catalogRecord?.description ?? null,
descriptionLegal: catalogRecord?.descriptionLegal ?? null,
illustrations: catalogRecord?.illustrations ?? null,
rawCatalog: catalogRecord?.rawCatalog ?? null
};
});
}
function mergeGvlVendorDetailIds(...sources) {
const ids = [];
const seen = new Set();
sources.forEach((source) => {
if (!Array.isArray(source)) {
return;
}
source.forEach((value) => {
const id = Number(value);
if (!Number.isFinite(id) || seen.has(id)) {
return;
}
seen.add(id);
ids.push(id);
});
});
return ids;
}
function parseGvlVendorDetailId(value) {
const vendorId = Number(value);