Files
Webklar-Kundenbereich/docs/superpowers/specs/2026-10-01-website-analyse-design.md
2026-10-01 09:18:23 +00:00

148 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Website-Analyse (Funktion im Kundenportal) – Design
Stand: 2026-10-01 · Status: vom Nutzer im Chat freigegeben (Abschnitte 1–3), Spec zur Durchsicht
Teilprojekt **3** von 3 (1 Projektarten – live 30.09.; 2 Preview-Runner – live 01.10.).
Regel des Nutzers: bauen → live → Nutzer testet live.
Recherchen: `/root/reports/analyse-funktion-2026-09-30/` (recht-tracking-deutschland.md,
technik-analyse-heatmap.md). Keine Rechtsberatung – AVV/Texte vor Livegang anwaltlich prüfen lassen.
## Ziel
Kunden mit Wartungs-Abo schalten im Portal (Funktionen) eine Website-Analyse ein. Auf ihrer Website
erscheint ein Cookie-Banner (Annehmen/Ablehnen). Ohne Einwilligung werden nur serverseitig Seitenaufrufe,
Herkunft, Land und Gerätetyp gezählt; nach „Annehmen“ zusätzlich Besucher, Verweildauer, Scrolltiefe,
Klick-Heatmap und Kontakt-Klicks. Alles läuft auf dem eigenen Server, ohne Google o. ä., und wird
**ohne Änderung am Kunden-Repo** beim Veröffentlichen durch den Preview-Runner eingebunden.
## Entscheidungen (vom Nutzer)
| # | Entscheidung |
|---|---|
| W1 | Nur mit **Wartungs-Abo** (`productKey === 'wartung'`, Status in `ACTIVE_STATUSES`). Ohne Abo: Kachel „Im Wartungs-Abo enthalten“ mit Link zum Abo. Hosting-Abo (`hosting`) ausgeschlossen (bestätigt 01.10.). |
| W2 | **Kunde schaltet selbst ein** (Portal, AVV-Haken); WOMS kann ebenfalls schalten (Ausnahmen, protokolliert). |
| W3 | Umfang V1 wie unten (Abschnitt 2); nicht: Session-Aufzeichnung, Formularinhalte, Ladezeiten/JS-Fehler, Trichter/UTM. |
| W4 | Banner: **schlichte Leiste unten**, „Ablehnen“ und „Annehmen“ gleichwertig, Link „Datenschutz“, eine wählbare Akzentfarbe; Handy: kompakte Karte. Footer-Link „Cookie-Einstellungen“. Entscheidung gilt 6 Monate. Deutsch. |
| W5 | Datenschutz: **automatische Zusatzseite** `/datenschutz-analyse.html` + **WOMS-Ticket** „Datenschutzerklärung von <Projekt> um Hinweis auf Analyse ergänzen“. |
| W6 | **Eigene Lösung** (Sammeldienst + Skript), kein Umami/Matomo/Google. |
| W7 | Nur auf **öffentlichen** Websites (`isPublic`), nicht auf login-geschützten Previews. |
| W8 | Abo endet oder Analyse aus ⇒ Banner/Skript beim nächsten Veröffentlichen entfernt, Daten nach **60 Tagen** gelöscht. |
| W9 | Aufbewahrung: Rohdaten **30 Tage**, Tageswerte bis Abo-Ende + 60 Tage. |
## 1. Bausteine
1. **Sammeldienst** `webklar-analytics` – Code im Portal-Repo unter `analytics/` (eigenes `package.json`,
keine Laufzeit-Abhängigkeiten), Container `node:24-alpine` (eingebautes `node:sqlite`), Code
schreibgeschützt eingehängt wie der Runner, Daten `/srv/webklar-analytics/` (SQLite). `deploy.sh`
startet ihn neu, wenn sich `analytics/` geändert hat. Erreichbar über Traefik unter
`https://project.webklar.com/a/…` (eigener Router mit höherer Priorität auf `PathPrefix(/a/)`).
Länder über **DB-IP Lite Country** (CSV/MMDB, CC BY 4.0, Namensnennung im Portal), monatlich
aktualisiert per Skript.
2. **Einbinden über den Runner** (Teilprojekt 2): Ist die Analyse für das Projekt aktiv **und** die Seite
öffentlich, setzt der Runner beim Veröffentlichen (static und build) vor `</head>` jeder HTML-Datei ein:
`<script src="https://project.webklar.com/a/w.js" data-site="<siteKey>" data-color="<hex>" defer></script>`
und erzeugt `/datenschutz-analyse.html` aus einer Vorlage (Firmenname, Anschrift, E-Mail des Kunden aus
Appwrite). Ist sie aus, unterbleibt beides. Das Portal übergibt dafür im Build-Auftrag ein Feld
`analytics: { siteKey, color, privacy: { company, address, email } } | null` (validiert im Runner:
siteKey `^[a-z0-9]{16,40}$`, color `^#[0-9a-f]{6}$`, Texte HTML-maskiert, Länge begrenzt).
Einschalten/Ausschalten löst einen Neubau aus (Portal `runDeploy`).
3. **Skript** `w.js` (≤ 6 KB minifiziert, ohne Abhängigkeiten, vom Sammeldienst ausgeliefert, Cache 1 h):
Seitenaufruf ohne Einwilligung; Banner; nach „Annehmen“ Zusatzereignisse (Abschnitt 2); Footer-Link
„Cookie-Einstellungen“ (fest unten rechts, klein, wenn kein Element `[data-webklar-consent]` auf der
Seite existiert – sonst wird dieses Element zum Auslöser); Overlay-Modus für die Heatmap
(Parameter `__wk_heat=<token>`, lädt `h.js` nach).
4. **Portal:** Funktionen-Kachel „Analyse“ (Abo-Prüfung, Projektwahl, Erklärung, Akzentfarbe mit
Banner-Vorschau, AVV-Haken, Ein/Aus), Ansicht „Analyse“ (Abschnitt 3), Admin-API für WOMS.
5. **WOMS:** Zeile „Website-Analyse an/aus“ auf der Projektkarte; Services zeigt die Funktion als aktiv.
6. **Rechtliches:** AVV Website-Analyse, Leistungsbeschreibung „Website-Analyse“, neue Fassung der
Abo-Bedingungen, Vorlage Zusatzseite, Absatz in WEBklar-Datenschutzerklärung (Auftragsverarbeitung).
## 2. Daten und Datenschutz
**Ereignisse** (alle per `navigator.sendBeacon` als `text/plain` an `POST /a/e`, Body JSON ≤ 4 KB):
| Typ | Wann | Felder |
|---|---|---|
| `pv` Seitenaufruf | immer | site, path (ohne Query/Hash, ≤ 300), ref (nur Host, nur wenn ≠ eigene Domain), vp (`m` < 768 ≤ `t` < 1024 ≤ `d`) |
| `bn` Banner | Anzeige / Klick | site, a: `shown`\|`accept`\|`reject` – **keine** weiteren Felder |
| `en` Verweildauer | nur nach Annehmen, beim Verlassen | site, path, vp, sec (0–3600), scroll (0–100) |
| `ck` Klick | nur nach Annehmen | site, path, vp, x, y (0–1, relativ zur Dokumentgröße), sel (CSS-Pfad ≤ 200) |
| `ct` Kontakt-Klick | nur nach Annehmen | site, path, kind: `tel`\|`mail`\|`wa`\|`out`, host (nur bei `out`) |
**Server:** IP ⇒ Land (DB-IP), danach verworfen; User-Agent ⇒ Gerätetyp + Bot-Erkennung, danach verworfen.
Besucher-Kennung nur für `en`/`ck`/`ct`: `HMAC-SHA256(tagesSalz, siteKey|ip|ua)` gekürzt auf 16 Hex,
Salz zufällig, nur im RAM, wird um 00:00 Europe/Berlin neu erzeugt (nach Neustart neues Salz). Keine
Cookies, keine `Set-Cookie`-Header. Gespeichert auf dem Gerät nur die Banner-Wahl
(`localStorage['wk-consent'] = {v:1, a:'accept'|'reject', t}`), gültig 6 Monate.
GPC (`navigator.globalPrivacyControl`) oder DNT ⇒ wie „Ablehnen“, Banner nicht anzeigen.
**Annahme-Prüfung:** Unbekannter siteKey, Origin/Referer-Host nicht in den Domains der Website
(Subdomain `*.project.webklar.com` + `liveDomain`) ⇒ 204 ohne Speichern. Bots ⇒ verworfen. Mengenlimit
120 Ereignisse/Minute je (site, IP) im RAM. CORS nur für die eigenen Domains der Website.
**Speicherung (SQLite):** Tabellen `events_raw` (30 Tage), `daily` (site, day, dim, key, count),
`clicks` (30 Tage), `sites` (siteKey, projectId, domains, active, offSince). Nächtlicher Job 02:30:
Verdichten, Rohdaten > 30 Tage löschen, Websites mit `offSince` > 60 Tage komplett löschen.
Traefik: Zugriffsprotokoll für `/a/` ohne Client-IP (Feld filtern oder Router ohne Access-Log) – im
Livegang prüfen.
**Einwilligungsnachweis:** technischer Nachweis (ohne Annehmen sendet das Skript nachweislich nichts
Weiteres); so in AVV und Leistungsbeschreibung beschrieben; Prüfpunkt für den Anwalt.
## 3. Portal, WOMS, Abläufe
**Funktionsdaten:** vorhandenes Collection `portalFeatures` mit `featureKey: 'analyse'` je
(Kunde, Projekt): `state` (`off`|`active`|`disabled`), `siteKey`, neu `accentColor`, `avvVersion`,
`avvAcceptedAt`, `avvAcceptedBy` (`customer`|Mitarbeitername), `activatedAt`, `offSince`, `privacyTicketId`.
Fehlende Attribute per Schema-Skript (Muster `scripts/preview-runner-setup.mjs`, limit 200).
**Einschalten (Kunde):** `POST /api/funktionen/analyse/activate { projectId, accentColor, avvVersion,
consent:true }` ⇒ Prüfungen (Eigentum, Wartungs-Abo aktiv, Projekt öffentlich mit Subdomain/Live-Domain,
AVV-Fassung aktuell) ⇒ Dokument `active`, Sammeldienst-Website registrieren (`PUT /a/admin/sites/:siteKey`
mit Token), Neubau anstoßen, Ticket „Datenschutzerklärung … ergänzen“ (Muster `createFeatureTicket`),
Mail an Kunden (neue Vorlage K-Nummer laut Bestand) „Deine Website-Analyse ist aktiv“.
**Ausschalten:** `POST …/deactivate { projectId }` ⇒ `off`, `offSince`, Neubau, Sammeldienst `active=false`.
**Abo-Ende:** täglicher Job: aktive Analyse ohne aktives Wartungs-Abo ⇒ wie Ausschalten (Mail an Kunden).
**WOMS:** `POST /api/admin/analyse/:projectId { on, avvConfirmedVerbally }` (requireAppwriteStaff);
beim Einschalten durch Mitarbeiter Pflicht-Bestätigung „Kunde hat AVV bestätigt“, protokolliert.
**Auswertung:** `GET /api/funktionen/analyse/stats?projectId&range=7|30|90` (Portal holt beim Sammeldienst
`GET /a/admin/stats/:siteKey`, Token): Kennzahlen (Aufrufe, Besucher*, Kontakt-Klicks*, Zustimmungsquote),
Tagesverlauf, Top-Seiten, Herkunft, Länder, Geräte, Kontakt-Klicks nach Art, Banner-Zahlen
(*nur aus Einwilligungen – Hinweistext). Heatmap: `POST …/heatmap-link { projectId, path, vp }` ⇒ signierter
Link `https://<domain><path>?__wk_heat=<token>` (HMAC, 30 min); `h.js` lädt Punkte von
`GET /a/heat?token=…` und zeichnet sie (Canvas, eigene kleine Implementierung) über die Seite.
Ansicht handytauglich; Diagramme als leichtes SVG (keine Chart-Bibliothek nötig).
**Rechtstexte** (Entwürfe im Repo unter `docs/rechtliches/`, Upload in WOMS „Rechtliches“ durch den Nutzer):
AVV Website-Analyse (Art. 28 DSGVO; Gegenstand, Daten, Betroffene, TOMs, Unterauftragsverarbeiter
Hetzner, Löschung, Kontrollrechte), Leistungsbeschreibung (Umfang, Pflichten des Kunden: Banner nicht
umgehen, keine weiteren Tracker, Datenschutzerklärung pflegen), Abo-Bedingungen neue Fassung (Analyse als
Abo-Leistung), Vorlage `/datenschutz-analyse.html` (Art. 13: Zwecke, Rechtsgrundlagen § 25 Abs. 1 TDDDG /
Art. 6 Abs. 1 lit. a und f DSGVO, Empfänger, Speicherdauer, Widerruf, Rechte), Absatz WEBklar-
Datenschutzerklärung.
## Fehlerfälle
Sammeldienst nicht erreichbar ⇒ Website läuft normal (Skript fängt Fehler, Beacon ohne Wirkung);
Portal-Statistik zeigt „Auswertung gerade nicht erreichbar“. Länder-DB fehlt ⇒ Land „unbekannt“.
Neubau nach Ein/Aus scheitert ⇒ Zustand bleibt gespeichert, Hinweis in der Kachel, nächster Neubau holt nach.
## Tests
Sammeldienst (kein IP/UA gespeichert, Bots, Domain-Prüfung, Limit, Salz-Rotation, Verdichtung, Löschung),
Skript (jsdom/Playwright: ohne Annehmen nur `pv`+`bn`; GPC; Banner gleichwertig; Widerruf; Overlay),
Runner (Einsetzen/Entfernen, nur öffentlich, Maskierung, Zusatzseite), Portal (Abo-Pflicht, AVV-Pflicht,
Ticket, Abo-Ende-Job, Statistik-Proxy, Heatmap-Token), Playwright für Kachel, Auswertung, Banner (390 px).
## Livegang (je Schritt mit OK des Nutzers)
L1 Sammeldienst-Container + Datenverzeichnis + DB-IP + Traefik-Route `/a/` (ohne IP-Log) + Schema-Skript ·
L2 Portal/Runner pushen (Kachel noch „Kommt bald“ per Schalter `ANALYSE_ENABLED=off`) · L3 Probe auf einer
Wegwerf-Website (öffentlich) inkl. Banner, Heatmap, Löschung · L4 WOMS pushen, Rechtstexte durch den Nutzer
in „Rechtliches“, Schalter an · L5 Live-Test des Nutzers an einer echten Kunden-Website mit Wartungs-Abo.
## Nicht Teil dieser Spec
Session-Aufzeichnung, Formularanalyse, Ladezeiten/JS-Fehler, UTM/Trichter, Einwilligung für fremde
Inhalte (z. B. Google-Karte) über denselben Banner, Websites außerhalb des eigenen Hostings, Export.