Open Source und Technik des Einheiten-Erfassungsbogens
Diese Seite beschreibt, wie der digitale Einheiten-Erfassungsbogen technisch gebaut ist: woraus die Anwendung besteht, wo Eingaben liegen, wann überhaupt etwas das Gerät verlässt und wie das Datenformat aussieht. Sie richtet sich an alle, die das Werkzeug in einer Behörde oder Hilfsorganisation prüfen wollen, bevor sie es einsetzen oder empfehlen.
Abgrenzung: Rechtlich verbindlich ist die Datenschutzerklärung — dort stehen Verantwortlicher, Rechtsgrundlagen, Betroffenenrechte und der Widerspruch gegen die Reichweitenmessung. Diese Seite ist die technische Ergänzung dazu und ersetzt sie nicht. Jede Aussage hier lässt sich im Quellcode nachlesen; die Fundstelle steht jeweils dabei.
Aufbau der Anwendung
Die Anwendung ist eine Web-App aus React und TypeScript, gebaut mit Vite. Es gibt keinen Anwendungsserver und keine Datenbank im Hintergrund — der gesamte Programmablauf, einschließlich PDF-Erzeugung und QR-Codierung, läuft im Browser beziehungsweise in der App auf dem Gerät.
| Fassung | Technik | Herkunft der Dateien |
|---|---|---|
| Web / installierte PWA | Vite-Build, Service Worker über vite-plugin-pwa |
erfassungsbogen.app (GitHub Pages) |
| Windows, macOS, Linux | Electron; dieselbe Web-App im Paket | lokal aus dem Installationspaket (file://) |
| iOS, Android | Capacitor; dieselbe Web-App im App-Paket | lokal aus dem App-Paket |
Der Electron-Hauptprozess lädt ausschließlich das mitgelieferte Bundle und
betreibt das Fenster ohne Node-Zugriff (contextIsolation,
sandbox, kein nodeIntegration); erteilt werden nur
die Berechtigungen für Kamera und Zwischenablage, alle anderen Anfragen lehnt
der Hauptprozess ab (electron/main.js). Externe Links öffnet er im
Systembrowser statt im App-Fenster.
Wo die eingegebenen Daten liegen
Alle Angaben eines Bogens werden im lokalen Speicher (localStorage)
des Browsers beziehungsweise der App gehalten. Eine IndexedDB wird nicht
verwendet, Cookies werden nicht gesetzt. Gespeichert sind unter anderem:
- der aktuelle Bogen-Entwurf (
eeb.entwurf.v1) - eigene Vorlagen (
eeb.vorlagen.v1) - Einsatz-Sammlungen des Meldekopf-Betriebs (
eeb.einsaetze.v1) - die Absenderkarte, sofern ausgefüllt (
eeb.absenderkarte.v1) - der private Geräteschlüssel der Signatur (
eeb.geraeteschluessel.v1) - der gewählte Anzeigemodus (
eeb.anzeigemodus.v1) - ein etwaiger Widerspruch gegen die Reichweitenmessung (
skipgc)
Diese Einträge werden nicht an einen Server übertragen. Löschen lassen sie sich über „Alle Daten löschen“ in der Fußzeile der App, über das Löschen der Websitedaten im Browser oder durch Deinstallation. „Datensicherung“ in derselben Fußzeile gibt sie als Datei aus und spielt sie wieder ein.
Wann Daten das Gerät verlassen
Bogeninhalte verlassen das Gerät nur, wenn sie selbst weitergegeben werden — als PDF, als Datei, als QR-Code oder als Link. Unabhängig davon gibt es aber Verbindungen, die die Anwendung von sich aus aufbaut. Sie sind hier vollständig aufgeführt:
Nicht von der Anwendung ausgelöst, aber der Vollständigkeit halber: Das
Betriebssystem sichert den Speicher der App in ein eingeschaltetes Systembackup
(iCloud, Google One) mit. Auf Android bleibt allowBackup dafür
bewusst gesetzt, damit ein Gerätewechsel oder ein Defekt die Arbeit nicht
vernichtet. Abschalten lässt sich das nur in den Systemeinstellungen des Geräts.
1. Auslieferung der Website
Die Web-Fassung wird über GitHub Pages ausgeliefert. Beim Aufruf verarbeitet GitHub die üblichen Verbindungsdaten (unter anderem die IP-Adresse) in Server-Logs. Die installierten Fassungen (Desktop, iOS, Android) laden ihre Dateien aus dem eigenen Paket und rufen dafür nichts ab. In der Web-Fassung werden einzelne Bestandteile — etwa ein angeklickter Beispielbogen — bei Bedarf von derselben Adresse nachgeladen.
2. Reichweitenmessung (GoatCounter)
Bei jedem Kaltstart sendet die Anwendung einen einzelnen Zählimpuls an
erfassungsbogen.goatcounter.com (src/app/statistik.ts).
Das gilt auf allen Plattformen, also auch in der Desktop- und in der Mobil-App.
Übertragen werden dabei nur drei Angaben:
- ein Zählpfad, der die Nutzungsart benennt (
/browser/ios,/pwa/android,/app/desktopund so weiter), - ein dazu passender Titel,
- im Browser die verweisende Seite (
document.referrer); in den installierten Fassungen bleibt dieses Feld leer.
Der Zählimpuls trägt bewusst weder location.search noch das
Adressfragment, weil die App Bogendaten im Fragment transportiert. Es wird kein
Fremdskript nachgeladen — die Zähl-URL wird selbst gebaut und per
sendBeacon, ersatzweise als Bildabruf, abgesetzt. In der lokalen
Entwicklung (localhost) unterbleibt der Impuls. Ohne Internetverbindung schlägt
er still fehl.
Widerspruch: in der App unter „Datenschutz“ die Option Nicht mitzählen
aktivieren; sie setzt den Eintrag skipgc im lokalen Speicher, und
der Zählimpuls unterbleibt ab dem nächsten Start. Welche Angaben GoatCounter
darüber hinaus aus der Verbindung ableitet und auf welcher Rechtsgrundlage das
geschieht, steht in der Datenschutzerklärung.
3. Prüfung auf neue Versionen
-
Android (per Datei verteilte APK): beim Start wird
api.github.comnach dem neuesten Release gefragt; erst nach einem Tippen auf „Aktualisieren“ wird die APK von GitHub geladen (src/app/aktualisierung.tsx). -
Desktop: der Electron-Updater prüft im installierten Zustand
gegen die GitHub-Releases und lädt ein Update im Hintergrund; installiert wird
es nach Rückfrage beziehungsweise beim Beenden (
electron/main.js). - Web und PWA: der Service Worker lädt eine neue Fassung im Hintergrund von derselben Adresse und meldet sie; neu geladen wird erst auf Klick, damit ein offener Bogen nicht verlorengeht.
- iOS: keine eigene Update-Prüfung in der App.
Bogeninhalte werden bei keiner dieser Verbindungen übertragen.
Offline-Betrieb
Die Web-Fassung registriert einen Service Worker, der die App-Shell —
JavaScript, CSS, HTML, Icons, Manifest und die eingebettete Schrift — in den
Vorrat legt. Nach dem ersten Aufruf startet die Seite damit ohne Netz. Der
Service Worker läuft im Modus „prompt“: eine neue Fassung wird erst nach
Bestätigung aktiviert, nie automatisch mitten in der Arbeit. In der Electron-
und der Capacitor-Fassung wird er bewusst nicht registriert; dort liegt die App
ohnehin lokal (Konfiguration in vite.config.ts).
Datenformat und Signatur
Ein Bogen wird für die Weitergabe binär codiert (src/codec.ts):
feste Feldreihenfolge, Varint-Längen, UTF-8-Strings, anschließend Deflate. Das
Ergebnis trägt die Kennung EEB2, in signierter Form
EEB2C. Für den Transport wird es als Text codiert — Base64url oder
das platzsparendere Base41 — und steckt im QR-Code, in der Datei und, bei einem
Link, hinter dem # der Adresse. Was hinter dem # steht,
sendet ein Browser nicht an den Server.
Jeder weitergegebene Bogen wird mit einem Ed25519-Schlüssel des ausstellenden
Geräts signiert; wird ein bereits signierter Bogen unverändert weitergereicht,
hängt die App eine weitere Stufe an die Kette an. Verwendet wird
@noble/ed25519 statt der Ed25519-Funktion der WebCrypto-API, weil
diese in älteren WebViews und in Electron nicht überall vorhanden ist. Der
private Schlüssel wird auf dem Gerät erzeugt und erscheint nie in QR-Code, Link
oder Datei (src/app/geraete-schluessel.ts, src/signatur.ts).
Das Vertrauensmodell ist bewusst schlicht: Die Prüfung belegt, dass der Bogen seit der Signatur unverändert ist und von diesem Schlüssel stammt. Sie belegt nicht, welche Person oder Dienststelle hinter dem Schlüssel steht — es gibt keine Zertifikatsstelle. Eine fehlgeschlagene Prüfung blockiert das Einlesen nicht; sie erscheint als Anzeigestatus.
Content-Security-Policy
Der Build setzt eine Content-Security-Policy als <meta> in den
Kopf der Seite (Plugin in vite.config.ts). Sie begrenzt Netzzugriffe
auf die eigene Herkunft und genau einen fremden Host, den Zähl-Endpunkt:
default-src 'self' file:
base-uri 'none'
object-src 'none'
form-action 'none'
script-src 'self' file: 'unsafe-inline'
style-src 'self' file: 'unsafe-inline'
img-src 'self' file: data: blob: https://erfassungsbogen.goatcounter.com
font-src 'self' file: data:
connect-src 'self' file: https://erfassungsbogen.goatcounter.com
'unsafe-inline' steht dort, weil Start-Gerüst, Design-Token und die
eingebettete Schrift inline im Dokument liegen. Der Vite-Entwicklungsserver
bekommt die Policy nicht — er braucht Verbindungen, die sie blockieren würde.
Der praktische Gewinn liegt in default-src 'self',
object-src 'none', base-uri 'none' und dem auf einen
Host begrenzten connect-src: Eine Ausleitung von Daten an eine
andere Adresse ist damit unterbunden.
Quellcode, Lizenz und Releases
- Quellcode: github.com/wattnpapa/erfassungsbogen
- Lizenz: EUPL-1.2
- Fertige Fassungen für Windows, macOS, Linux und Android: Releases
- Blanko-Bogen zum Ausdrucken: PDF-Vorlage (siehe Aufbau des Bogens)
Der Quellcode ist vollständig einsehbar; wer die Aussagen dieser Seite prüfen will, findet die genannten Dateien dort unter denselben Pfaden. Fehler und Verbesserungsvorschläge nimmt das GitHub-Projekt entgegen.
Projekt und Autor
Der Einheiten-Erfassungsbogen ist ein privates, nicht gewerbliches Projekt von Johannes Rudolph, entstanden aus der ehrenamtlichen Arbeit im Technischen Hilfswerk. Es besteht keine Verbindung zu einer Behörde oder Hilfsorganisation; genannte Organisationen und ihre Bezeichnungen dienen allein der fachlichen Zuordnung der Vorlagen.
Kontakt für Fragen, Fehler und Wünsche: johannes.rudolph@thw-oldenburg.de. Anschrift und Anbieterkennzeichnung stehen im Impressum.