/** * Lädt die Datenschutzerklärung aus der mitgelieferten Datei. * * ## Warum genau wie die Lizenztexte * * Die Anwendung liefert bereits zwei lange Rechtstexte mit und zeigt sie an — * den EUPL-Volltext und die Übersicht der Fremdkomponenten. Der Weg dorthin * ist erprobt und wird gegen das **gepackte** Paket geprüft * (`e2e/gepackt.spec.ts`, „lädt die Lizenztexte aus resources/“): Datei über * `extraResources` ins Paket, im Hauptprozess über `process.resourcesPath` * gelesen, im Quellbaum aufwärts gesucht. Diese Datei macht dasselbe und * erfindet nichts daneben. * * ## Was bei fehlender Datei geschieht * * Die Anwendung läuft weiter und sagt, was fehlt. Ein leerer Bereich ohne * Begründung wäre die schlechteste Auskunft — der Nutzende hielte die * Erklärung für nicht vorhanden statt für nicht ladbar, und ausgerechnet * beim Datenschutz ist das der falsche Eindruck. * * ## Die Prüfsumme wird hier **nicht** geprüft * * Sie steht in der Datei und wird von `tests/datenschutz.test.ts` gegen * `docs/datenschutz.md` gerechnet — also beim Bauen, nicht beim Starten. Zur * Laufzeit liegt die Quelldatei gar nicht vor: `docs/` wird nicht * mitgeliefert. Eine Prüfung, die im Paket nie etwas zu vergleichen hat, * wäre eine Zusage ohne Deckung. */ import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { app } from 'electron'; import type { Datenschutzangaben, Datenschutzblock, Datenschutzerklaerung, } from '../shared/datenschutz'; import { sucheAufwaerts } from './katalog'; const DATEI = 'datenschutz.json'; const RELATIV = join('content', DATEI); /** * Obergrenze beim Lesen. * * Nachgemessen ist die Datei 80.304 Byte groß. Eine Grenze bei zwei Megabyte * lässt jedes denkbare Wachstum zu und fängt zugleich den Fall ab, dass * jemand versehentlich etwas anderes unter diesen Namen legt. */ const HOECHSTGROESSE = 2 * 1024 * 1024; let speicher: Datenschutzangaben | null = null; function fehlerText(fehler: unknown): string { return fehler instanceof Error ? fehler.message : String(fehler); } function pfadSuchen(): string | null { if (app.isPackaged) { const pfad = join(process.resourcesPath, DATEI); return existsSync(pfad) ? pfad : null; } for (const start of [app.getAppPath(), __dirname, process.cwd()]) { const treffer = sucheAufwaerts(start, RELATIV); if (treffer !== null) { return treffer; } } return null; } /** Prüft die Form so weit, dass die Oberfläche sich darauf verlassen kann. */ function istBlock(wert: unknown): wert is Datenschutzblock { if (typeof wert !== 'object' || wert === null) { return false; } const art = (wert as { art?: unknown }).art; return art === 'ueberschrift' || art === 'absatz' || art === 'liste' || art === 'code'; } /** * Baut die Angaben aus dem Dateiinhalt. * * Getrennt von {@link datenschutzLesen}, damit Tests sie ohne Electron und * ohne Dateisystem prüfen können – dieselbe Aufteilung wie bei * `lizenzenBilden`. */ export function datenschutzBilden(roh: string | null, warnung: string | null): Datenschutzangaben { if (roh === null) { return { erklaerung: null, warnung }; } let gelesen: unknown; try { gelesen = JSON.parse(roh); } catch (fehler) { return { erklaerung: null, warnung: `Die Datenschutzerklärung ist nicht lesbar: ${fehlerText(fehler)}`, }; } const daten = gelesen as Partial; const bloecke = Array.isArray(daten.bloecke) ? daten.bloecke.filter(istBlock) : []; if ( typeof daten.fassung !== 'string' || typeof daten.stand !== 'string' || typeof daten.quellpruefsumme !== 'string' || bloecke.length === 0 ) { return { erklaerung: null, warnung: 'Die mitgelieferte Datenschutzerklärung ist unvollständig. ' + 'Die veröffentlichte Fassung gilt unverändert.', }; } return { erklaerung: { quellpruefsumme: daten.quellpruefsumme, fassung: daten.fassung, stand: daten.stand, bloecke, }, warnung: null, }; } /** Liest die Erklärung, einmal je Programmlauf. */ export function datenschutzLesen(): Datenschutzangaben { if (speicher !== null) { return speicher; } const pfad = pfadSuchen(); if (pfad === null) { speicher = datenschutzBilden( null, `Die Datenschutzerklärung (${DATEI}) wurde nicht gefunden. ` + 'Bitte fordern Sie sie unter der Adresse an, die unter „Rückmeldung und ' + 'Kontakt“ steht.', ); return speicher; } let roh: string | null = null; let warnung: string | null = null; try { roh = readFileSync(pfad, 'utf8').slice(0, HOECHSTGROESSE); } catch (fehler) { warnung = `Die Datei ${DATEI} ist nicht lesbar: ${fehlerText(fehler)}`; } speicher = datenschutzBilden(roh, warnung); return speicher; } /** Leert den Zwischenspeicher – Gegenstück für Tests. */ export function datenschutzZuruecksetzen(): void { speicher = null; }