aboutsummaryrefslogtreecommitdiff
path: root/src/main/resources
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-15 10:51:11 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-15 10:51:11 +0200
commit2b1841e17533543d1cb15220ca86a2a37c853daf (patch)
tree01b940cb06c5abc36704d2d4853343ee7b38ce02 /src/main/resources
parent5700bbcb89186e432c5c7dd232b61c052de8e784 (diff)
Die Web-App rechnet im Browser statt auf dem Server
Bisher lief die Weboberfläche auf einem JDK-HttpServer: Uploads landeten als temporäre Dateien auf dem Server, die Synopse entstand dort. Das kostete Betrieb (systemd, Reverse Proxy, Rate-Limiting, Timeouts) und verlangte ein Datenschutzversprechen, das nur zusicherbar, nicht nachprüfbar war — gerade Entwurfstexte verließen den Rechner der Nutzer:innen. Neu übersetzt `./mvnw -Pwasm package` dieselbe Pipeline mit GraalVM Web Image (`native-image --tool:svm-wasm`) nach WebAssembly, PDFBox eingeschlossen. Ausgeliefert werden nur noch statische Dateien; gerechnet wird im Browser. Die erzeugte Synopse ist byteweise identisch mit der der Befehlszeile (SHA-256 verglichen für IfSG 48/27/21 und BayJG 151/3/54). Die Befehlszeile bleibt unberührt: `./mvnw package` erzeugt unverändert das JAR, alle Optionen und Meldungstexte sind gleich, das Wasm-Profil ist rein additiv und verlangt Oracle GraalVM 25.1+ (die CE hat kein Web Image). Portabilitätsschnitt (nützt beiden Fassungen): * `Quelle` (Name + Bytes) ersetzt `Path` in der Pipeline; nur die Befehlszeile kennt noch ein Dateisystem. Der Name trägt genau den bisherigen `getFileName()`-Text, damit Warnungen und Quellenzeile wortgleich bleiben. * `DateiTyp` erkennt PDF/XML/Klartext an den Signaturbytes. Tika entfällt — eine schwergewichtige Abhängigkeit samt ServiceLoader- und XML-Konfiguration weniger, was der Wasm-Übersetzung unmittelbar zugutekommt. Vier Eigenheiten von Web Image, die der Quelltext jeweils an Ort und Stelle vermerkt: * `java.util.zip.Inflater` ist nicht angebunden (GR-65205), ohne Inflate ist kein PDF lesbar. `InflaterErsatz` substituiert ihn durch jzlib. * Typisierte JS-Felder lassen sich nicht nach `byte[]` umsetzen („byteArrayHub is not defined“); der Dateiinhalt wandert als Base64. * JULs Standardformatter ruft `StackWalker`, den es dort nicht gibt. * Im Worker fehlt `document.currentScript`, worauf die Laufzeit das Wasm-Modul neben `worker.js` sucht; die VM wird deshalb mit ausdrücklichem Pfad ein zweites Mal gestartet. Die Reachability-Metadaten stammen aus einem Lauf des Tracing-Agents über die Pipeline; die PDFBox- und FontBox-Ressourcen sind als Globs ergänzt, sonst scheitern PDFs an „Could not find referenced cmap stream Identity-H“. Entfallen: WebMain, UploadHandler, StaticHandler, Multipart und die systemd-Unit. Die nginx-Vorlage liefert jetzt statische Dateien aus, und die Datenschutzseite sagt, was nun stimmt: Die Dateien verlassen den Rechner nicht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Change-Id: I38faf2ac0f764d601f080d4276babe4747773683
Diffstat (limited to 'src/main/resources')
-rw-r--r--src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json361
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/app.js90
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/datenschutz.html23
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/impressum.html4
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/index.html19
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/style.css26
-rw-r--r--src/main/resources/eu/mulk/aendggner/web/worker.js42
7 files changed, 545 insertions, 20 deletions
diff --git a/src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json b/src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json
new file mode 100644
index 0000000..4f85735
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json
@@ -0,0 +1,361 @@
+{
+ "reflection": [
+ {
+ "type": "ch.qos.logback.classic.Logger"
+ },
+ {
+ "type": "com.sun.crypto.provider.AESCipher$General",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "java.awt.image.BufferedImage",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "colorModel"
+ },
+ {
+ "name": "imageType"
+ },
+ {
+ "name": "raster"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getRGB",
+ "parameterTypes": [
+ "int",
+ "int",
+ "int",
+ "int",
+ "int[]",
+ "int",
+ "int"
+ ]
+ },
+ {
+ "name": "setRGB",
+ "parameterTypes": [
+ "int",
+ "int",
+ "int",
+ "int",
+ "int[]",
+ "int",
+ "int"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.awt.image.ColorModel",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "colorSpace"
+ },
+ {
+ "name": "colorSpaceType"
+ },
+ {
+ "name": "isAlphaPremultiplied"
+ },
+ {
+ "name": "is_sRGB"
+ },
+ {
+ "name": "nBits"
+ },
+ {
+ "name": "numComponents"
+ },
+ {
+ "name": "supportsAlpha"
+ },
+ {
+ "name": "transparency"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getRGBdefault",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "java.awt.image.Raster",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "dataBuffer"
+ },
+ {
+ "name": "height"
+ },
+ {
+ "name": "minX"
+ },
+ {
+ "name": "minY"
+ },
+ {
+ "name": "numBands"
+ },
+ {
+ "name": "numDataElements"
+ },
+ {
+ "name": "sampleModel"
+ },
+ {
+ "name": "sampleModelTranslateX"
+ },
+ {
+ "name": "sampleModelTranslateY"
+ },
+ {
+ "name": "width"
+ }
+ ]
+ },
+ {
+ "type": "java.awt.image.SampleModel",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "height"
+ },
+ {
+ "name": "width"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getPixels",
+ "parameterTypes": [
+ "int",
+ "int",
+ "int",
+ "int",
+ "int[]",
+ "java.awt.image.DataBuffer"
+ ]
+ },
+ {
+ "name": "setPixels",
+ "parameterTypes": [
+ "int",
+ "int",
+ "int",
+ "int",
+ "int[]",
+ "java.awt.image.DataBuffer"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.awt.image.SinglePixelPackedSampleModel",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "bitMasks"
+ },
+ {
+ "name": "bitOffsets"
+ },
+ {
+ "name": "bitSizes"
+ },
+ {
+ "name": "maxBitSize"
+ }
+ ]
+ },
+ {
+ "type": "java.lang.Boolean",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "getBoolean",
+ "parameterTypes": [
+ "java.lang.String"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "java.lang.System",
+ "jniAccessible": true,
+ "methods": [
+ {
+ "name": "load",
+ "parameterTypes": [
+ "java.lang.String"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "org.apache.commons.logging.LogFactory"
+ },
+ {
+ "type": "org.apache.commons.logging.impl.SLF4JLogFactory"
+ },
+ {
+ "type": "org.apache.commons.logging.impl.WeakHashtable",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.apache.log4j.LogManager"
+ },
+ {
+ "type": "org.apache.logging.log4j.Logger"
+ },
+ {
+ "type": "org.apache.pdfbox.pdmodel.encryption.StandardSecurityHandler",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "org.jboss.logmanager.LogManager"
+ },
+ {
+ "type": "org.slf4j.jul.JULServiceProvider"
+ },
+ {
+ "type": "sun.awt.image.IntegerComponentRaster",
+ "jniAccessible": true,
+ "fields": [
+ {
+ "name": "data"
+ },
+ {
+ "name": "dataOffsets"
+ },
+ {
+ "name": "pixelStride"
+ },
+ {
+ "name": "scanlineStride"
+ },
+ {
+ "name": "type"
+ }
+ ]
+ },
+ {
+ "type": "sun.misc.Unsafe",
+ "fields": [
+ {
+ "name": "theUnsafe"
+ }
+ ],
+ "methods": [
+ {
+ "name": "invokeCleaner",
+ "parameterTypes": [
+ "java.nio.ByteBuffer"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.security.provider.MD5",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.security.provider.NativePRNG",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": [
+ "java.security.SecureRandomParameters"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "sun.security.provider.SHA2$SHA256",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": []
+ }
+ ]
+ },
+ {
+ "type": "sun.text.resources.cldr.FormatData"
+ },
+ {
+ "type": "sun.util.resources.cldr.CalendarData"
+ }
+ ],
+ "resources": [
+ {
+ "glob": "META-INF/services/java.net.spi.URLStreamHandlerProvider"
+ },
+ {
+ "glob": "META-INF/services/java.time.zone.ZoneRulesProvider"
+ },
+ {
+ "glob": "META-INF/services/javax.xml.parsers.DocumentBuilderFactory"
+ },
+ {
+ "glob": "META-INF/services/org.apache.commons.logging.LogFactory"
+ },
+ {
+ "glob": "META-INF/services/org.jboss.logging.LoggerProvider"
+ },
+ {
+ "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider"
+ },
+ {
+ "glob": "commons-logging.properties"
+ },
+ {
+ "module": "java.base",
+ "glob": "jdk/internal/icu/impl/data/icudt76b/nfc.nrm"
+ },
+ {
+ "module": "java.base",
+ "glob": "jdk/internal/icu/impl/data/icudt76b/ubidi.icu"
+ },
+ {
+ "glob": "org/apache/fontbox/**"
+ },
+ {
+ "glob": "org/apache/pdfbox/resources/**"
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/main/resources/eu/mulk/aendggner/web/app.js b/src/main/resources/eu/mulk/aendggner/web/app.js
new file mode 100644
index 0000000..c59f6e3
--- /dev/null
+++ b/src/main/resources/eu/mulk/aendggner/web/app.js
@@ -0,0 +1,90 @@
+// Nimmt die Dateien aus dem Formular entgegen, reicht sie an den Worker und zeigt die Synopse.
+// Es gibt keinen Server: Alles läuft im Browser.
+
+const formular = document.querySelector("#synopse-formular");
+const knopf = formular.querySelector("button");
+const meldung = document.querySelector("#meldung");
+
+let worker = null;
+
+function zeige(text, art) {
+ meldung.textContent = text;
+ meldung.className = art ? "meldung " + art : "meldung";
+ meldung.hidden = !text;
+}
+
+/**
+ * Der Dateiinhalt geht als Base64 an das Wasm-Modul: Die Umsetzung typisierter Felder nach
+ * byte[] ist in Web Image derzeit defekt, Zeichenketten überqueren die Grenze zuverlässig.
+ */
+async function alsBase64(datei) {
+ const bytes = new Uint8Array(await datei.arrayBuffer());
+ let roh = "";
+ const block = 0x8000; // btoa verträgt keine beliebig langen Argumentlisten.
+ for (let i = 0; i < bytes.length; i += block) {
+ roh += String.fromCharCode.apply(null, bytes.subarray(i, i + block));
+ }
+ return { name: datei.name, base64: btoa(roh) };
+}
+
+formular.addEventListener("submit", async (ereignis) => {
+ ereignis.preventDefault();
+
+ const stammDatei = document.querySelector("#stamm").files[0];
+ const patchDateien = Array.from(document.querySelector("#aenderung").files);
+ if (!stammDatei || patchDateien.length === 0) {
+ zeige("Bitte ein Stammgesetz und mindestens ein Änderungsdokument wählen.", "fehler");
+ return;
+ }
+
+ knopf.disabled = true;
+ zeige("Lade das Rechenwerk (einmalig einige Megabyte) und werte aus …", "arbeit");
+
+ try {
+ const [stamm, patches] = await Promise.all([
+ alsBase64(stammDatei),
+ Promise.all(patchDateien.map(alsBase64)),
+ ]);
+
+ if (!worker) {
+ worker = new Worker("worker.js");
+ }
+
+ const ergebnis = await new Promise((aufloesen, ablehnen) => {
+ worker.onmessage = (nachricht) => aufloesen(nachricht.data);
+ worker.onerror = (fehler) => ablehnen(new Error(fehler.message || "Worker-Fehler"));
+ worker.postMessage({
+ stamm,
+ patches,
+ vollstaendig: document.querySelector("#vollstaendig").checked,
+ artikel: null,
+ });
+ });
+
+ if (ergebnis.fehler) {
+ zeige("Verarbeitung fehlgeschlagen: " + ergebnis.fehler, "fehler");
+ return;
+ }
+
+ const blob = new Blob([ergebnis.html], { type: "text/html;charset=utf-8" });
+ const adresse = URL.createObjectURL(blob);
+
+ // Kein window.open: Der Aufruf käme nach dem Warten auf den Worker und gälte dem Browser
+ // nicht mehr als Nutzerhandlung — er würde als Popup blockiert. Stattdessen ein Link, der
+ // sich öffnen und ebenso gut speichern lässt.
+ zeige(
+ `${ergebnis.angewandt} Befehle angewandt, ${ergebnis.manuell} manuell zu prüfen, ` +
+ `${ergebnis.normen} geänderte Normen. `,
+ "fertig",
+ );
+ const verweis = document.createElement("a");
+ verweis.href = adresse;
+ verweis.target = "_blank";
+ verweis.textContent = "Synopse öffnen";
+ meldung.append(verweis);
+ } catch (e) {
+ zeige("Verarbeitung fehlgeschlagen: " + (e && e.message ? e.message : e), "fehler");
+ } finally {
+ knopf.disabled = false;
+ }
+});
diff --git a/src/main/resources/eu/mulk/aendggner/web/datenschutz.html b/src/main/resources/eu/mulk/aendggner/web/datenschutz.html
index 65ec0c9..5b9bbac 100644
--- a/src/main/resources/eu/mulk/aendggner/web/datenschutz.html
+++ b/src/main/resources/eu/mulk/aendggner/web/datenschutz.html
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Datenschutz — ÄndGgner</title>
- <link rel="stylesheet" href="/style.css">
+ <link rel="stylesheet" href="style.css">
</head>
<body>
<main>
@@ -15,19 +15,22 @@
Zugriffslogs, Hosting-Anbieter).
</p>
- <h2>Hochgeladene Dateien</h2>
+ <h2>Ihre Dateien</h2>
<p>
- Stammgesetz und Änderungsgesetz(e) werden ausschließlich im Arbeitsspeicher bzw. in
- temporären Dateien verarbeitet, um die Synopse zu erzeugen. Nach Auslieferung der Antwort
- werden diese temporären Dateien sofort gelöscht. Es findet keine dauerhafte Speicherung des
- Inhalts statt.
+ Die gewählten Dateien werden <strong>nicht übertragen</strong>. Die gesamte Auswertung —
+ PDF-Textgewinnung, Anwendung der Änderungsbefehle, Erzeugung der Synopse — läuft als
+ WebAssembly-Programm in Ihrem Browser. Der Server sieht die Dateien nicht, kann sie nicht
+ sehen und speichert folglich auch nichts davon. Wer das prüfen möchte, öffne die
+ Netzwerkanzeige der Entwicklerwerkzeuge: Nach dem Laden der Seite und des Rechenwerks
+ entsteht kein weiterer Netzverkehr.
</p>
<h2>Zugriffsprotokolle</h2>
<p>
- Wie bei jedem Webserver werden beim Aufruf technische Zugriffsdaten (u. a. IP-Adresse,
- Zeitpunkt, aufgerufene Adresse) für einen begrenzten Zeitraum zur Absicherung des Betriebs
- protokolliert.
+ Beim Abruf der Seite selbst und der zugehörigen Dateien werden wie bei jedem Webserver
+ technische Zugriffsdaten (u. a. IP-Adresse, Zeitpunkt, aufgerufene Adresse) für einen
+ begrenzten Zeitraum zur Absicherung des Betriebs protokolliert. Diese Protokolle enthalten
+ keine Angaben über die von Ihnen ausgewerteten Dokumente.
</p>
<h2>Keine Cookies, keine Konten, kein Tracking</h2>
@@ -36,6 +39,6 @@
Analyse- oder Tracking-Dienste Dritter ein.
</p>
</main>
- <footer><a href="/">Zurück zum Formular</a></footer>
+ <footer><a href="index.html">Zurück zum Formular</a></footer>
</body>
</html>
diff --git a/src/main/resources/eu/mulk/aendggner/web/impressum.html b/src/main/resources/eu/mulk/aendggner/web/impressum.html
index 303db8e..525ef77 100644
--- a/src/main/resources/eu/mulk/aendggner/web/impressum.html
+++ b/src/main/resources/eu/mulk/aendggner/web/impressum.html
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Impressum — ÄndGgner</title>
- <link rel="stylesheet" href="/style.css">
+ <link rel="stylesheet" href="style.css">
</head>
<body>
<main>
@@ -21,6 +21,6 @@
[E-Mail-Adresse]
</address>
</main>
- <footer><a href="/">Zurück zum Formular</a></footer>
+ <footer><a href="index.html">Zurück zum Formular</a></footer>
</body>
</html>
diff --git a/src/main/resources/eu/mulk/aendggner/web/index.html b/src/main/resources/eu/mulk/aendggner/web/index.html
index 02f8f75..9f26889 100644
--- a/src/main/resources/eu/mulk/aendggner/web/index.html
+++ b/src/main/resources/eu/mulk/aendggner/web/index.html
@@ -4,7 +4,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ÄndGgner — Änderungsgesetze konsolidieren</title>
- <link rel="stylesheet" href="/style.css">
+ <link rel="stylesheet" href="style.css">
+ <script src="app.js" defer></script>
</head>
<body>
<header>
@@ -15,7 +16,7 @@
</header>
<main>
- <form method="post" action="/synopse" enctype="multipart/form-data">
+ <form id="synopse-formular">
<div class="field">
<label for="stamm">Stammgesetz</label>
<input type="file" id="stamm" name="stamm" required>
@@ -39,7 +40,7 @@
<div class="field checkbox">
<label>
- <input type="checkbox" name="vollstaendig">
+ <input type="checkbox" id="vollstaendig" name="vollstaendig">
Unveränderte Vorschriften mit in die Synopse aufnehmen
</label>
</div>
@@ -47,18 +48,20 @@
<button type="submit">Synopse erzeugen</button>
</form>
+ <p id="meldung" class="meldung" hidden></p>
+
<p class="disclaimer">
Die Synopse wird automatisch erstellt und ist <strong>ohne Gewähr</strong> für Vollständigkeit
und Richtigkeit; sie ersetzt keine Rechtsberatung. Uneindeutige Änderungsbefehle werden zur
- manuellen Prüfung markiert statt stillschweigend verworfen. Hochgeladene Dateien werden
- ausschließlich zur Erzeugung der Synopse verarbeitet und danach sofort wieder gelöscht — es
- findet keine dauerhafte Speicherung statt. Bitte nur Rechtstexte hochladen.
+ manuellen Prüfung markiert statt stillschweigend verworfen. Die gewählten Dateien
+ <strong>verlassen Ihren Rechner nicht</strong>: Die Auswertung läuft vollständig im Browser,
+ es wird nichts hochgeladen und nichts gespeichert.
</p>
</main>
<footer>
- <a href="/impressum">Impressum</a> ·
- <a href="/datenschutz">Datenschutz</a> ·
+ <a href="impressum.html">Impressum</a> ·
+ <a href="datenschutz.html">Datenschutz</a> ·
<a href="https://TODO-QUELLCODE-URL-EINTRAGEN">Quellcode (AGPLv3)</a>
</footer>
</body>
diff --git a/src/main/resources/eu/mulk/aendggner/web/style.css b/src/main/resources/eu/mulk/aendggner/web/style.css
index 6e2ec70..cdea20e 100644
--- a/src/main/resources/eu/mulk/aendggner/web/style.css
+++ b/src/main/resources/eu/mulk/aendggner/web/style.css
@@ -112,3 +112,29 @@ footer {
footer a {
color: var(--accent);
}
+
+.meldung {
+ margin-top: 1.25rem;
+ padding: 0.75rem 1rem;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ font-size: 0.9375rem;
+}
+
+.meldung.arbeit {
+ color: var(--muted);
+}
+
+.meldung.fertig {
+ border-color: var(--accent);
+}
+
+.meldung.fehler {
+ border-color: #a33;
+ color: #a33;
+}
+
+button[disabled] {
+ filter: grayscale(0.6);
+ cursor: progress;
+}
diff --git a/src/main/resources/eu/mulk/aendggner/web/worker.js b/src/main/resources/eu/mulk/aendggner/web/worker.js
new file mode 100644
index 0000000..12ba400
--- /dev/null
+++ b/src/main/resources/eu/mulk/aendggner/web/worker.js
@@ -0,0 +1,42 @@
+// Führt das Wasm-Modul in einem eigenen Thread aus: Web Image ist einthreadig, und ein Lauf über
+// mehrere Sekunden würde die Oberfläche sonst einfrieren.
+
+const bereit = new Promise((aufloesen) => {
+ self.aendggnerBereit = aufloesen;
+});
+
+// Die Laufzeit von aendggner.js sucht das Wasm-Modul neben ihrer eigenen Datei und findet die
+// über `document.currentScript` — den es im Worker nicht gibt, wo sie stattdessen auf
+// `location.href` (also diese Datei) zurückfällt und ins Leere greift. Der Selbststart beim
+// Import scheitert deshalb; danach starten wir die VM mit ausdrücklichem Pfad noch einmal.
+self.importScripts("aendggner.js");
+
+const gestartet = GraalVM.run([], Object.assign(new GraalVM.Config(), {
+ wasm_path: new URL("aendggner.js.wasm", self.location.href).href,
+}));
+
+self.onmessage = async (nachricht) => {
+ const { stamm, patches, vollstaendig, artikel } = nachricht.data;
+ try {
+ await gestartet;
+ await bereit;
+ const ergebnis = self.aendggnerSynopse({
+ stamm,
+ patches,
+ artikel: artikel ?? null,
+ vollstaendig: Boolean(vollstaendig),
+ });
+ if (ergebnis.fehler) {
+ self.postMessage({ fehler: String(ergebnis.fehler) });
+ } else {
+ self.postMessage({
+ html: String(ergebnis.html),
+ angewandt: Number(ergebnis.angewandt),
+ manuell: Number(ergebnis.manuell),
+ normen: Number(ergebnis.normen),
+ });
+ }
+ } catch (e) {
+ self.postMessage({ fehler: e && e.message ? e.message : String(e) });
+ }
+};