From 2498b0eb1f312ab17abea730f20c0303a1331b88 Mon Sep 17 00:00:00 2001 From: Benjamin Boenisch Date: Thu, 12 Feb 2026 00:49:21 +0100 Subject: [PATCH] Update CLAUDE.md, MkDocs, nginx docs proxy, .claude/rules - CLAUDE.md: Comprehensive documentation for core infrastructure - docs-src: Cleaned 316MB junk, kept only markdown docs - mkdocs.yml: Updated nav for core-only content - nginx: Docs proxy targets split (3002->lehrer, 3007->compliance) - docker-compose: Fixed docs port mapping (8009:80), added INSTALL_LOCK - .claude/rules: testing, documentation, open-source-policy, night-scheduler Co-Authored-By: Claude Opus 4.6 --- .claude/CLAUDE.md | 293 +++- .claude/rules/documentation.md | 91 ++ .claude/rules/night-scheduler.md | 297 ++++ .claude/rules/open-source-policy.md | 99 ++ .claude/rules/testing.md | 202 +++ docker-compose.yml | 5 +- .../migrations/001_rbac_schema.sql | 321 ---- .../migrations/002_dsgvo_schema.sql | 215 --- .../migrations/003_ucca_schema.sql | 96 -- .../migrations/004_ucca_escalations.sql | 168 -- .../migrations/005_roadmap_schema.sql | 193 --- .../migrations/006_workshop_schema.sql | 207 --- .../migrations/007_portfolio_schema.sql | 267 ---- .../app/models/__init__.py | 11 - .../app/models/generation_job.py | 101 -- .../backend/alerts_agent/models/__init__.py | 12 - .../backend/alerts_agent/models/alert_item.py | 174 -- .../alerts_agent/models/relevance_profile.py | 288 ---- .../backend/llm_gateway/models/__init__.py | 31 - docs-src/backend/llm_gateway/models/chat.py | 135 -- .../migrations/add_abitur_docs_tables.sql | 155 -- .../migrations/add_agent_core_tables.sql | 293 ---- .../migrations/add_compliance_tables.sql | 807 ---------- .../backend/migrations/add_game_tables.sql | 241 --- .../add_recording_transcription_tables.sql | 409 ----- .../backend/migrations/add_unit_tables.sql | 410 ----- .../hardware/mac-mini/init-db.sql | 204 --- docs-src/breakpilot-drive/Build/.gitkeep | 2 - docs-src/consent-sdk/dist/angular/index.d.mts | 390 ----- docs-src/consent-sdk/dist/angular/index.d.ts | 390 ----- docs-src/consent-sdk/dist/angular/index.js | 1330 --------------- .../consent-sdk/dist/angular/index.js.map | 1 - docs-src/consent-sdk/dist/angular/index.mjs | 1297 --------------- .../consent-sdk/dist/angular/index.mjs.map | 1 - docs-src/consent-sdk/dist/index.d.mts | 707 -------- docs-src/consent-sdk/dist/index.d.ts | 707 -------- docs-src/consent-sdk/dist/index.js | 1142 ------------- docs-src/consent-sdk/dist/index.js.map | 1 - docs-src/consent-sdk/dist/index.mjs | 1108 ------------- docs-src/consent-sdk/dist/index.mjs.map | 1 - docs-src/consent-sdk/dist/react/index.d.mts | 450 ------ docs-src/consent-sdk/dist/react/index.d.ts | 450 ------ docs-src/consent-sdk/dist/react/index.js | 1361 ---------------- docs-src/consent-sdk/dist/react/index.js.map | 1 - docs-src/consent-sdk/dist/react/index.mjs | 1337 ---------------- docs-src/consent-sdk/dist/react/index.mjs.map | 1 - docs-src/consent-sdk/dist/vue/index.d.mts | 483 ------ docs-src/consent-sdk/dist/vue/index.d.ts | 483 ------ docs-src/consent-sdk/dist/vue/index.js | 1423 ----------------- docs-src/consent-sdk/dist/vue/index.js.map | 1 - docs-src/consent-sdk/dist/vue/index.mjs | 1401 ---------------- docs-src/consent-sdk/dist/vue/index.mjs.map | 1 - mkdocs.yml | 45 +- nginx/conf.d/default.conf | 4 +- 54 files changed, 953 insertions(+), 19290 deletions(-) create mode 100644 .claude/rules/documentation.md create mode 100644 .claude/rules/night-scheduler.md create mode 100644 .claude/rules/open-source-policy.md create mode 100644 .claude/rules/testing.md delete mode 100644 docs-src/ai-compliance-sdk/migrations/001_rbac_schema.sql delete mode 100644 docs-src/ai-compliance-sdk/migrations/002_dsgvo_schema.sql delete mode 100644 docs-src/ai-compliance-sdk/migrations/003_ucca_schema.sql delete mode 100644 docs-src/ai-compliance-sdk/migrations/004_ucca_escalations.sql delete mode 100644 docs-src/ai-compliance-sdk/migrations/005_roadmap_schema.sql delete mode 100644 docs-src/ai-compliance-sdk/migrations/006_workshop_schema.sql delete mode 100644 docs-src/ai-compliance-sdk/migrations/007_portfolio_schema.sql delete mode 100644 docs-src/ai-content-generator/app/models/__init__.py delete mode 100644 docs-src/ai-content-generator/app/models/generation_job.py delete mode 100644 docs-src/backend/alerts_agent/models/__init__.py delete mode 100644 docs-src/backend/alerts_agent/models/alert_item.py delete mode 100644 docs-src/backend/alerts_agent/models/relevance_profile.py delete mode 100644 docs-src/backend/llm_gateway/models/__init__.py delete mode 100644 docs-src/backend/llm_gateway/models/chat.py delete mode 100644 docs-src/backend/migrations/add_abitur_docs_tables.sql delete mode 100644 docs-src/backend/migrations/add_agent_core_tables.sql delete mode 100644 docs-src/backend/migrations/add_compliance_tables.sql delete mode 100644 docs-src/backend/migrations/add_game_tables.sql delete mode 100644 docs-src/backend/migrations/add_recording_transcription_tables.sql delete mode 100644 docs-src/backend/migrations/add_unit_tables.sql delete mode 100644 docs-src/breakpilot-compliance-sdk/hardware/mac-mini/init-db.sql delete mode 100644 docs-src/breakpilot-drive/Build/.gitkeep delete mode 100644 docs-src/consent-sdk/dist/angular/index.d.mts delete mode 100644 docs-src/consent-sdk/dist/angular/index.d.ts delete mode 100644 docs-src/consent-sdk/dist/angular/index.js delete mode 100644 docs-src/consent-sdk/dist/angular/index.js.map delete mode 100644 docs-src/consent-sdk/dist/angular/index.mjs delete mode 100644 docs-src/consent-sdk/dist/angular/index.mjs.map delete mode 100644 docs-src/consent-sdk/dist/index.d.mts delete mode 100644 docs-src/consent-sdk/dist/index.d.ts delete mode 100644 docs-src/consent-sdk/dist/index.js delete mode 100644 docs-src/consent-sdk/dist/index.js.map delete mode 100644 docs-src/consent-sdk/dist/index.mjs delete mode 100644 docs-src/consent-sdk/dist/index.mjs.map delete mode 100644 docs-src/consent-sdk/dist/react/index.d.mts delete mode 100644 docs-src/consent-sdk/dist/react/index.d.ts delete mode 100644 docs-src/consent-sdk/dist/react/index.js delete mode 100644 docs-src/consent-sdk/dist/react/index.js.map delete mode 100644 docs-src/consent-sdk/dist/react/index.mjs delete mode 100644 docs-src/consent-sdk/dist/react/index.mjs.map delete mode 100644 docs-src/consent-sdk/dist/vue/index.d.mts delete mode 100644 docs-src/consent-sdk/dist/vue/index.d.ts delete mode 100644 docs-src/consent-sdk/dist/vue/index.js delete mode 100644 docs-src/consent-sdk/dist/vue/index.js.map delete mode 100644 docs-src/consent-sdk/dist/vue/index.mjs delete mode 100644 docs-src/consent-sdk/dist/vue/index.mjs.map diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 315f17e..480caa2 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,65 +1,272 @@ -# BreakPilot Core — Shared Infrastructure +# BreakPilot Core - Shared Infrastructure -## Entwicklungsumgebung +## Entwicklungsumgebung (WICHTIG - IMMER ZUERST LESEN) ### Zwei-Rechner-Setup -| Gerät | Rolle | -|-------|-------| -| **MacBook** | Client/Terminal | -| **Mac Mini** | Server/Docker/Git | + +| Geraet | Rolle | Aufgaben | +|--------|-------|----------| +| **MacBook** | Client | Claude Terminal, Browser (Frontend-Tests) | +| **Mac Mini** | Server | Docker, alle Services, Code-Ausfuehrung, Tests, Git | + +**WICHTIG:** Die Entwicklung findet vollstaendig auf dem **Mac Mini** statt! + +### SSH-Verbindung ```bash +ssh macmini +# Projektverzeichnis: +cd /Users/benjaminadmin/Projekte/breakpilot-core + +# Einzelbefehle (BEVORZUGT): ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && " ``` -## Projektübersicht +--- -**breakpilot-core** ist das Infrastruktur-Projekt der BreakPilot-Plattform. Es stellt alle gemeinsamen Services bereit, die von **breakpilot-lehrer** und **breakpilot-compliance** genutzt werden. +## Projekt-Kontext -### Enthaltene Services (~28 Container) +**breakpilot-core** ist das Infrastruktur-Fundament der BreakPilot-Plattform. Es stellt alle gemeinsamen Services bereit, die von **breakpilot-lehrer** und **breakpilot-compliance** genutzt werden. -| Service | Port | Beschreibung | -|---------|------|--------------| -| nginx | 80/443 | Reverse Proxy (SSL) | -| postgres | 5432 | PostGIS 16 (3 Schemas: core, lehrer, compliance) | -| valkey | 6379 | Session-Cache | -| vault | 8200 | Secrets Management | -| qdrant | 6333 | Vektordatenbank | -| minio | 9000 | S3 Storage | -| backend-core | 8000 | Shared APIs (Auth, RBAC, Notifications) | -| rag-service | 8097 | RAG: Dokumente, Suche, Embeddings | -| embedding-service | 8087 | Text-Embeddings | -| consent-service | 8081 | Consent-Management | -| health-aggregator | 8099 | Health-Check aller Services | -| gitea | 3003 | Git-Server | -| woodpecker | 8090 | CI/CD | -| camunda | 8089 | BPMN | -| synapse | 8008 | Matrix Chat | -| jitsi | 8443 | Video | -| mailpit | 8025 | E-Mail (Dev) | +### 3-Projekt-Architektur -### Docker-Netzwerk -Alle 3 Projekte teilen sich das `breakpilot-network`: +``` +breakpilot-core (dieses Repo — MUSS ZUERST starten) + ├── breakpilot-lehrer (haengt von Core ab) + └── breakpilot-compliance (haengt von Core ab) +``` + +Alle 3 Projekte teilen sich ein Docker-Netzwerk: ```yaml networks: breakpilot-network: driver: bridge - name: breakpilot-network + name: breakpilot-network # Fixer Name, kein Auto-Prefix! ``` -### Start-Reihenfolge +--- + +## Haupt-URLs (via Nginx Reverse Proxy) + +### Core-eigene Services + +| URL | Service | Beschreibung | +|-----|---------|--------------| +| https://macmini:8000/ | backend-core | Shared APIs (Auth, RBAC, Notifications) | +| https://macmini:8097/ | rag-service | Semantische Suche, Dokument-Upload | +| https://macmini:8443/ | Jitsi Meet | Videokonferenzen | +| http://macmini:8099/ | health-aggregator | Health-Check aller Services | + +### Interne Dienste (kein HTTPS) + +| URL | Service | Beschreibung | +|-----|---------|--------------| +| http://macmini:3003/ | Gitea | Git-Server (User: pilotadmin) | +| http://macmini:8090/ | Woodpecker CI | CI/CD Server | +| http://macmini:8200/ | Vault | Secrets Management | +| http://macmini:8025/ | Mailpit | E-Mail (Dev) | +| http://macmini:9001/ | MinIO Console | S3 Storage UI | +| http://macmini:8096/ | Night Scheduler | Auto-Shutdown/Startup | +| http://macmini:8089/ | Camunda | BPMN Engine | + +### Nginx-Proxy fuer Lehrer/Compliance (durchgereicht) + +| URL | Projekt | Service | +|-----|---------|---------| +| https://macmini/ | Lehrer | Studio v2 | +| https://macmini:3000/ | Lehrer | Website | +| https://macmini:3002/ | Lehrer | Admin Lehrer | +| https://macmini:3007/ | Compliance | Admin Compliance | +| https://macmini:8001/ | Lehrer | Backend Lehrer | +| https://macmini:8002/ | Compliance | Backend Compliance | +| https://macmini:8086/ | Lehrer | Klausur-Service | +| https://macmini:8093/ | Compliance | AI Compliance SDK | + +--- + +## Services (~28 Container) + +### Infrastruktur + +| Service | Tech | Port | Container | +|---------|------|------|-----------| +| nginx | Nginx | 80/443 + viele | bp-core-nginx | +| postgres | PostGIS 16 | 5432 | bp-core-postgres | +| valkey | Valkey 8 | 6379 | bp-core-valkey | +| vault | HashiCorp Vault | 8200 | bp-core-vault | +| vault-agent | Vault | - | bp-core-vault-agent | +| qdrant | Qdrant | 6333/6334 | bp-core-qdrant | +| minio | MinIO | 9000/9001 | bp-core-minio | +| mailpit | Mailpit | 8025/1025 | bp-core-mailpit | + +### Shared Backend-Services + +| Service | Tech | Port | Container | +|---------|------|------|-----------| +| backend-core | Python/FastAPI | 8000 | bp-core-backend | +| rag-service | Python/FastAPI | 8097 | bp-core-rag-service | +| embedding-service | Python/FastAPI | 8087 | bp-core-embedding-service | +| consent-service | Go/Gin | 8081 | bp-core-consent-service | +| billing-service | Go | 8083 | bp-core-billing-service | +| health-aggregator | Python/FastAPI | 8099 | bp-core-health | +| night-scheduler | Python/FastAPI | 8096 | bp-core-night-scheduler | + +### Kommunikation + +| Service | Tech | Container | +|---------|------|-----------| +| jitsi-web | Jitsi | bp-core-jitsi-web | +| jitsi-xmpp | Prosody | bp-core-jitsi-xmpp | +| jitsi-jicofo | Jicofo | bp-core-jitsi-jicofo | +| jitsi-jvb | JVB | bp-core-jitsi-jvb | +| synapse + synapse-db | Matrix | bp-core-synapse (Profil: chat) | + +### DevOps + +| Service | Tech | Port | Container | +|---------|------|------|-----------| +| gitea | Gitea | 3003 | bp-core-gitea | +| gitea-runner | Gitea Actions | - | bp-core-gitea-runner | +| woodpecker-server | Woodpecker | 8090 | bp-core-woodpecker-server | +| woodpecker-agent | Woodpecker | - | bp-core-woodpecker-agent | +| camunda | Camunda | 8089 | bp-core-camunda (Profil: bpmn) | + +### Profile (nur bei Bedarf starten) + +| Profil | Services | Start mit | +|--------|----------|-----------| +| chat | synapse, synapse-db | `--profile chat` | +| erp | erpnext-* (7 Container) | `--profile erp` | +| bpmn | camunda | `--profile bpmn` | +| docs | docs (MkDocs) | `--profile docs` | +| backup | pg-backup | `--profile backup` | + +--- + +## Datenbank + +### 3 Schemas (shared PostgreSQL) + +| Schema | Projekt | Tabellen | +|--------|---------|----------| +| `core` | Core | users, sessions, auth, rbac, notifications, email_templates, billing | +| `lehrer` | Lehrer | classroom, units, klausuren, vocab, game, meetings, messenger | +| `compliance` | Compliance | compliance_*, dsr, gdpr, sdk_tenants, consent_admin | + ```bash -# 1. Core MUSS zuerst starten -docker compose up -d -# 2. Dann Lehrer und Compliance (warten auf Core Health) +# DB-Zugang +ssh macmini "docker exec bp-core-postgres psql -U breakpilot -d breakpilot_db" ``` -### DB-Schemas -- `core` — users, sessions, auth, rbac, notifications -- `lehrer` — classroom, units, klausuren, game -- `compliance` — compliance, dsr, gdpr, sdk +--- -## Git Remotes -Immer zu BEIDEN pushen: -- `origin`: lokale Gitea (macmini:3003) -- `gitea`: gitea.meghsakha.com +## Verzeichnisstruktur + +``` +breakpilot-core/ +├── .claude/ +│ ├── CLAUDE.md # Diese Datei +│ └── rules/ # Automatische Regeln +├── backend-core/ # Python/FastAPI (Auth, RBAC, Notifications) +├── rag-service/ # Python/FastAPI (Qdrant, MinIO, Embeddings) +├── embedding-service/ # Python/FastAPI (Sentence-Transformers) +├── consent-service/ # Go/Gin (Consent-Management) +├── billing-service/ # Go (Abrechnungsservice) +├── night-scheduler/ # Python/FastAPI (Auto-Shutdown) +├── nginx/conf.d/ # Nginx Reverse Proxy Config +├── vault/ # Vault Config + TLS Certs +├── gitea/ # Gitea Config +├── docs-src/ # MkDocs Quellen +├── mkdocs.yml # MkDocs Config +├── scripts/ # Helper Scripts +└── docker-compose.yml # Haupt-Compose (28+ Services) +``` + +--- + +## Haeufige Befehle + +### Docker + +```bash +# Alle Core-Services starten +ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && /usr/local/bin/docker compose up -d" + +# Einzelnen Service neu bauen +ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && /usr/local/bin/docker compose build --no-cache " + +# Logs +ssh macmini "/usr/local/bin/docker logs -f bp-core-" + +# Status +ssh macmini "/usr/local/bin/docker ps --filter name=bp-core" +``` + +**WICHTIG:** Docker-Pfad auf Mac Mini ist `/usr/local/bin/docker` (nicht im Standard-SSH-PATH). + +### Alle 3 Projekte starten + +```bash +# 1. Core (MUSS zuerst!) +ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && /usr/local/bin/docker compose up -d" +# Warten auf Health: +ssh macmini "curl -sf http://127.0.0.1:8099/health" + +# 2. Lehrer +ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && /usr/local/bin/docker compose up -d" + +# 3. Compliance +ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-compliance && /usr/local/bin/docker compose up -d" +``` + +### Git + +```bash +# Zu BEIDEN Remotes pushen (PFLICHT!): +ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-core && git push all main" + +# Remotes: +# origin: lokale Gitea (macmini:3003) +# gitea: gitea.meghsakha.com +# all: beide gleichzeitig +``` + +--- + +## Kernprinzipien + +### 1. Open Source Policy +- **NUR Open Source mit kommerziell nutzbarer Lizenz** +- Erlaubt: MIT, Apache-2.0, BSD, ISC, MPL-2.0, LGPL +- **VERBOTEN:** GPL (ausser LGPL), AGPL, proprietaer + +### 2. Testing & Dokumentation +- Tests sind Pflicht bei jeder Aenderung +- MkDocs aktualisieren: `--profile docs` starten + +### 3. Sensitive Dateien +**NIEMALS aendern oder committen:** +- `.env`, `.env.local`, Vault-Tokens, SSL-Zertifikate +- `*.pdf`, `*.docx`, kompilierte Binaries, grosse Medien + +--- + +## Nginx Port-Zuordnung (Uebersicht) + +| Port | Ziel-Container | Projekt | +|------|----------------|---------| +| 443 | bp-lehrer-studio-v2 | Lehrer | +| 3000 | bp-lehrer-website | Lehrer | +| 3002 | bp-lehrer-admin | Lehrer | +| 3006 | bp-compliance-developer-portal | Compliance | +| 3007 | bp-compliance-admin | Compliance | +| 8000 | bp-core-backend | Core | +| 8001 | bp-lehrer-backend | Lehrer | +| 8002 | bp-compliance-backend | Compliance | +| 8086 | bp-lehrer-klausur-service | Lehrer | +| 8087 | bp-core-embedding-service | Core | +| 8089 | bp-core-camunda | Core | +| 8091 | bp-lehrer-voice-service | Lehrer | +| 8093 | bp-compliance-ai-sdk | Compliance | +| 8097 | bp-core-rag-service | Core | +| 8443 | bp-core-jitsi-web | Core | diff --git a/.claude/rules/documentation.md b/.claude/rules/documentation.md new file mode 100644 index 0000000..e5e035f --- /dev/null +++ b/.claude/rules/documentation.md @@ -0,0 +1,91 @@ +# Dokumentations-Regeln + +## Automatische Dokumentations-Aktualisierung + +**WICHTIG:** Bei JEDER Code-Änderung muss die entsprechende Dokumentation aktualisiert werden! + +## Wann Dokumentation aktualisieren? + +### API-Änderungen +Wenn du einen Endpoint änderst, hinzufügst oder entfernst: +- Aktualisiere `/docs/api/consent-service-api.md` (Go Endpoints) +- Aktualisiere `/docs/api/backend-api.md` (Python Endpoints) + +### Neue Funktionen/Klassen +Wenn du neue Funktionen, Klassen oder Module erstellst: +- Aktualisiere `/docs/consent-service/README.md` (für Go) +- Aktualisiere `/docs/backend/README.md` (für Python) + +### Architektur-Änderungen +Wenn du die Systemarchitektur änderst: +- Aktualisiere `/docs/architecture/system-architecture.md` +- Aktualisiere `/docs/architecture/data-model.md` (bei DB-Änderungen) + +### Neue Konfigurationsoptionen +Wenn du neue Umgebungsvariablen oder Konfigurationen hinzufügst: +- Aktualisiere die entsprechende README +- Füge zur `guides/local-development.md` hinzu + +## Dokumentations-Format + +### API-Endpoints dokumentieren + +```markdown +### METHOD /path/to/endpoint + +Kurze Beschreibung. + +**Request Body:** +\`\`\`json +{ + "field": "value" +} +\`\`\` + +**Response (200):** +\`\`\`json +{ + "result": "value" +} +\`\`\` + +**Errors:** +- `400`: Beschreibung +- `401`: Beschreibung +``` + +### Funktionen dokumentieren + +```markdown +### FunctionName (file.go:123) + +\`\`\`go +func FunctionName(param Type) ReturnType +\`\`\` + +**Beschreibung:** Was macht die Funktion? + +**Parameter:** +- `param`: Beschreibung + +**Rückgabe:** Beschreibung +``` + +## Checkliste nach Code-Änderungen + +Vor dem Abschluss einer Aufgabe prüfe: + +- [ ] Wurden neue API-Endpoints hinzugefügt? → API-Docs aktualisieren +- [ ] Wurden Datenmodelle geändert? → data-model.md aktualisieren +- [ ] Wurden neue Konfigurationen hinzugefügt? → README aktualisieren +- [ ] Wurden neue Abhängigkeiten hinzugefügt? → requirements.txt/go.mod UND Docs +- [ ] Wurde die Architektur geändert? → architecture/ aktualisieren + +## Beispiel: Vollständige Dokumentation einer neuen Funktion + +Wenn du z.B. `GetUserStats()` im Go Service hinzufügst: + +1. **Code schreiben** in `internal/services/stats_service.go` +2. **API-Doc aktualisieren** in `docs/api/consent-service-api.md` +3. **Service-Doc aktualisieren** in `docs/consent-service/README.md` +4. **Test schreiben** (siehe testing.md) diff --git a/.claude/rules/night-scheduler.md b/.claude/rules/night-scheduler.md new file mode 100644 index 0000000..8a001e0 --- /dev/null +++ b/.claude/rules/night-scheduler.md @@ -0,0 +1,297 @@ +# Night Scheduler - Entwicklerdokumentation + +**Status:** Produktiv +**Letzte Aktualisierung:** 2026-02-09 +**URL:** https://macmini:3002/infrastructure/night-mode +**API:** http://macmini:8096 + +--- + +## Uebersicht + +Der Night Scheduler ermoeglicht die automatische Nachtabschaltung der Docker-Services: +- Zeitgesteuerte Abschaltung (Standard: 22:00) +- Zeitgesteuerter Start (Standard: 06:00) +- Manuelle Sofortaktionen (Start/Stop) +- Dashboard-UI zur Konfiguration + +--- + +## Architektur + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Admin Dashboard (Port 3002) │ +│ /infrastructure/night-mode │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ API Proxy: /api/admin/night-mode │ +│ - GET: Status abrufen │ +│ - POST: Konfiguration speichern │ +│ - POST /execute: Sofortaktion (start/stop) │ +│ - GET /services: Service-Liste │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ night-scheduler (Port 8096) │ +│ - Python/FastAPI Container │ +│ - Prueft jede Minute ob Aktion faellig │ +│ - Fuehrt docker compose start/stop aus │ +│ - Speichert Config in /config/night-mode.json │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Dateien + +| Pfad | Beschreibung | +|------|--------------| +| `night-scheduler/scheduler.py` | Python Scheduler mit FastAPI | +| `night-scheduler/Dockerfile` | Container mit Docker CLI | +| `night-scheduler/requirements.txt` | Dependencies | +| `night-scheduler/config/night-mode.json` | Konfigurationsdatei | +| `night-scheduler/tests/test_scheduler.py` | Unit Tests | +| `admin-v2/app/api/admin/night-mode/route.ts` | API Proxy | +| `admin-v2/app/api/admin/night-mode/execute/route.ts` | Execute Endpoint | +| `admin-v2/app/api/admin/night-mode/services/route.ts` | Services Endpoint | +| `admin-v2/app/(admin)/infrastructure/night-mode/page.tsx` | UI Seite | + +--- + +## API Endpoints + +### GET /api/night-mode +Status und Konfiguration abrufen. + +**Response:** +```json +{ + "config": { + "enabled": true, + "shutdown_time": "22:00", + "startup_time": "06:00", + "last_action": "startup", + "last_action_time": "2026-02-09T06:00:00", + "excluded_services": ["night-scheduler", "nginx"] + }, + "current_time": "14:30:00", + "next_action": "shutdown", + "next_action_time": "22:00", + "time_until_next_action": "7h 30min", + "services_status": { + "backend": "running", + "postgres": "running" + } +} +``` + +### POST /api/night-mode +Konfiguration aktualisieren. + +**Request:** +```json +{ + "enabled": true, + "shutdown_time": "23:00", + "startup_time": "07:00", + "excluded_services": ["night-scheduler", "nginx", "vault"] +} +``` + +### POST /api/night-mode/execute +Sofortige Aktion ausfuehren. + +**Request:** +```json +{ + "action": "stop" // oder "start" +} +``` + +**Response:** +```json +{ + "success": true, + "message": "Aktion 'stop' erfolgreich ausgefuehrt fuer 25 Services" +} +``` + +### GET /api/night-mode/services +Liste aller Services abrufen. + +**Response:** +```json +{ + "all_services": ["backend", "postgres", "valkey", ...], + "excluded_services": ["night-scheduler", "nginx"], + "status": { + "backend": "running", + "postgres": "running" + } +} +``` + +--- + +## Konfiguration + +### Config-Format (night-mode.json) + +```json +{ + "enabled": true, + "shutdown_time": "22:00", + "startup_time": "06:00", + "last_action": "startup", + "last_action_time": "2026-02-09T06:00:00", + "excluded_services": ["night-scheduler", "nginx"] +} +``` + +### Umgebungsvariablen + +| Variable | Default | Beschreibung | +|----------|---------|--------------| +| `COMPOSE_PROJECT_NAME` | `breakpilot-pwa` | Docker Compose Projektname | + +--- + +## Ausgeschlossene Services + +Diese Services werden NICHT gestoppt: + +1. **night-scheduler** - Muss laufen, um Services zu starten +2. **nginx** - Optional, fuer HTTPS-Zugriff + +Weitere Services koennen ueber die Konfiguration ausgeschlossen werden. + +--- + +## Docker Compose Integration + +```yaml +night-scheduler: + build: ./night-scheduler + container_name: breakpilot-pwa-night-scheduler + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./night-scheduler/config:/config + - ./docker-compose.yml:/app/docker-compose.yml:ro + environment: + - COMPOSE_PROJECT_NAME=breakpilot-pwa + ports: + - "8096:8096" + networks: + - breakpilot-pwa-network + restart: unless-stopped +``` + +--- + +## Tests ausfuehren + +```bash +# Im Container +docker exec -it breakpilot-pwa-night-scheduler pytest -v + +# Lokal (mit Dependencies) +cd night-scheduler +pip install -r requirements.txt +pytest -v tests/ +``` + +--- + +## Deployment + +```bash +# 1. Dateien synchronisieren +rsync -avz night-scheduler/ macmini:.../night-scheduler/ + +# 2. Container bauen +ssh macmini "docker compose -f .../docker-compose.yml build --no-cache night-scheduler" + +# 3. Container starten +ssh macmini "docker compose -f .../docker-compose.yml up -d night-scheduler" + +# 4. Testen +curl http://macmini:8096/health +curl http://macmini:8096/api/night-mode +``` + +--- + +## Troubleshooting + +### Problem: Services werden nicht gestoppt/gestartet + +1. Pruefen ob Docker Socket gemountet ist: + ```bash + docker exec breakpilot-pwa-night-scheduler ls -la /var/run/docker.sock + ``` + +2. Pruefen ob docker compose CLI verfuegbar ist: + ```bash + docker exec breakpilot-pwa-night-scheduler docker compose version + ``` + +3. Logs pruefen: + ```bash + docker logs breakpilot-pwa-night-scheduler + ``` + +### Problem: Konfiguration wird nicht gespeichert + +1. Pruefen ob /config beschreibbar ist: + ```bash + docker exec breakpilot-pwa-night-scheduler touch /config/test + ``` + +2. Volume-Mount pruefen in docker-compose.yml + +### Problem: API nicht erreichbar + +1. Container-Status pruefen: + ```bash + docker ps | grep night-scheduler + ``` + +2. Health-Check pruefen: + ```bash + curl http://localhost:8096/health + ``` + +--- + +## Sicherheitshinweise + +- Der Container benoetigt Zugriff auf den Docker Socket +- Nur interne Services koennen gestoppt/gestartet werden +- Keine Authentifizierung (internes Netzwerk) +- Keine sensitiven Daten in der Konfiguration + +--- + +## Dependencies (SBOM) + +| Package | Version | Lizenz | +|---------|---------|--------| +| FastAPI | 0.109.0 | MIT | +| Uvicorn | 0.27.0 | BSD-3-Clause | +| Pydantic | 2.5.3 | MIT | +| pytest | 8.0.0 | MIT | +| pytest-asyncio | 0.23.0 | Apache-2.0 | +| httpx | 0.26.0 | BSD-3-Clause | + +--- + +## Aenderungshistorie + +| Datum | Aenderung | +|-------|-----------| +| 2026-02-09 | Initiale Implementierung | + diff --git a/.claude/rules/open-source-policy.md b/.claude/rules/open-source-policy.md new file mode 100644 index 0000000..af7001a --- /dev/null +++ b/.claude/rules/open-source-policy.md @@ -0,0 +1,99 @@ +# Open Source Policy + +## Lizenzprüfung (AUTOMATISCH BEI JEDER DEPENDENCY) + +### Erlaubte Lizenzen ✅ + +| Lizenz | Typ | Kommerziell OK | +|--------|-----|----------------| +| MIT | Permissive | ✅ | +| Apache-2.0 | Permissive | ✅ | +| BSD-2-Clause | Permissive | ✅ | +| BSD-3-Clause | Permissive | ✅ | +| ISC | Permissive | ✅ | +| MPL-2.0 | Weak Copyleft | ✅ | +| LGPL-2.1 / LGPL-3.0 | Weak Copyleft | ✅ (nur linking) | +| CC0-1.0 | Public Domain | ✅ | +| Unlicense | Public Domain | ✅ | + +### Verbotene Lizenzen ❌ + +| Lizenz | Grund | +|--------|-------| +| GPL-2.0 / GPL-3.0 | Copyleft - infiziert Projekt | +| AGPL-3.0 | Network Copyleft - SaaS-Killer | +| SSPL | Server Side Public License | +| BSL | Business Source License | +| "Non-Commercial" | Keine kommerzielle Nutzung | +| "Educational Only" | Nur für Bildung | +| Proprietary | Keine OSS | + +--- + +## Workflow bei neuer Dependency + +### 1. Vor dem Hinzufügen prüfen + +```bash +# NPM Package +npm view license + +# Python Package +pip show | grep License + +# Go Module +go-licenses check +``` + +### 2. Bei Unklarheit + +- README.md des Projekts lesen +- LICENSE-Datei prüfen +- SPDX-Identifier suchen +- Im Zweifel: **NICHT verwenden** + +### 3. Nach dem Hinzufügen + +**SBOM aktualisieren:** https://macmini:3002/infrastructure/sbom + +```bash +# SBOM generieren +cd /Users/benjaminadmin/Projekte/breakpilot-pwa + +# Python +pip-licenses --format=json > sbom/python-licenses.json + +# Node.js +npx license-checker --json > sbom/node-licenses.json + +# Go +go-licenses csv ./... > sbom/go-licenses.csv +``` + +--- + +## Grenzfälle + +### Dual-Licensed Packages +- Wenn MIT **oder** GPL angeboten wird → MIT wählen +- Dokumentieren welche Lizenz gewählt wurde + +### Transitive Dependencies +- Auch indirekte Abhängigkeiten prüfen +- `npm ls`, `pip-tree`, `go mod graph` + +### Fonts & Assets +- Google Fonts: ✅ (OFL) +- Font Awesome Free: ✅ (CC BY 4.0 / OFL / MIT) +- Icons8: ❌ (Attribution required, kompliziert) + +--- + +## Checkliste bei PR/Commit + +Wenn neue Dependencies hinzugefügt wurden: + +- [ ] Lizenz ist in der Whitelist +- [ ] SBOM wurde aktualisiert +- [ ] Keine GPL/AGPL-Abhängigkeiten eingeschleppt +- [ ] Bei Dual-License: MIT/Apache gewählt diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 0000000..d7441f6 --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,202 @@ +# Test-Regeln + +## Automatische Test-Erweiterung + +**WICHTIG:** Bei JEDER Code-Änderung müssen entsprechende Tests erstellt oder aktualisiert werden! + +## Wann Tests schreiben? + +### IMMER wenn du: +1. **Neue Funktionen** erstellst → Unit Test +2. **Neue API-Endpoints** hinzufügst → Handler Test +3. **Bugs fixst** → Regression Test (der Bug sollte nie wieder auftreten) +4. **Bestehenden Code änderst** → Bestehende Tests anpassen + +## Test-Struktur + +### Go Tests (Consent Service) + +**Speicherort:** Im gleichen Verzeichnis wie der Code + +``` +internal/ +├── services/ +│ ├── auth_service.go +│ └── auth_service_test.go ← Test hier +├── handlers/ +│ ├── handlers.go +│ └── handlers_test.go ← Test hier +└── middleware/ + ├── auth.go + └── middleware_test.go ← Test hier +``` + +**Test-Namenskonvention:** +```go +func TestFunctionName_Scenario_ExpectedResult(t *testing.T) + +// Beispiele: +func TestHashPassword_ValidPassword_ReturnsHash(t *testing.T) +func TestLogin_InvalidCredentials_Returns401(t *testing.T) +func TestCreateDocument_MissingTitle_ReturnsError(t *testing.T) +``` + +**Test-Template:** +```go +func TestFunctionName(t *testing.T) { + // Arrange + service := &MyService{} + input := "test-input" + + // Act + result, err := service.DoSomething(input) + + // Assert + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if result != expected { + t.Errorf("Expected %v, got %v", expected, result) + } +} +``` + +**Table-Driven Tests bevorzugen:** +```go +func TestValidateEmail(t *testing.T) { + tests := []struct { + name string + email string + expected bool + }{ + {"valid email", "test@example.com", true}, + {"missing @", "testexample.com", false}, + {"empty", "", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ValidateEmail(tt.email) + if result != tt.expected { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } +} +``` + +### Python Tests (Backend) + +**Speicherort:** `/backend/tests/` + +``` +backend/ +├── consent_client.py +├── gdpr_api.py +└── tests/ + ├── __init__.py + ├── test_consent_client.py ← Tests für consent_client.py + └── test_gdpr_api.py ← Tests für gdpr_api.py +``` + +**Test-Namenskonvention:** +```python +class TestClassName: + def test_method_scenario_expected_result(self): + pass + +# Beispiele: +class TestConsentClient: + def test_check_consent_valid_token_returns_status(self): + pass + + def test_check_consent_expired_token_raises_error(self): + pass +``` + +**Test-Template:** +```python +import pytest +from unittest.mock import AsyncMock, patch, MagicMock + +class TestMyFeature: + def test_sync_function(self): + # Arrange + input_data = "test" + + # Act + result = my_function(input_data) + + # Assert + assert result == expected + + @pytest.mark.asyncio + async def test_async_function(self): + # Arrange + client = MyClient() + + # Act + with patch("httpx.AsyncClient") as mock: + mock_instance = AsyncMock() + mock.return_value = mock_instance + result = await client.fetch_data() + + # Assert + assert result is not None +``` + +## Test-Kategorien + +### 1. Unit Tests (Höchste Priorität) +- Testen einzelne Funktionen/Methoden +- Keine externen Abhängigkeiten (Mocks verwenden) +- Schnell ausführbar + +### 2. Integration Tests +- Testen Zusammenspiel mehrerer Komponenten +- Können echte DB verwenden (Test-DB) + +### 3. Security Tests +- Auth/JWT Validierung +- Passwort-Hashing +- Berechtigungsprüfung + +## Checkliste vor Abschluss + +Vor dem Abschluss einer Aufgabe: + +- [ ] Gibt es Tests für alle neuen Funktionen? +- [ ] Gibt es Tests für alle Edge Cases? +- [ ] Gibt es Tests für Fehlerfälle? +- [ ] Laufen alle bestehenden Tests noch? (`go test ./...` / `pytest`) +- [ ] Ist die Test-Coverage angemessen? + +## Tests ausführen + +```bash +# Go - Alle Tests +cd consent-service && go test -v ./... + +# Go - Mit Coverage +cd consent-service && go test -cover ./... + +# Python - Alle Tests +cd backend && source venv/bin/activate && pytest -v + +# Python - Mit Coverage +cd backend && pytest --cov=. --cov-report=html +``` + +## Beispiel: Vollständiger Test-Workflow + +Wenn du z.B. eine neue `GetUserStats()` Funktion im Go Service hinzufügst: + +1. **Funktion schreiben** in `internal/services/stats_service.go` +2. **Test erstellen** in `internal/services/stats_service_test.go`: + ```go + func TestGetUserStats_ValidUser_ReturnsStats(t *testing.T) {...} + func TestGetUserStats_InvalidUser_ReturnsError(t *testing.T) {...} + func TestGetUserStats_NoConsents_ReturnsEmptyStats(t *testing.T) {...} + ``` +3. **Tests ausführen**: `go test -v ./internal/services/...` +4. **Dokumentation aktualisieren** (siehe documentation.md) diff --git a/docker-compose.yml b/docker-compose.yml index d10da9c..f412eaa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -681,6 +681,7 @@ services: GITEA__service__REQUIRE_SIGNIN_VIEW: "true" GITEA__repository__DEFAULT_BRANCH: main GITEA__log__LEVEL: Warn + GITEA__security__INSTALL_LOCK: "true" GITEA__webhook__ALLOWED_HOST_LIST: "*" extra_hosts: - "macmini:192.168.178.100" @@ -810,9 +811,9 @@ services: profiles: [docs] platform: linux/arm64 ports: - - "8009:8009" + - "8009:80" healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8009/"] + test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:80/"] interval: 30s timeout: 10s retries: 3 diff --git a/docs-src/ai-compliance-sdk/migrations/001_rbac_schema.sql b/docs-src/ai-compliance-sdk/migrations/001_rbac_schema.sql deleted file mode 100644 index 72352be..0000000 --- a/docs-src/ai-compliance-sdk/migrations/001_rbac_schema.sql +++ /dev/null @@ -1,321 +0,0 @@ --- AI Compliance SDK - RBAC Schema --- Migration 001: Multi-Tenant RBAC with Namespace Isolation - --- Enable UUID extension -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; - --- ============================================================================ --- Tenants (Mandanten) --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_tenants ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(255) NOT NULL, - slug VARCHAR(100) NOT NULL UNIQUE, - settings JSONB DEFAULT '{}', - max_users INT DEFAULT 100, - llm_quota_monthly INT DEFAULT 10000, - status VARCHAR(50) DEFAULT 'active', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_compliance_tenants_slug ON compliance_tenants(slug); -CREATE INDEX idx_compliance_tenants_status ON compliance_tenants(status); - --- ============================================================================ --- Namespaces (Abteilungen - z.B. CFO Use-Case) --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_namespaces ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - slug VARCHAR(100) NOT NULL, - parent_namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL, - isolation_level VARCHAR(50) DEFAULT 'strict', -- 'strict', 'shared', 'public' - data_classification VARCHAR(50) DEFAULT 'internal', -- 'public', 'internal', 'confidential', 'restricted' - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(tenant_id, slug) -); - -CREATE INDEX idx_compliance_namespaces_tenant ON compliance_namespaces(tenant_id); -CREATE INDEX idx_compliance_namespaces_parent ON compliance_namespaces(parent_namespace_id); - --- ============================================================================ --- Roles with Permissions --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_roles ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID REFERENCES compliance_tenants(id) ON DELETE CASCADE, - name VARCHAR(100) NOT NULL, - description TEXT, - permissions TEXT[] NOT NULL DEFAULT '{}', - is_system_role BOOLEAN DEFAULT FALSE, - hierarchy_level INT DEFAULT 100, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(tenant_id, name) -); - -CREATE INDEX idx_compliance_roles_tenant ON compliance_roles(tenant_id); -CREATE INDEX idx_compliance_roles_system ON compliance_roles(is_system_role); - --- ============================================================================ --- System Roles (Pre-defined) --- ============================================================================ -INSERT INTO compliance_roles (name, description, permissions, is_system_role, hierarchy_level) VALUES -('compliance_executive', 'Executive mit Lesezugriff auf Compliance-Daten und LLM-Queries', - ARRAY['compliance:*:read', 'llm:query:execute', 'audit:own:read'], TRUE, 10), -('compliance_officer', 'Compliance-Verantwortlicher mit vollem Zugriff', - ARRAY['compliance:*', 'audit:*', 'llm:*', 'namespace:read'], TRUE, 20), -('data_protection_officer', 'Datenschutzbeauftragter', - ARRAY['compliance:privacy:*', 'consent:*', 'dsr:*', 'audit:read', 'llm:query:execute'], TRUE, 25), -('namespace_admin', 'Administrator fuer einen Namespace', - ARRAY['namespace:own:admin', 'compliance:own:*', 'llm:own:query', 'audit:own:read'], TRUE, 50), -('auditor', 'Auditor mit Lesezugriff', - ARRAY['compliance:read', 'audit:log:read', 'evidence:read'], TRUE, 60), -('compliance_user', 'Standardbenutzer mit eingeschraenktem Zugriff', - ARRAY['compliance:own:read', 'llm:own:query'], TRUE, 100) -ON CONFLICT (tenant_id, name) DO NOTHING; - --- ============================================================================ --- User-Role Assignments with Namespace Scope --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_user_roles ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL, - role_id UUID NOT NULL REFERENCES compliance_roles(id) ON DELETE CASCADE, - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE CASCADE, - granted_by UUID NOT NULL, - expires_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(user_id, role_id, tenant_id, namespace_id) -); - -CREATE INDEX idx_compliance_user_roles_user ON compliance_user_roles(user_id); -CREATE INDEX idx_compliance_user_roles_tenant ON compliance_user_roles(tenant_id); -CREATE INDEX idx_compliance_user_roles_namespace ON compliance_user_roles(namespace_id); -CREATE INDEX idx_compliance_user_roles_expires ON compliance_user_roles(expires_at) WHERE expires_at IS NOT NULL; - --- ============================================================================ --- LLM Access Policies --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_llm_policies ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - description TEXT, - allowed_data_categories TEXT[] DEFAULT '{}', -- 'salary', 'health', 'personal', 'financial' - blocked_data_categories TEXT[] DEFAULT '{}', - require_pii_redaction BOOLEAN DEFAULT TRUE, - pii_redaction_level VARCHAR(50) DEFAULT 'strict', -- 'strict', 'moderate', 'minimal', 'none' - allowed_models TEXT[] DEFAULT '{}', -- 'qwen2.5:7b', 'claude-3-sonnet' - max_tokens_per_request INT DEFAULT 4000, - max_requests_per_day INT DEFAULT 1000, - max_requests_per_hour INT DEFAULT 100, - is_active BOOLEAN DEFAULT TRUE, - priority INT DEFAULT 100, -- Lower = higher priority - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_compliance_llm_policies_tenant ON compliance_llm_policies(tenant_id); -CREATE INDEX idx_compliance_llm_policies_namespace ON compliance_llm_policies(namespace_id); -CREATE INDEX idx_compliance_llm_policies_active ON compliance_llm_policies(is_active, priority); - --- ============================================================================ --- LLM Audit Log (Immutable) --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_llm_audit_log ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id), - namespace_id UUID REFERENCES compliance_namespaces(id), - user_id UUID NOT NULL, - session_id VARCHAR(100), - operation VARCHAR(100) NOT NULL, -- 'query', 'completion', 'embedding', 'analysis' - model_used VARCHAR(100) NOT NULL, - provider VARCHAR(50) NOT NULL, -- 'ollama', 'anthropic', 'openai' - prompt_hash VARCHAR(64) NOT NULL, -- SHA-256 of prompt (no raw PII stored) - prompt_length INT NOT NULL, - response_length INT, - tokens_used INT NOT NULL, - duration_ms INT NOT NULL, - pii_detected BOOLEAN DEFAULT FALSE, - pii_types_detected TEXT[] DEFAULT '{}', - pii_redacted BOOLEAN DEFAULT FALSE, - policy_id UUID REFERENCES compliance_llm_policies(id), - policy_violations TEXT[] DEFAULT '{}', - data_categories_accessed TEXT[] DEFAULT '{}', - error_message TEXT, - request_metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Partitioning-ready indexes for large audit tables -CREATE INDEX idx_llm_audit_tenant_date ON compliance_llm_audit_log(tenant_id, created_at DESC); -CREATE INDEX idx_llm_audit_user ON compliance_llm_audit_log(user_id, created_at DESC); -CREATE INDEX idx_llm_audit_namespace ON compliance_llm_audit_log(namespace_id, created_at DESC); -CREATE INDEX idx_llm_audit_operation ON compliance_llm_audit_log(operation, created_at DESC); -CREATE INDEX idx_llm_audit_pii ON compliance_llm_audit_log(pii_detected, created_at DESC) WHERE pii_detected = TRUE; -CREATE INDEX idx_llm_audit_violations ON compliance_llm_audit_log(created_at DESC) WHERE array_length(policy_violations, 1) > 0; - --- ============================================================================ --- General Audit Trail --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_audit_trail ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id), - namespace_id UUID REFERENCES compliance_namespaces(id), - user_id UUID NOT NULL, - action VARCHAR(100) NOT NULL, -- 'create', 'update', 'delete', 'access', 'export' - resource_type VARCHAR(100) NOT NULL, -- 'role', 'namespace', 'policy', 'evidence' - resource_id UUID, - old_values JSONB, - new_values JSONB, - ip_address INET, - user_agent TEXT, - reason TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_audit_trail_tenant_date ON compliance_audit_trail(tenant_id, created_at DESC); -CREATE INDEX idx_audit_trail_user ON compliance_audit_trail(user_id, created_at DESC); -CREATE INDEX idx_audit_trail_resource ON compliance_audit_trail(resource_type, resource_id); - --- ============================================================================ --- API Keys for SDK Access --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_api_keys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - key_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 of API key - key_prefix VARCHAR(8) NOT NULL, -- First 8 chars for identification - permissions TEXT[] DEFAULT '{}', - namespace_restrictions UUID[] DEFAULT '{}', -- Empty = all namespaces - rate_limit_per_hour INT DEFAULT 1000, - expires_at TIMESTAMPTZ, - last_used_at TIMESTAMPTZ, - is_active BOOLEAN DEFAULT TRUE, - created_by UUID NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_api_keys_tenant ON compliance_api_keys(tenant_id); -CREATE INDEX idx_api_keys_prefix ON compliance_api_keys(key_prefix); -CREATE INDEX idx_api_keys_active ON compliance_api_keys(is_active, expires_at); - --- ============================================================================ --- LLM Usage Statistics (Aggregated) --- ============================================================================ -CREATE TABLE IF NOT EXISTS compliance_llm_usage_stats ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id), - namespace_id UUID REFERENCES compliance_namespaces(id), - user_id UUID, - period_start DATE NOT NULL, - period_type VARCHAR(20) NOT NULL, -- 'daily', 'weekly', 'monthly' - total_requests INT DEFAULT 0, - total_tokens INT DEFAULT 0, - total_duration_ms BIGINT DEFAULT 0, - requests_with_pii INT DEFAULT 0, - policy_violations INT DEFAULT 0, - models_used JSONB DEFAULT '{}', -- {"qwen2.5:7b": 100, "claude-3-sonnet": 50} - created_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(tenant_id, namespace_id, user_id, period_start, period_type) -); - -CREATE INDEX idx_llm_usage_tenant_period ON compliance_llm_usage_stats(tenant_id, period_start DESC); - --- ============================================================================ --- Helper Functions --- ============================================================================ - --- Function to check if user has permission in namespace -CREATE OR REPLACE FUNCTION check_namespace_permission( - p_user_id UUID, - p_tenant_id UUID, - p_namespace_id UUID, - p_permission TEXT -) RETURNS BOOLEAN AS $$ -DECLARE - has_permission BOOLEAN := FALSE; -BEGIN - SELECT EXISTS ( - SELECT 1 - FROM compliance_user_roles ur - JOIN compliance_roles r ON ur.role_id = r.id - WHERE ur.user_id = p_user_id - AND ur.tenant_id = p_tenant_id - AND (ur.namespace_id = p_namespace_id OR ur.namespace_id IS NULL) - AND (ur.expires_at IS NULL OR ur.expires_at > NOW()) - AND ( - p_permission = ANY(r.permissions) - OR EXISTS ( - SELECT 1 FROM unnest(r.permissions) perm - WHERE perm LIKE '%:*' AND p_permission LIKE replace(perm, ':*', '') || ':%' - ) - ) - ) INTO has_permission; - - RETURN has_permission; -END; -$$ LANGUAGE plpgsql; - --- Function to get effective permissions for user in namespace -CREATE OR REPLACE FUNCTION get_effective_permissions( - p_user_id UUID, - p_tenant_id UUID, - p_namespace_id UUID -) RETURNS TEXT[] AS $$ -DECLARE - permissions TEXT[]; -BEGIN - SELECT array_agg(DISTINCT perm) - INTO permissions - FROM ( - SELECT unnest(r.permissions) as perm - FROM compliance_user_roles ur - JOIN compliance_roles r ON ur.role_id = r.id - WHERE ur.user_id = p_user_id - AND ur.tenant_id = p_tenant_id - AND (ur.namespace_id = p_namespace_id OR ur.namespace_id IS NULL) - AND (ur.expires_at IS NULL OR ur.expires_at > NOW()) - ) sub; - - RETURN COALESCE(permissions, '{}'); -END; -$$ LANGUAGE plpgsql; - --- ============================================================================ --- Default Tenant for Breakpilot (Self-Hosting) --- ============================================================================ -INSERT INTO compliance_tenants (name, slug, settings, max_users, llm_quota_monthly) -VALUES ( - 'Breakpilot', - 'breakpilot', - '{"deployment": "self-hosted", "hybrid_mode": true}', - 1000, - 100000 -) ON CONFLICT (slug) DO NOTHING; - --- Default namespaces -INSERT INTO compliance_namespaces (tenant_id, name, slug, data_classification) -SELECT - t.id, - ns.name, - ns.slug, - ns.classification -FROM compliance_tenants t -CROSS JOIN (VALUES - ('Allgemein', 'general', 'internal'), - ('Finanzen', 'finance', 'restricted'), - ('Personal', 'hr', 'confidential'), - ('IT', 'it', 'internal'), - ('Compliance', 'compliance', 'confidential') -) AS ns(name, slug, classification) -WHERE t.slug = 'breakpilot' -ON CONFLICT (tenant_id, slug) DO NOTHING; diff --git a/docs-src/ai-compliance-sdk/migrations/002_dsgvo_schema.sql b/docs-src/ai-compliance-sdk/migrations/002_dsgvo_schema.sql deleted file mode 100644 index 1f729ed..0000000 --- a/docs-src/ai-compliance-sdk/migrations/002_dsgvo_schema.sql +++ /dev/null @@ -1,215 +0,0 @@ --- DSGVO Schema Migration --- AI Compliance SDK - Phase 4: DSGVO Integration - --- ============================================================================ --- VVT - Verarbeitungsverzeichnis (Art. 30 DSGVO) --- ============================================================================ - -CREATE TABLE IF NOT EXISTS dsgvo_processing_activities ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL, - name VARCHAR(255) NOT NULL, - description TEXT, - purpose TEXT NOT NULL, - legal_basis VARCHAR(50) NOT NULL, -- consent, contract, legal_obligation, vital_interests, public_interest, legitimate_interests - legal_basis_details TEXT, - data_categories JSONB DEFAULT '[]', - data_subject_categories JSONB DEFAULT '[]', - recipients JSONB DEFAULT '[]', - third_country_transfer BOOLEAN DEFAULT FALSE, - transfer_safeguards TEXT, - retention_period VARCHAR(255), - retention_policy_id UUID, - tom_reference JSONB DEFAULT '[]', - dsfa_required BOOLEAN DEFAULT FALSE, - dsfa_id UUID, - responsible_person VARCHAR(255), - responsible_department VARCHAR(255), - systems JSONB DEFAULT '[]', - status VARCHAR(50) DEFAULT 'draft', -- draft, active, under_review, archived - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID NOT NULL, - last_reviewed_at TIMESTAMPTZ, - next_review_at TIMESTAMPTZ -); - -CREATE INDEX IF NOT EXISTS idx_dsgvo_pa_tenant ON dsgvo_processing_activities(tenant_id); -CREATE INDEX IF NOT EXISTS idx_dsgvo_pa_status ON dsgvo_processing_activities(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_dsgvo_pa_namespace ON dsgvo_processing_activities(namespace_id) WHERE namespace_id IS NOT NULL; - --- ============================================================================ --- DSFA - Datenschutz-Folgenabschätzung (Art. 35 DSGVO) --- ============================================================================ - -CREATE TABLE IF NOT EXISTS dsgvo_dsfa ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL, - processing_activity_id UUID REFERENCES dsgvo_processing_activities(id) ON DELETE SET NULL, - name VARCHAR(255) NOT NULL, - description TEXT, - processing_description TEXT, - necessity_assessment TEXT, - proportionality_assessment TEXT, - risks JSONB DEFAULT '[]', - mitigations JSONB DEFAULT '[]', - dpo_consulted BOOLEAN DEFAULT FALSE, - dpo_opinion TEXT, - authority_consulted BOOLEAN DEFAULT FALSE, - authority_reference VARCHAR(255), - status VARCHAR(50) DEFAULT 'draft', -- draft, in_progress, completed, approved, rejected - overall_risk_level VARCHAR(20), -- low, medium, high, very_high - conclusion TEXT, - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID NOT NULL, - approved_by UUID, - approved_at TIMESTAMPTZ -); - -CREATE INDEX IF NOT EXISTS idx_dsgvo_dsfa_tenant ON dsgvo_dsfa(tenant_id); -CREATE INDEX IF NOT EXISTS idx_dsgvo_dsfa_status ON dsgvo_dsfa(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_dsgvo_dsfa_pa ON dsgvo_dsfa(processing_activity_id) WHERE processing_activity_id IS NOT NULL; - --- ============================================================================ --- TOM - Technische und Organisatorische Maßnahmen (Art. 32 DSGVO) --- ============================================================================ - -CREATE TABLE IF NOT EXISTS dsgvo_tom ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL, - category VARCHAR(50) NOT NULL, -- access_control, encryption, pseudonymization, etc. - subcategory VARCHAR(100), - name VARCHAR(255) NOT NULL, - description TEXT, - type VARCHAR(20) NOT NULL, -- technical, organizational - implementation_status VARCHAR(50) DEFAULT 'planned', -- planned, in_progress, implemented, verified, not_applicable - implemented_at TIMESTAMPTZ, - verified_at TIMESTAMPTZ, - verified_by UUID, - effectiveness_rating VARCHAR(20), -- low, medium, high - documentation TEXT, - responsible_person VARCHAR(255), - responsible_department VARCHAR(255), - review_frequency VARCHAR(50), -- monthly, quarterly, annually - last_review_at TIMESTAMPTZ, - next_review_at TIMESTAMPTZ, - related_controls JSONB DEFAULT '[]', - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID NOT NULL -); - -CREATE INDEX IF NOT EXISTS idx_dsgvo_tom_tenant ON dsgvo_tom(tenant_id); -CREATE INDEX IF NOT EXISTS idx_dsgvo_tom_category ON dsgvo_tom(tenant_id, category); -CREATE INDEX IF NOT EXISTS idx_dsgvo_tom_status ON dsgvo_tom(tenant_id, implementation_status); - --- ============================================================================ --- DSR - Data Subject Requests / Betroffenenrechte (Art. 15-22 DSGVO) --- ============================================================================ - -CREATE TABLE IF NOT EXISTS dsgvo_dsr ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL, - request_type VARCHAR(50) NOT NULL, -- access, rectification, erasure, restriction, portability, objection - status VARCHAR(50) DEFAULT 'received', -- received, verified, in_progress, completed, rejected, extended - subject_name VARCHAR(255) NOT NULL, - subject_email VARCHAR(255) NOT NULL, - subject_identifier VARCHAR(255), - request_description TEXT, - request_channel VARCHAR(50), -- email, form, phone, letter - received_at TIMESTAMPTZ NOT NULL, - verified_at TIMESTAMPTZ, - verification_method VARCHAR(100), - deadline_at TIMESTAMPTZ NOT NULL, - extended_deadline_at TIMESTAMPTZ, - extension_reason TEXT, - completed_at TIMESTAMPTZ, - response_sent BOOLEAN DEFAULT FALSE, - response_sent_at TIMESTAMPTZ, - response_method VARCHAR(50), - rejection_reason TEXT, - notes TEXT, - affected_systems JSONB DEFAULT '[]', - assigned_to UUID, - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID NOT NULL -); - -CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_tenant ON dsgvo_dsr(tenant_id); -CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_status ON dsgvo_dsr(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_deadline ON dsgvo_dsr(tenant_id, deadline_at) WHERE status NOT IN ('completed', 'rejected'); -CREATE INDEX IF NOT EXISTS idx_dsgvo_dsr_type ON dsgvo_dsr(tenant_id, request_type); - --- ============================================================================ --- Retention Policies - Löschfristen (Art. 17 DSGVO) --- ============================================================================ - -CREATE TABLE IF NOT EXISTS dsgvo_retention_policies ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL, - name VARCHAR(255) NOT NULL, - description TEXT, - data_category VARCHAR(100) NOT NULL, - retention_period_days INT NOT NULL, - retention_period_text VARCHAR(255), -- Human readable - legal_basis VARCHAR(100), - legal_reference VARCHAR(255), -- § 147 AO, § 257 HGB, etc. - deletion_method VARCHAR(50), -- automatic, manual, anonymization - deletion_procedure TEXT, - exception_criteria TEXT, - applicable_systems JSONB DEFAULT '[]', - responsible_person VARCHAR(255), - responsible_department VARCHAR(255), - status VARCHAR(50) DEFAULT 'draft', -- draft, active, archived - last_review_at TIMESTAMPTZ, - next_review_at TIMESTAMPTZ, - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID NOT NULL -); - -CREATE INDEX IF NOT EXISTS idx_dsgvo_retention_tenant ON dsgvo_retention_policies(tenant_id); -CREATE INDEX IF NOT EXISTS idx_dsgvo_retention_status ON dsgvo_retention_policies(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_dsgvo_retention_category ON dsgvo_retention_policies(tenant_id, data_category); - --- ============================================================================ --- Insert default TOM categories as reference data --- ============================================================================ - --- This is optional - the categories are also defined in code --- But having them in the database allows for easier UI population - -CREATE TABLE IF NOT EXISTS dsgvo_tom_categories ( - id VARCHAR(50) PRIMARY KEY, - name_de VARCHAR(255) NOT NULL, - name_en VARCHAR(255) NOT NULL, - description_de TEXT, - article_reference VARCHAR(50) -); - -INSERT INTO dsgvo_tom_categories (id, name_de, name_en, description_de, article_reference) VALUES - ('access_control', 'Zutrittskontrolle', 'Physical Access Control', 'Maßnahmen zur Verhinderung des unbefugten Zutritts zu Datenverarbeitungsanlagen', 'Art. 32 Abs. 1 lit. b'), - ('admission_control', 'Zugangskontrolle', 'Logical Access Control', 'Maßnahmen zur Verhinderung der unbefugten Nutzung von DV-Systemen', 'Art. 32 Abs. 1 lit. b'), - ('access_management', 'Zugriffskontrolle', 'Access Management', 'Maßnahmen zur Gewährleistung, dass nur befugte Personen Zugriff auf Daten haben', 'Art. 32 Abs. 1 lit. b'), - ('transfer_control', 'Weitergabekontrolle', 'Transfer Control', 'Maßnahmen zur Verhinderung des unbefugten Lesens, Kopierens oder Entfernens bei der Übertragung', 'Art. 32 Abs. 1 lit. b'), - ('input_control', 'Eingabekontrolle', 'Input Control', 'Maßnahmen zur Nachvollziehbarkeit von Eingabe, Änderung und Löschung von Daten', 'Art. 32 Abs. 1 lit. b'), - ('availability_control', 'Verfügbarkeitskontrolle', 'Availability Control', 'Maßnahmen zum Schutz gegen zufällige oder mutwillige Zerstörung oder Verlust', 'Art. 32 Abs. 1 lit. b, c'), - ('separation_control', 'Trennungskontrolle', 'Separation Control', 'Maßnahmen zur getrennten Verarbeitung von Daten, die zu unterschiedlichen Zwecken erhoben wurden', 'Art. 32 Abs. 1 lit. b'), - ('encryption', 'Verschlüsselung', 'Encryption', 'Verschlüsselung personenbezogener Daten', 'Art. 32 Abs. 1 lit. a'), - ('pseudonymization', 'Pseudonymisierung', 'Pseudonymization', 'Verarbeitung in einer Weise, dass die Daten ohne zusätzliche Informationen nicht mehr zugeordnet werden können', 'Art. 32 Abs. 1 lit. a'), - ('resilience', 'Belastbarkeit', 'Resilience', 'Fähigkeit, die Verfügbarkeit und den Zugang bei einem Zwischenfall rasch wiederherzustellen', 'Art. 32 Abs. 1 lit. b, c'), - ('recovery', 'Wiederherstellung', 'Recovery', 'Verfahren zur Wiederherstellung der Verfügbarkeit und des Zugangs', 'Art. 32 Abs. 1 lit. c'), - ('testing', 'Regelmäßige Überprüfung', 'Regular Testing', 'Verfahren zur regelmäßigen Überprüfung, Bewertung und Evaluierung der Wirksamkeit', 'Art. 32 Abs. 1 lit. d') -ON CONFLICT (id) DO NOTHING; diff --git a/docs-src/ai-compliance-sdk/migrations/003_ucca_schema.sql b/docs-src/ai-compliance-sdk/migrations/003_ucca_schema.sql deleted file mode 100644 index cdddb2c..0000000 --- a/docs-src/ai-compliance-sdk/migrations/003_ucca_schema.sql +++ /dev/null @@ -1,96 +0,0 @@ --- Migration 003: UCCA (Use-Case Compliance & Feasibility Advisor) Schema --- Creates table for storing AI use-case assessments - --- ============================================================================ --- UCCA Assessments Table --- ============================================================================ - -CREATE TABLE IF NOT EXISTS ucca_assessments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL, - - -- Metadata - title VARCHAR(500), - policy_version VARCHAR(50) NOT NULL DEFAULT '1.0.0', - status VARCHAR(50) DEFAULT 'completed', - - -- Input - intake JSONB NOT NULL, -- Full UseCaseIntake - use_case_text_stored BOOLEAN DEFAULT FALSE, -- Opt-in for raw text storage - use_case_text_hash VARCHAR(64), -- SHA-256 hash (always stored) - - -- Results - Main verdict - feasibility VARCHAR(20) NOT NULL, -- YES/CONDITIONAL/NO - risk_level VARCHAR(20) NOT NULL, -- MINIMAL/LOW/MEDIUM/HIGH/UNACCEPTABLE - complexity VARCHAR(10) NOT NULL, -- LOW/MEDIUM/HIGH - risk_score INT NOT NULL DEFAULT 0, -- 0-100 - - -- Results - Details (JSONB for flexibility) - triggered_rules JSONB DEFAULT '[]', -- Array of TriggeredRule - required_controls JSONB DEFAULT '[]', -- Array of RequiredControl - recommended_architecture JSONB DEFAULT '[]', -- Array of PatternRecommendation - forbidden_patterns JSONB DEFAULT '[]', -- Array of ForbiddenPattern - example_matches JSONB DEFAULT '[]', -- Array of ExampleMatch - - -- Results - Flags - dsfa_recommended BOOLEAN DEFAULT FALSE, - art22_risk BOOLEAN DEFAULT FALSE, -- Art. 22 GDPR automated decision risk - training_allowed VARCHAR(50), -- YES/CONDITIONAL/NO - - -- LLM Explanation (optional) - explanation_text TEXT, - explanation_generated_at TIMESTAMPTZ, - explanation_model VARCHAR(100), - - -- Domain classification - domain VARCHAR(50), - - -- Audit trail - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID NOT NULL -); - --- ============================================================================ --- Indexes for Performance --- ============================================================================ - --- Primary lookup by tenant -CREATE INDEX idx_ucca_tenant ON ucca_assessments(tenant_id); - --- List view with sorting by date -CREATE INDEX idx_ucca_tenant_created ON ucca_assessments(tenant_id, created_at DESC); - --- Filter by feasibility -CREATE INDEX idx_ucca_tenant_feasibility ON ucca_assessments(tenant_id, feasibility); - --- Filter by domain -CREATE INDEX idx_ucca_tenant_domain ON ucca_assessments(tenant_id, domain); - --- Filter by risk level -CREATE INDEX idx_ucca_tenant_risk ON ucca_assessments(tenant_id, risk_level); - --- JSONB index for searching within triggered_rules -CREATE INDEX idx_ucca_triggered_rules ON ucca_assessments USING GIN (triggered_rules); - --- ============================================================================ --- Comments for Documentation --- ============================================================================ - -COMMENT ON TABLE ucca_assessments IS 'UCCA (Use-Case Compliance & Feasibility Advisor) assessments - stores evaluated AI use cases with GDPR compliance verdicts'; - -COMMENT ON COLUMN ucca_assessments.intake IS 'Full UseCaseIntake JSON including data types, purpose, automation level, hosting, etc.'; -COMMENT ON COLUMN ucca_assessments.use_case_text_stored IS 'Whether the raw use case description text is stored (opt-in)'; -COMMENT ON COLUMN ucca_assessments.use_case_text_hash IS 'SHA-256 hash of use case text for deduplication without storing raw text'; -COMMENT ON COLUMN ucca_assessments.feasibility IS 'Overall verdict: YES (low risk), CONDITIONAL (needs controls), NO (not allowed)'; -COMMENT ON COLUMN ucca_assessments.risk_score IS 'Numeric risk score 0-100 calculated from triggered rules'; -COMMENT ON COLUMN ucca_assessments.triggered_rules IS 'Array of rules that were triggered during evaluation'; -COMMENT ON COLUMN ucca_assessments.required_controls IS 'Array of controls/mitigations that must be implemented'; -COMMENT ON COLUMN ucca_assessments.recommended_architecture IS 'Array of recommended architecture patterns'; -COMMENT ON COLUMN ucca_assessments.forbidden_patterns IS 'Array of patterns that must NOT be used'; -COMMENT ON COLUMN ucca_assessments.example_matches IS 'Array of matching didactic examples'; -COMMENT ON COLUMN ucca_assessments.dsfa_recommended IS 'Whether a Data Protection Impact Assessment is recommended'; -COMMENT ON COLUMN ucca_assessments.art22_risk IS 'Whether there is risk under Art. 22 GDPR (automated individual decisions)'; -COMMENT ON COLUMN ucca_assessments.training_allowed IS 'Whether model training with the data is allowed'; -COMMENT ON COLUMN ucca_assessments.explanation_text IS 'LLM-generated explanation in German (optional)'; diff --git a/docs-src/ai-compliance-sdk/migrations/004_ucca_escalations.sql b/docs-src/ai-compliance-sdk/migrations/004_ucca_escalations.sql deleted file mode 100644 index 7857593..0000000 --- a/docs-src/ai-compliance-sdk/migrations/004_ucca_escalations.sql +++ /dev/null @@ -1,168 +0,0 @@ --- Migration 004: UCCA Escalation Workflow --- Implements E0-E3 escalation levels with DSB routing - --- ============================================================================ --- Escalation Levels (Reference) --- ============================================================================ --- E0: Auto-Approve - Only INFO rules triggered, Risk < 20 --- E1: Team-Lead Review - WARN rules OR Risk 20-40 --- E2: DSB Consultation - Art. 9 data OR Risk 40-60 OR DSFA recommended --- E3: DSB + Legal - BLOCK rules OR Risk > 60 OR Art. 22 risk - --- ============================================================================ --- Escalation Queue Table --- ============================================================================ - -CREATE TABLE IF NOT EXISTS ucca_escalations ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - assessment_id UUID NOT NULL REFERENCES ucca_assessments(id) ON DELETE CASCADE, - - -- Escalation Level - escalation_level VARCHAR(10) NOT NULL CHECK (escalation_level IN ('E0', 'E1', 'E2', 'E3')), - escalation_reason TEXT NOT NULL, - - -- Routing - assigned_to UUID, -- User ID of assignee (DSB, Team Lead, etc.) - assigned_role VARCHAR(50), -- Role for assignment (dsb, team_lead, legal) - assigned_at TIMESTAMPTZ, - - -- Status - status VARCHAR(30) NOT NULL DEFAULT 'pending' - CHECK (status IN ('pending', 'assigned', 'in_review', 'approved', 'rejected', 'returned')), - - -- Review - reviewer_id UUID, - reviewer_notes TEXT, - reviewed_at TIMESTAMPTZ, - - -- Decision - decision VARCHAR(20) CHECK (decision IN ('approve', 'reject', 'modify', 'escalate')), - decision_notes TEXT, - decision_at TIMESTAMPTZ, - - -- Conditions for approval - conditions JSONB DEFAULT '[]', -- Array of conditions that must be met - - -- Timestamps - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - due_date TIMESTAMPTZ, -- SLA deadline - - -- Notifications sent - notification_sent BOOLEAN DEFAULT FALSE, - notification_sent_at TIMESTAMPTZ -); - --- ============================================================================ --- Escalation History (Audit Trail) --- ============================================================================ - -CREATE TABLE IF NOT EXISTS ucca_escalation_history ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - escalation_id UUID NOT NULL REFERENCES ucca_escalations(id) ON DELETE CASCADE, - - -- What changed - action VARCHAR(50) NOT NULL, -- created, assigned, reviewed, decided, escalated, etc. - old_status VARCHAR(30), - new_status VARCHAR(30), - old_level VARCHAR(10), - new_level VARCHAR(10), - - -- Who and when - actor_id UUID NOT NULL, - actor_role VARCHAR(50), - notes TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- ============================================================================ --- DSB Assignment Pool --- ============================================================================ - -CREATE TABLE IF NOT EXISTS ucca_dsb_pool ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - user_id UUID NOT NULL, - user_name VARCHAR(255) NOT NULL, - user_email VARCHAR(255) NOT NULL, - role VARCHAR(50) NOT NULL DEFAULT 'dsb', -- dsb, deputy_dsb, legal - is_active BOOLEAN DEFAULT TRUE, - max_concurrent_reviews INT DEFAULT 10, - current_reviews INT DEFAULT 0, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - - UNIQUE(tenant_id, user_id) -); - --- ============================================================================ --- SLA Configuration per Escalation Level --- ============================================================================ - -CREATE TABLE IF NOT EXISTS ucca_escalation_sla ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, - escalation_level VARCHAR(10) NOT NULL CHECK (escalation_level IN ('E0', 'E1', 'E2', 'E3')), - - -- SLA settings - response_hours INT NOT NULL DEFAULT 24, -- Hours to first response - resolution_hours INT NOT NULL DEFAULT 72, -- Hours to resolution - - -- Notification settings - notify_on_creation BOOLEAN DEFAULT TRUE, - notify_on_approaching_sla BOOLEAN DEFAULT TRUE, - notify_on_sla_breach BOOLEAN DEFAULT TRUE, - approaching_sla_hours INT DEFAULT 8, -- Notify X hours before SLA breach - - -- Auto-escalation - auto_escalate_on_breach BOOLEAN DEFAULT FALSE, - - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - - UNIQUE(tenant_id, escalation_level) -); - --- ============================================================================ --- Indexes --- ============================================================================ - --- Fast lookup by tenant and status -CREATE INDEX idx_ucca_escalations_tenant_status ON ucca_escalations(tenant_id, status); - --- Fast lookup by assignee -CREATE INDEX idx_ucca_escalations_assigned ON ucca_escalations(assigned_to, status); - --- Fast lookup by assessment -CREATE INDEX idx_ucca_escalations_assessment ON ucca_escalations(assessment_id); - --- SLA monitoring (find escalations approaching or past due date) -CREATE INDEX idx_ucca_escalations_due ON ucca_escalations(due_date) WHERE status NOT IN ('approved', 'rejected'); - --- History lookup -CREATE INDEX idx_ucca_escalation_history_escalation ON ucca_escalation_history(escalation_id); - --- DSB pool lookup -CREATE INDEX idx_ucca_dsb_pool_tenant ON ucca_dsb_pool(tenant_id, is_active); - --- ============================================================================ --- Default SLA Values (inserted on first use) --- ============================================================================ - --- Note: These will be inserted per-tenant when needed via application logic --- E0: Auto-approve, no SLA --- E1: 24h response, 72h resolution --- E2: 8h response, 48h resolution --- E3: 4h response, 24h resolution (urgent) - --- ============================================================================ --- Comments --- ============================================================================ - -COMMENT ON TABLE ucca_escalations IS 'UCCA escalation queue for assessments requiring review'; -COMMENT ON COLUMN ucca_escalations.escalation_level IS 'E0=Auto, E1=Team, E2=DSB, E3=DSB+Legal'; -COMMENT ON COLUMN ucca_escalations.conditions IS 'JSON array of conditions required for approval'; -COMMENT ON TABLE ucca_escalation_history IS 'Audit trail of all escalation state changes'; -COMMENT ON TABLE ucca_dsb_pool IS 'Pool of DSB/Legal reviewers for assignment'; -COMMENT ON TABLE ucca_escalation_sla IS 'SLA configuration per escalation level per tenant'; diff --git a/docs-src/ai-compliance-sdk/migrations/005_roadmap_schema.sql b/docs-src/ai-compliance-sdk/migrations/005_roadmap_schema.sql deleted file mode 100644 index 4acfbbf..0000000 --- a/docs-src/ai-compliance-sdk/migrations/005_roadmap_schema.sql +++ /dev/null @@ -1,193 +0,0 @@ --- ============================================================================ --- Migration 005: Roadmap Schema --- Compliance Roadmap Management with Import Support --- ============================================================================ - --- Roadmaps table -CREATE TABLE IF NOT EXISTS roadmaps ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL, - - title VARCHAR(255) NOT NULL, - description TEXT, - version VARCHAR(50) DEFAULT '1.0', - - -- Links to other entities - assessment_id UUID REFERENCES ucca_assessments(id) ON DELETE SET NULL, - portfolio_id UUID, -- Will reference portfolio table when created - - -- Status tracking - status VARCHAR(50) DEFAULT 'draft', -- draft, active, completed, archived - total_items INT DEFAULT 0, - completed_items INT DEFAULT 0, - progress INT DEFAULT 0, -- Percentage 0-100 - - -- Timeline - start_date DATE, - target_date DATE, - - -- Audit - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID NOT NULL -); - --- Roadmap items table -CREATE TABLE IF NOT EXISTS roadmap_items ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - roadmap_id UUID NOT NULL REFERENCES roadmaps(id) ON DELETE CASCADE, - - -- Core fields - title VARCHAR(500) NOT NULL, - description TEXT, - category VARCHAR(50) DEFAULT 'TECHNICAL', -- TECHNICAL, ORGANIZATIONAL, PROCESSUAL, DOCUMENTATION, TRAINING - priority VARCHAR(50) DEFAULT 'MEDIUM', -- CRITICAL, HIGH, MEDIUM, LOW - status VARCHAR(50) DEFAULT 'PLANNED', -- PLANNED, IN_PROGRESS, BLOCKED, COMPLETED, DEFERRED - - -- Compliance mapping - control_id VARCHAR(100), -- e.g., "CTRL-AVV" - regulation_ref VARCHAR(255), -- e.g., "DSGVO Art. 28" - gap_id VARCHAR(100), -- e.g., "GAP_AVV_MISSING" - - -- Effort estimation - effort_days INT, - effort_hours INT, - estimated_cost INT, -- EUR - - -- Assignment - assignee_id UUID, - assignee_name VARCHAR(255), - department VARCHAR(255), - - -- Timeline - planned_start DATE, - planned_end DATE, - actual_start DATE, - actual_end DATE, - - -- Dependencies (JSONB arrays of UUIDs) - depends_on JSONB DEFAULT '[]', - blocked_by JSONB DEFAULT '[]', - - -- Evidence - evidence_required JSONB DEFAULT '[]', -- Array of strings - evidence_provided JSONB DEFAULT '[]', -- Array of strings - - -- Notes - notes TEXT, - risk_notes TEXT, - - -- Import metadata - source_row INT, - source_file VARCHAR(500), - - -- Ordering - sort_order INT DEFAULT 0, - - -- Audit - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- Import jobs table -CREATE TABLE IF NOT EXISTS roadmap_import_jobs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - roadmap_id UUID REFERENCES roadmaps(id) ON DELETE SET NULL, - - -- File info - filename VARCHAR(500) NOT NULL, - format VARCHAR(50) NOT NULL, -- EXCEL, CSV, JSON - file_size BIGINT, - content_type VARCHAR(255), - - -- Status - status VARCHAR(50) DEFAULT 'pending', -- pending, parsing, parsed, validating, completed, failed - error_message TEXT, - - -- Parsing results - total_rows INT DEFAULT 0, - valid_rows INT DEFAULT 0, - invalid_rows INT DEFAULT 0, - imported_items INT DEFAULT 0, - - -- Parsed items (before confirmation) - parsed_items JSONB DEFAULT '[]', - - -- Audit - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - completed_at TIMESTAMPTZ, - created_by UUID NOT NULL -); - --- ============================================================================ --- Indexes --- ============================================================================ - --- Roadmaps indexes -CREATE INDEX IF NOT EXISTS idx_roadmaps_tenant ON roadmaps(tenant_id); -CREATE INDEX IF NOT EXISTS idx_roadmaps_status ON roadmaps(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_roadmaps_assessment ON roadmaps(assessment_id) WHERE assessment_id IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_roadmaps_portfolio ON roadmaps(portfolio_id) WHERE portfolio_id IS NOT NULL; - --- Roadmap items indexes -CREATE INDEX IF NOT EXISTS idx_roadmap_items_roadmap ON roadmap_items(roadmap_id); -CREATE INDEX IF NOT EXISTS idx_roadmap_items_status ON roadmap_items(roadmap_id, status); -CREATE INDEX IF NOT EXISTS idx_roadmap_items_priority ON roadmap_items(roadmap_id, priority); -CREATE INDEX IF NOT EXISTS idx_roadmap_items_category ON roadmap_items(roadmap_id, category); -CREATE INDEX IF NOT EXISTS idx_roadmap_items_assignee ON roadmap_items(assignee_id) WHERE assignee_id IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_roadmap_items_control ON roadmap_items(control_id) WHERE control_id IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_roadmap_items_deadline ON roadmap_items(planned_end) WHERE planned_end IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_roadmap_items_sort ON roadmap_items(roadmap_id, sort_order); - --- Import jobs indexes -CREATE INDEX IF NOT EXISTS idx_import_jobs_tenant ON roadmap_import_jobs(tenant_id); -CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON roadmap_import_jobs(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_import_jobs_roadmap ON roadmap_import_jobs(roadmap_id) WHERE roadmap_id IS NOT NULL; - --- ============================================================================ --- Triggers for updated_at --- ============================================================================ - --- Trigger function (reuse if exists) -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ language 'plpgsql'; - --- Roadmaps trigger -DROP TRIGGER IF EXISTS update_roadmaps_updated_at ON roadmaps; -CREATE TRIGGER update_roadmaps_updated_at - BEFORE UPDATE ON roadmaps - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- Roadmap items trigger -DROP TRIGGER IF EXISTS update_roadmap_items_updated_at ON roadmap_items; -CREATE TRIGGER update_roadmap_items_updated_at - BEFORE UPDATE ON roadmap_items - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- Import jobs trigger -DROP TRIGGER IF EXISTS update_roadmap_import_jobs_updated_at ON roadmap_import_jobs; -CREATE TRIGGER update_roadmap_import_jobs_updated_at - BEFORE UPDATE ON roadmap_import_jobs - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- ============================================================================ --- Comments --- ============================================================================ - -COMMENT ON TABLE roadmaps IS 'Compliance implementation roadmaps'; -COMMENT ON TABLE roadmap_items IS 'Individual items/tasks in a compliance roadmap'; -COMMENT ON TABLE roadmap_import_jobs IS 'Track file imports for roadmap items'; - -COMMENT ON COLUMN roadmap_items.control_id IS 'Reference to controls catalog (e.g., CTRL-AVV)'; -COMMENT ON COLUMN roadmap_items.regulation_ref IS 'Reference to regulation article (e.g., DSGVO Art. 28)'; -COMMENT ON COLUMN roadmap_items.gap_id IS 'Reference to gap mapping (e.g., GAP_AVV_MISSING)'; -COMMENT ON COLUMN roadmap_items.depends_on IS 'Array of item IDs this item depends on'; -COMMENT ON COLUMN roadmap_items.blocked_by IS 'Array of item IDs currently blocking this item'; diff --git a/docs-src/ai-compliance-sdk/migrations/006_workshop_schema.sql b/docs-src/ai-compliance-sdk/migrations/006_workshop_schema.sql deleted file mode 100644 index 5890fc9..0000000 --- a/docs-src/ai-compliance-sdk/migrations/006_workshop_schema.sql +++ /dev/null @@ -1,207 +0,0 @@ --- ============================================================================ --- Migration 006: Workshop Session Schema --- Collaborative Compliance Workshop Sessions --- ============================================================================ - --- Workshop sessions table -CREATE TABLE IF NOT EXISTS workshop_sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL, - - -- Session info - title VARCHAR(255) NOT NULL, - description TEXT, - session_type VARCHAR(50) NOT NULL, -- 'ucca', 'dsfa', 'custom' - status VARCHAR(50) DEFAULT 'DRAFT', -- DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, CANCELLED - - -- Wizard configuration - wizard_schema VARCHAR(100), -- Reference to wizard schema version - current_step INT DEFAULT 1, - total_steps INT DEFAULT 10, - - -- Links to other entities - assessment_id UUID REFERENCES ucca_assessments(id) ON DELETE SET NULL, - roadmap_id UUID REFERENCES roadmaps(id) ON DELETE SET NULL, - portfolio_id UUID, -- Will reference portfolio table when created - - -- Scheduling - scheduled_start TIMESTAMPTZ, - scheduled_end TIMESTAMPTZ, - actual_start TIMESTAMPTZ, - actual_end TIMESTAMPTZ, - - -- Access control - join_code VARCHAR(10) NOT NULL UNIQUE, - require_auth BOOLEAN DEFAULT FALSE, - allow_anonymous BOOLEAN DEFAULT TRUE, - - -- Settings (JSONB) - settings JSONB DEFAULT '{}', - - -- Audit - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID NOT NULL -); - --- Workshop participants table -CREATE TABLE IF NOT EXISTS workshop_participants ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE, - user_id UUID, -- Null for anonymous participants - - -- Info - name VARCHAR(255) NOT NULL, - email VARCHAR(255), - role VARCHAR(50) DEFAULT 'STAKEHOLDER', -- FACILITATOR, EXPERT, STAKEHOLDER, OBSERVER - department VARCHAR(255), - - -- Status - is_active BOOLEAN DEFAULT TRUE, - last_active_at TIMESTAMPTZ, - joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - left_at TIMESTAMPTZ, - - -- Permissions - can_edit BOOLEAN DEFAULT TRUE, - can_comment BOOLEAN DEFAULT TRUE, - can_approve BOOLEAN DEFAULT FALSE -); - --- Workshop step progress table -CREATE TABLE IF NOT EXISTS workshop_step_progress ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE, - step_number INT NOT NULL, - - -- Status - status VARCHAR(50) DEFAULT 'pending', -- pending, in_progress, completed, skipped - progress INT DEFAULT 0, -- 0-100 - - -- Timestamps - started_at TIMESTAMPTZ, - completed_at TIMESTAMPTZ, - - -- Notes - notes TEXT, - - UNIQUE(session_id, step_number) -); - --- Workshop responses table -CREATE TABLE IF NOT EXISTS workshop_responses ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE, - participant_id UUID NOT NULL REFERENCES workshop_participants(id) ON DELETE CASCADE, - - -- Question reference - step_number INT NOT NULL, - field_id VARCHAR(100) NOT NULL, - - -- Response data - value JSONB, -- Can be any JSON type - value_type VARCHAR(50), -- string, boolean, array, number, object - - -- Status - status VARCHAR(50) DEFAULT 'SUBMITTED', -- PENDING, DRAFT, SUBMITTED, REVIEWED - - -- Review - reviewed_by UUID, - reviewed_at TIMESTAMPTZ, - review_notes TEXT, - - -- Audit - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - -- Unique constraint per participant per field - UNIQUE(session_id, participant_id, field_id) -); - --- Workshop comments table -CREATE TABLE IF NOT EXISTS workshop_comments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id UUID NOT NULL REFERENCES workshop_sessions(id) ON DELETE CASCADE, - participant_id UUID NOT NULL REFERENCES workshop_participants(id) ON DELETE CASCADE, - - -- Target (one of these should be set) - step_number INT, - field_id VARCHAR(100), - response_id UUID REFERENCES workshop_responses(id) ON DELETE CASCADE, - - -- Content - text TEXT NOT NULL, - is_resolved BOOLEAN DEFAULT FALSE, - - -- Audit - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- ============================================================================ --- Indexes --- ============================================================================ - --- Session indexes -CREATE INDEX IF NOT EXISTS idx_workshop_sessions_tenant ON workshop_sessions(tenant_id); -CREATE INDEX IF NOT EXISTS idx_workshop_sessions_status ON workshop_sessions(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_workshop_sessions_join_code ON workshop_sessions(join_code); -CREATE INDEX IF NOT EXISTS idx_workshop_sessions_assessment ON workshop_sessions(assessment_id) WHERE assessment_id IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_workshop_sessions_created_by ON workshop_sessions(created_by); - --- Participant indexes -CREATE INDEX IF NOT EXISTS idx_workshop_participants_session ON workshop_participants(session_id); -CREATE INDEX IF NOT EXISTS idx_workshop_participants_user ON workshop_participants(user_id) WHERE user_id IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_workshop_participants_active ON workshop_participants(session_id, is_active); - --- Step progress indexes -CREATE INDEX IF NOT EXISTS idx_workshop_step_progress_session ON workshop_step_progress(session_id); - --- Response indexes -CREATE INDEX IF NOT EXISTS idx_workshop_responses_session ON workshop_responses(session_id); -CREATE INDEX IF NOT EXISTS idx_workshop_responses_participant ON workshop_responses(participant_id); -CREATE INDEX IF NOT EXISTS idx_workshop_responses_step ON workshop_responses(session_id, step_number); -CREATE INDEX IF NOT EXISTS idx_workshop_responses_field ON workshop_responses(session_id, field_id); - --- Comment indexes -CREATE INDEX IF NOT EXISTS idx_workshop_comments_session ON workshop_comments(session_id); -CREATE INDEX IF NOT EXISTS idx_workshop_comments_response ON workshop_comments(response_id) WHERE response_id IS NOT NULL; - --- ============================================================================ --- Triggers --- ============================================================================ - --- Reuse existing update_updated_at_column function - --- Sessions trigger -DROP TRIGGER IF EXISTS update_workshop_sessions_updated_at ON workshop_sessions; -CREATE TRIGGER update_workshop_sessions_updated_at - BEFORE UPDATE ON workshop_sessions - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- Responses trigger -DROP TRIGGER IF EXISTS update_workshop_responses_updated_at ON workshop_responses; -CREATE TRIGGER update_workshop_responses_updated_at - BEFORE UPDATE ON workshop_responses - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- Comments trigger -DROP TRIGGER IF EXISTS update_workshop_comments_updated_at ON workshop_comments; -CREATE TRIGGER update_workshop_comments_updated_at - BEFORE UPDATE ON workshop_comments - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- ============================================================================ --- Comments --- ============================================================================ - -COMMENT ON TABLE workshop_sessions IS 'Collaborative compliance workshop sessions'; -COMMENT ON TABLE workshop_participants IS 'Participants in workshop sessions'; -COMMENT ON TABLE workshop_step_progress IS 'Progress tracking for each wizard step'; -COMMENT ON TABLE workshop_responses IS 'Participant responses to wizard questions'; -COMMENT ON TABLE workshop_comments IS 'Comments and discussions on responses'; - -COMMENT ON COLUMN workshop_sessions.join_code IS 'Code for participants to join the session'; -COMMENT ON COLUMN workshop_sessions.settings IS 'JSON settings (allow_back_navigation, require_all_responses, etc.)'; -COMMENT ON COLUMN workshop_responses.value IS 'JSON response value (can be any type)'; diff --git a/docs-src/ai-compliance-sdk/migrations/007_portfolio_schema.sql b/docs-src/ai-compliance-sdk/migrations/007_portfolio_schema.sql deleted file mode 100644 index 33249a1..0000000 --- a/docs-src/ai-compliance-sdk/migrations/007_portfolio_schema.sql +++ /dev/null @@ -1,267 +0,0 @@ --- ============================================================================ --- Migration 007: Portfolio Schema --- AI Use Case Portfolio Management with Merge Support --- ============================================================================ - --- Portfolios table -CREATE TABLE IF NOT EXISTS portfolios ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL, - - -- Info - name VARCHAR(255) NOT NULL, - description TEXT, - status VARCHAR(50) DEFAULT 'DRAFT', -- DRAFT, ACTIVE, REVIEW, APPROVED, ARCHIVED - - -- Organization - department VARCHAR(255), - business_unit VARCHAR(255), - owner VARCHAR(255), - owner_email VARCHAR(255), - - -- Aggregated metrics (computed) - total_assessments INT DEFAULT 0, - total_roadmaps INT DEFAULT 0, - total_workshops INT DEFAULT 0, - avg_risk_score DECIMAL(5,2) DEFAULT 0, - high_risk_count INT DEFAULT 0, - conditional_count INT DEFAULT 0, - approved_count INT DEFAULT 0, - compliance_score DECIMAL(5,2) DEFAULT 0, -- 0-100 - - -- Settings (JSONB) - settings JSONB DEFAULT '{}', - - -- Audit - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID NOT NULL, - approved_at TIMESTAMPTZ, - approved_by UUID -); - --- Portfolio items table (links portfolios to assessments, roadmaps, workshops) -CREATE TABLE IF NOT EXISTS portfolio_items ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - portfolio_id UUID NOT NULL REFERENCES portfolios(id) ON DELETE CASCADE, - item_type VARCHAR(50) NOT NULL, -- ASSESSMENT, ROADMAP, WORKSHOP, DOCUMENT - item_id UUID NOT NULL, - - -- Cached info from the linked item - title VARCHAR(500), - status VARCHAR(50), - risk_level VARCHAR(20), - risk_score INT DEFAULT 0, - feasibility VARCHAR(20), - - -- Ordering and categorization - sort_order INT DEFAULT 0, - tags JSONB DEFAULT '[]', - notes TEXT, - - -- Audit - added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - added_by UUID NOT NULL, - - -- Unique constraint: item can only be in portfolio once - UNIQUE(portfolio_id, item_id) -); - --- Portfolio activity log table -CREATE TABLE IF NOT EXISTS portfolio_activity ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - portfolio_id UUID NOT NULL REFERENCES portfolios(id) ON DELETE CASCADE, - - -- Activity info - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), - action VARCHAR(50) NOT NULL, -- added, removed, updated, merged, approved, submitted - item_type VARCHAR(50), - item_id UUID, - item_title VARCHAR(500), - user_id UUID NOT NULL, - - -- Additional details - details JSONB -); - --- ============================================================================ --- Indexes --- ============================================================================ - --- Portfolio indexes -CREATE INDEX IF NOT EXISTS idx_portfolios_tenant ON portfolios(tenant_id); -CREATE INDEX IF NOT EXISTS idx_portfolios_tenant_status ON portfolios(tenant_id, status); -CREATE INDEX IF NOT EXISTS idx_portfolios_department ON portfolios(tenant_id, department) WHERE department IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_portfolios_business_unit ON portfolios(tenant_id, business_unit) WHERE business_unit IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_portfolios_owner ON portfolios(owner) WHERE owner IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_portfolios_created_by ON portfolios(created_by); -CREATE INDEX IF NOT EXISTS idx_portfolios_risk_score ON portfolios(avg_risk_score); - --- Portfolio item indexes -CREATE INDEX IF NOT EXISTS idx_portfolio_items_portfolio ON portfolio_items(portfolio_id); -CREATE INDEX IF NOT EXISTS idx_portfolio_items_type ON portfolio_items(portfolio_id, item_type); -CREATE INDEX IF NOT EXISTS idx_portfolio_items_item ON portfolio_items(item_id); -CREATE INDEX IF NOT EXISTS idx_portfolio_items_risk ON portfolio_items(portfolio_id, risk_level); -CREATE INDEX IF NOT EXISTS idx_portfolio_items_feasibility ON portfolio_items(portfolio_id, feasibility); -CREATE INDEX IF NOT EXISTS idx_portfolio_items_sort ON portfolio_items(portfolio_id, sort_order); - --- Activity indexes -CREATE INDEX IF NOT EXISTS idx_portfolio_activity_portfolio ON portfolio_activity(portfolio_id); -CREATE INDEX IF NOT EXISTS idx_portfolio_activity_timestamp ON portfolio_activity(portfolio_id, timestamp DESC); -CREATE INDEX IF NOT EXISTS idx_portfolio_activity_user ON portfolio_activity(user_id); - --- ============================================================================ --- Triggers --- ============================================================================ - --- Reuse existing update_updated_at_column function - --- Portfolios trigger -DROP TRIGGER IF EXISTS update_portfolios_updated_at ON portfolios; -CREATE TRIGGER update_portfolios_updated_at - BEFORE UPDATE ON portfolios - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- ============================================================================ --- Functions for Metrics Calculation --- ============================================================================ - --- Function to recalculate portfolio metrics -CREATE OR REPLACE FUNCTION recalculate_portfolio_metrics(p_portfolio_id UUID) -RETURNS VOID AS $$ -DECLARE - v_total_assessments INT; - v_total_roadmaps INT; - v_total_workshops INT; - v_avg_risk DECIMAL(5,2); - v_high_risk INT; - v_conditional INT; - v_approved INT; - v_compliance DECIMAL(5,2); -BEGIN - -- Count by type - SELECT COUNT(*) INTO v_total_assessments - FROM portfolio_items - WHERE portfolio_id = p_portfolio_id AND item_type = 'ASSESSMENT'; - - SELECT COUNT(*) INTO v_total_roadmaps - FROM portfolio_items - WHERE portfolio_id = p_portfolio_id AND item_type = 'ROADMAP'; - - SELECT COUNT(*) INTO v_total_workshops - FROM portfolio_items - WHERE portfolio_id = p_portfolio_id AND item_type = 'WORKSHOP'; - - -- Calculate risk metrics - SELECT COALESCE(AVG(risk_score), 0) INTO v_avg_risk - FROM portfolio_items - WHERE portfolio_id = p_portfolio_id AND item_type = 'ASSESSMENT'; - - SELECT COUNT(*) INTO v_high_risk - FROM portfolio_items - WHERE portfolio_id = p_portfolio_id - AND item_type = 'ASSESSMENT' - AND risk_level IN ('HIGH', 'UNACCEPTABLE'); - - SELECT COUNT(*) INTO v_conditional - FROM portfolio_items - WHERE portfolio_id = p_portfolio_id - AND item_type = 'ASSESSMENT' - AND feasibility = 'CONDITIONAL'; - - SELECT COUNT(*) INTO v_approved - FROM portfolio_items - WHERE portfolio_id = p_portfolio_id - AND item_type = 'ASSESSMENT' - AND feasibility = 'YES'; - - -- Calculate compliance score - IF v_total_assessments > 0 THEN - v_compliance := (v_approved::DECIMAL / v_total_assessments) * 100; - ELSE - v_compliance := 0; - END IF; - - -- Update portfolio - UPDATE portfolios SET - total_assessments = v_total_assessments, - total_roadmaps = v_total_roadmaps, - total_workshops = v_total_workshops, - avg_risk_score = v_avg_risk, - high_risk_count = v_high_risk, - conditional_count = v_conditional, - approved_count = v_approved, - compliance_score = v_compliance, - updated_at = NOW() - WHERE id = p_portfolio_id; -END; -$$ LANGUAGE plpgsql; - --- Trigger function to auto-update metrics on item changes -CREATE OR REPLACE FUNCTION portfolio_items_metrics_trigger() -RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'DELETE' THEN - PERFORM recalculate_portfolio_metrics(OLD.portfolio_id); - RETURN OLD; - ELSE - PERFORM recalculate_portfolio_metrics(NEW.portfolio_id); - RETURN NEW; - END IF; -END; -$$ LANGUAGE plpgsql; - --- Trigger for auto metrics update -DROP TRIGGER IF EXISTS trg_portfolio_items_metrics ON portfolio_items; -CREATE TRIGGER trg_portfolio_items_metrics - AFTER INSERT OR UPDATE OR DELETE ON portfolio_items - FOR EACH ROW EXECUTE FUNCTION portfolio_items_metrics_trigger(); - --- ============================================================================ --- Views --- ============================================================================ - --- View for portfolio summary with counts -CREATE OR REPLACE VIEW portfolio_summary_view AS -SELECT - p.id, - p.tenant_id, - p.name, - p.description, - p.status, - p.department, - p.business_unit, - p.owner, - p.total_assessments, - p.total_roadmaps, - p.total_workshops, - p.avg_risk_score, - p.high_risk_count, - p.conditional_count, - p.approved_count, - p.compliance_score, - p.created_at, - p.updated_at, - (p.total_assessments + p.total_roadmaps + p.total_workshops) as total_items, - CASE - WHEN p.high_risk_count > 0 THEN 'CRITICAL' - WHEN p.conditional_count > p.approved_count THEN 'WARNING' - ELSE 'GOOD' - END as health_status -FROM portfolios p; - --- ============================================================================ --- Comments --- ============================================================================ - -COMMENT ON TABLE portfolios IS 'AI use case portfolios for grouping and managing multiple assessments'; -COMMENT ON TABLE portfolio_items IS 'Items linked to portfolios (assessments, roadmaps, workshops)'; -COMMENT ON TABLE portfolio_activity IS 'Activity log for portfolio changes'; - -COMMENT ON COLUMN portfolios.compliance_score IS 'Percentage of assessments with YES feasibility (0-100)'; -COMMENT ON COLUMN portfolios.avg_risk_score IS 'Average risk score across all assessments in portfolio'; -COMMENT ON COLUMN portfolio_items.item_type IS 'Type of linked item: ASSESSMENT, ROADMAP, WORKSHOP, DOCUMENT'; -COMMENT ON COLUMN portfolio_items.sort_order IS 'Custom ordering within the portfolio'; - -COMMENT ON FUNCTION recalculate_portfolio_metrics(UUID) IS 'Recalculates aggregated metrics for a portfolio'; diff --git a/docs-src/ai-content-generator/app/models/__init__.py b/docs-src/ai-content-generator/app/models/__init__.py deleted file mode 100644 index 330c5e1..0000000 --- a/docs-src/ai-content-generator/app/models/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Models Package -Data Models for AI Content Generator -""" - -from .generation_job import GenerationJob, JobStatus - -__all__ = [ - "GenerationJob", - "JobStatus" -] diff --git a/docs-src/ai-content-generator/app/models/generation_job.py b/docs-src/ai-content-generator/app/models/generation_job.py deleted file mode 100644 index 03c5db0..0000000 --- a/docs-src/ai-content-generator/app/models/generation_job.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Generation Job Model -Tracking für Content-Generierungs-Jobs -""" - -from enum import Enum -from datetime import datetime -from typing import Optional, Dict, Any -import uuid - - -class JobStatus(str, Enum): - """Job Status""" - PENDING = "pending" - PROCESSING = "processing" - COMPLETED = "completed" - FAILED = "failed" - - -class GenerationJob: - """Content-Generierungs-Job""" - - def __init__( - self, - topic: str, - description: Optional[str] = None, - target_grade: Optional[str] = None, - material_count: int = 0 - ): - self.job_id = str(uuid.uuid4()) - self.topic = topic - self.description = description - self.target_grade = target_grade - self.material_count = material_count - - self.status = JobStatus.PENDING - self.progress = 0 - self.message = "Job created" - self.result: Optional[Dict[str, Any]] = None - self.error: Optional[str] = None - - self.created_at = datetime.utcnow() - self.updated_at = datetime.utcnow() - - def update_progress(self, progress: int, message: str): - """Update Job Progress""" - self.progress = progress - self.message = message - self.status = JobStatus.PROCESSING - self.updated_at = datetime.utcnow() - - def complete(self, result: Dict[str, Any]): - """Mark Job as Completed""" - self.status = JobStatus.COMPLETED - self.progress = 100 - self.message = "Content generation completed" - self.result = result - self.updated_at = datetime.utcnow() - - def fail(self, error: str): - """Mark Job as Failed""" - self.status = JobStatus.FAILED - self.message = "Content generation failed" - self.error = error - self.updated_at = datetime.utcnow() - - def to_dict(self) -> Dict[str, Any]: - """Convert to dict""" - return { - "job_id": self.job_id, - "topic": self.topic, - "description": self.description, - "target_grade": self.target_grade, - "material_count": self.material_count, - "status": self.status.value, - "progress": self.progress, - "message": self.message, - "result": self.result, - "error": self.error, - "created_at": self.created_at.isoformat(), - "updated_at": self.updated_at.isoformat() - } - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "GenerationJob": - """Create from dict""" - job = cls( - topic=data["topic"], - description=data.get("description"), - target_grade=data.get("target_grade"), - material_count=data.get("material_count", 0) - ) - job.job_id = data["job_id"] - job.status = JobStatus(data["status"]) - job.progress = data["progress"] - job.message = data["message"] - job.result = data.get("result") - job.error = data.get("error") - job.created_at = datetime.fromisoformat(data["created_at"]) - job.updated_at = datetime.fromisoformat(data["updated_at"]) - return job diff --git a/docs-src/backend/alerts_agent/models/__init__.py b/docs-src/backend/alerts_agent/models/__init__.py deleted file mode 100644 index bf1a48f..0000000 --- a/docs-src/backend/alerts_agent/models/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Alert Agent Models.""" - -from .alert_item import AlertItem, AlertSource, AlertStatus -from .relevance_profile import RelevanceProfile, PriorityItem - -__all__ = [ - "AlertItem", - "AlertSource", - "AlertStatus", - "RelevanceProfile", - "PriorityItem", -] diff --git a/docs-src/backend/alerts_agent/models/alert_item.py b/docs-src/backend/alerts_agent/models/alert_item.py deleted file mode 100644 index 0cb8ae0..0000000 --- a/docs-src/backend/alerts_agent/models/alert_item.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -AlertItem Model. - -Repräsentiert einen einzelnen Alert aus Google Alerts (RSS oder Email). -""" - -from dataclasses import dataclass, field -from datetime import datetime -from enum import Enum -from typing import Optional -import hashlib -import uuid - - -class AlertSource(str, Enum): - """Quelle des Alerts.""" - GOOGLE_ALERTS_RSS = "google_alerts_rss" - GOOGLE_ALERTS_EMAIL = "google_alerts_email" - MANUAL = "manual" - - -class AlertStatus(str, Enum): - """Verarbeitungsstatus des Alerts.""" - NEW = "new" - PROCESSED = "processed" - DUPLICATE = "duplicate" - SCORED = "scored" - REVIEWED = "reviewed" - ARCHIVED = "archived" - - -@dataclass -class AlertItem: - """Ein einzelner Alert-Eintrag.""" - - # Identifikation - id: str = field(default_factory=lambda: str(uuid.uuid4())) - - # Quelle - source: AlertSource = AlertSource.GOOGLE_ALERTS_RSS - topic_label: str = "" # z.B. "Schulrecht Bayern" - feed_url: Optional[str] = None - - # Content - title: str = "" - url: str = "" - snippet: str = "" - article_text: Optional[str] = None - - # Metadaten - lang: str = "de" - published_at: Optional[datetime] = None - fetched_at: datetime = field(default_factory=datetime.utcnow) - - # Deduplication - canonical_url: Optional[str] = None - url_hash: Optional[str] = None - content_hash: Optional[str] = None # SimHash für fuzzy matching - - # Verarbeitung - status: AlertStatus = AlertStatus.NEW - cluster_id: Optional[str] = None - - # Relevanz (nach Scoring) - relevance_score: Optional[float] = None # 0.0 - 1.0 - relevance_decision: Optional[str] = None # KEEP, DROP, REVIEW - relevance_reasons: list = field(default_factory=list) - relevance_summary: Optional[str] = None - - def __post_init__(self): - """Berechne Hashes nach Initialisierung.""" - if not self.url_hash and self.url: - self.url_hash = self._compute_url_hash() - if not self.canonical_url and self.url: - self.canonical_url = self._normalize_url(self.url) - - def _compute_url_hash(self) -> str: - """Berechne SHA256 Hash der URL.""" - normalized = self._normalize_url(self.url) - return hashlib.sha256(normalized.encode()).hexdigest()[:16] - - def _normalize_url(self, url: str) -> str: - """Normalisiere URL für Deduplizierung.""" - # Entferne Tracking-Parameter - import urllib.parse - parsed = urllib.parse.urlparse(url) - - # Google News Redirect auflösen - if "news.google.com" in parsed.netloc and "/articles/" in parsed.path: - # news.google.com URLs enthalten die echte URL base64-kodiert - # Hier nur Basic-Handling - echte Auflösung komplexer - pass - - # Tracking-Parameter entfernen - tracking_params = { - "utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term", - "fbclid", "gclid", "ref", "source" - } - - query_params = urllib.parse.parse_qs(parsed.query) - cleaned_params = {k: v for k, v in query_params.items() - if k.lower() not in tracking_params} - - cleaned_query = urllib.parse.urlencode(cleaned_params, doseq=True) - - # Rekonstruiere URL ohne Fragment - normalized = urllib.parse.urlunparse(( - parsed.scheme, - parsed.netloc.lower(), - parsed.path.rstrip("/"), - parsed.params, - cleaned_query, - "" # No fragment - )) - - return normalized - - def compute_content_hash(self, text: Optional[str] = None) -> str: - """ - Berechne SimHash des Inhalts für Fuzzy-Matching. - - SimHash erlaubt es, ähnliche Texte zu erkennen, auch wenn sie - sich leicht unterscheiden (z.B. verschiedene Quellen zum selben Thema). - """ - from ..processing.dedup import compute_simhash - - content = text or self.article_text or self.snippet or self.title - if content: - self.content_hash = compute_simhash(content) - return self.content_hash or "" - - def to_dict(self) -> dict: - """Konvertiere zu Dictionary für JSON/DB.""" - return { - "id": self.id, - "source": self.source.value, - "topic_label": self.topic_label, - "feed_url": self.feed_url, - "title": self.title, - "url": self.url, - "snippet": self.snippet, - "article_text": self.article_text, - "lang": self.lang, - "published_at": self.published_at.isoformat() if self.published_at else None, - "fetched_at": self.fetched_at.isoformat() if self.fetched_at else None, - "canonical_url": self.canonical_url, - "url_hash": self.url_hash, - "content_hash": self.content_hash, - "status": self.status.value, - "cluster_id": self.cluster_id, - "relevance_score": self.relevance_score, - "relevance_decision": self.relevance_decision, - "relevance_reasons": self.relevance_reasons, - "relevance_summary": self.relevance_summary, - } - - @classmethod - def from_dict(cls, data: dict) -> "AlertItem": - """Erstelle AlertItem aus Dictionary.""" - # Parse Enums - if "source" in data and isinstance(data["source"], str): - data["source"] = AlertSource(data["source"]) - if "status" in data and isinstance(data["status"], str): - data["status"] = AlertStatus(data["status"]) - - # Parse Timestamps - for field_name in ["published_at", "fetched_at"]: - if field_name in data and isinstance(data[field_name], str): - data[field_name] = datetime.fromisoformat(data[field_name]) - - return cls(**data) - - def __repr__(self) -> str: - return f"AlertItem(id={self.id[:8]}, title='{self.title[:50]}...', status={self.status.value})" diff --git a/docs-src/backend/alerts_agent/models/relevance_profile.py b/docs-src/backend/alerts_agent/models/relevance_profile.py deleted file mode 100644 index 9e9e961..0000000 --- a/docs-src/backend/alerts_agent/models/relevance_profile.py +++ /dev/null @@ -1,288 +0,0 @@ -""" -RelevanceProfile Model. - -Definiert das Relevanzprofil eines Nutzers für die Alerts-Filterung. -Lernt über Zeit durch Feedback. -""" - -from dataclasses import dataclass, field -from datetime import datetime -from typing import Optional -import uuid - - -@dataclass -class PriorityItem: - """Ein Prioritäts-Thema im Profil.""" - label: str # z.B. "Inklusion", "Datenschutz Schule" - weight: float = 0.5 # 0.0 - 1.0, höher = wichtiger - keywords: list = field(default_factory=list) # Zusätzliche Keywords - description: Optional[str] = None # Kontext für LLM - - def to_dict(self) -> dict: - return { - "label": self.label, - "weight": self.weight, - "keywords": self.keywords, - "description": self.description, - } - - @classmethod - def from_dict(cls, data: dict) -> "PriorityItem": - return cls(**data) - - -@dataclass -class RelevanceProfile: - """ - Nutzerprofil für Relevanz-Scoring. - - Das Profil wird verwendet, um Alerts auf Relevanz zu prüfen. - Es enthält: - - Prioritäten: Themen die wichtig sind (mit Gewichtung) - - Ausschlüsse: Themen die ignoriert werden sollen - - Positive Beispiele: URLs/Titel die relevant waren - - Negative Beispiele: URLs/Titel die irrelevant waren - - Policies: Zusätzliche Regeln (z.B. nur deutsche Quellen) - """ - - # Identifikation - id: str = field(default_factory=lambda: str(uuid.uuid4())) - user_id: Optional[str] = None # Falls benutzerspezifisch - - # Relevanz-Kriterien - priorities: list = field(default_factory=list) # List[PriorityItem] - exclusions: list = field(default_factory=list) # Keywords zum Ausschließen - - # Beispiele für Few-Shot Learning - positive_examples: list = field(default_factory=list) # Relevante Alerts - negative_examples: list = field(default_factory=list) # Irrelevante Alerts - - # Policies - policies: dict = field(default_factory=dict) - - # Metadaten - created_at: datetime = field(default_factory=datetime.utcnow) - updated_at: datetime = field(default_factory=datetime.utcnow) - - # Statistiken - total_scored: int = 0 - total_kept: int = 0 - total_dropped: int = 0 - accuracy_estimate: Optional[float] = None # Geschätzte Genauigkeit - - def add_priority(self, label: str, weight: float = 0.5, **kwargs) -> None: - """Füge ein Prioritäts-Thema hinzu.""" - self.priorities.append(PriorityItem( - label=label, - weight=weight, - **kwargs - )) - self.updated_at = datetime.utcnow() - - def add_exclusion(self, keyword: str) -> None: - """Füge ein Ausschluss-Keyword hinzu.""" - if keyword not in self.exclusions: - self.exclusions.append(keyword) - self.updated_at = datetime.utcnow() - - def add_positive_example(self, title: str, url: str, reason: str = "") -> None: - """Füge ein positives Beispiel hinzu (für Few-Shot Learning).""" - self.positive_examples.append({ - "title": title, - "url": url, - "reason": reason, - "added_at": datetime.utcnow().isoformat(), - }) - # Begrenze auf letzte 20 Beispiele - self.positive_examples = self.positive_examples[-20:] - self.updated_at = datetime.utcnow() - - def add_negative_example(self, title: str, url: str, reason: str = "") -> None: - """Füge ein negatives Beispiel hinzu.""" - self.negative_examples.append({ - "title": title, - "url": url, - "reason": reason, - "added_at": datetime.utcnow().isoformat(), - }) - # Begrenze auf letzte 20 Beispiele - self.negative_examples = self.negative_examples[-20:] - self.updated_at = datetime.utcnow() - - def update_from_feedback(self, alert_title: str, alert_url: str, - is_relevant: bool, reason: str = "") -> None: - """ - Aktualisiere Profil basierend auf Nutzer-Feedback. - - Args: - alert_title: Titel des Alerts - alert_url: URL des Alerts - is_relevant: True wenn der Nutzer den Alert als relevant markiert hat - reason: Optional - Grund für die Entscheidung - """ - if is_relevant: - self.add_positive_example(alert_title, alert_url, reason) - self.total_kept += 1 - else: - self.add_negative_example(alert_title, alert_url, reason) - self.total_dropped += 1 - - self.total_scored += 1 - - # Aktualisiere Accuracy-Schätzung (vereinfacht) - if self.total_scored > 10: - # Hier könnte eine komplexere Berechnung erfolgen - # basierend auf Vergleich von Vorhersage vs. tatsächlichem Feedback - pass - - def get_prompt_context(self) -> str: - """ - Generiere Kontext für LLM-Prompt. - - Dieser Text wird in den System-Prompt des Relevanz-Scorers eingefügt. - """ - lines = ["## Relevanzprofil des Nutzers\n"] - - # Prioritäten - if self.priorities: - lines.append("### Prioritäten (Themen von Interesse):") - for p in self.priorities: - if isinstance(p, dict): - p = PriorityItem.from_dict(p) - weight_label = "Sehr wichtig" if p.weight > 0.7 else "Wichtig" if p.weight > 0.4 else "Interessant" - lines.append(f"- **{p.label}** ({weight_label})") - if p.description: - lines.append(f" {p.description}") - if p.keywords: - lines.append(f" Keywords: {', '.join(p.keywords)}") - lines.append("") - - # Ausschlüsse - if self.exclusions: - lines.append("### Ausschlüsse (ignorieren):") - lines.append(f"Themen mit diesen Keywords: {', '.join(self.exclusions)}") - lines.append("") - - # Positive Beispiele - if self.positive_examples: - lines.append("### Beispiele für relevante Alerts:") - for ex in self.positive_examples[-5:]: # Letzte 5 - lines.append(f"- \"{ex['title']}\"") - if ex.get("reason"): - lines.append(f" Grund: {ex['reason']}") - lines.append("") - - # Negative Beispiele - if self.negative_examples: - lines.append("### Beispiele für irrelevante Alerts:") - for ex in self.negative_examples[-5:]: # Letzte 5 - lines.append(f"- \"{ex['title']}\"") - if ex.get("reason"): - lines.append(f" Grund: {ex['reason']}") - lines.append("") - - # Policies - if self.policies: - lines.append("### Zusätzliche Regeln:") - for key, value in self.policies.items(): - lines.append(f"- {key}: {value}") - - return "\n".join(lines) - - def to_dict(self) -> dict: - """Konvertiere zu Dictionary.""" - return { - "id": self.id, - "user_id": self.user_id, - "priorities": [p.to_dict() if isinstance(p, PriorityItem) else p - for p in self.priorities], - "exclusions": self.exclusions, - "positive_examples": self.positive_examples, - "negative_examples": self.negative_examples, - "policies": self.policies, - "created_at": self.created_at.isoformat(), - "updated_at": self.updated_at.isoformat(), - "total_scored": self.total_scored, - "total_kept": self.total_kept, - "total_dropped": self.total_dropped, - "accuracy_estimate": self.accuracy_estimate, - } - - @classmethod - def from_dict(cls, data: dict) -> "RelevanceProfile": - """Erstelle RelevanceProfile aus Dictionary.""" - # Parse Timestamps - for field_name in ["created_at", "updated_at"]: - if field_name in data and isinstance(data[field_name], str): - data[field_name] = datetime.fromisoformat(data[field_name]) - - # Parse Priorities - if "priorities" in data: - data["priorities"] = [ - PriorityItem.from_dict(p) if isinstance(p, dict) else p - for p in data["priorities"] - ] - - return cls(**data) - - @classmethod - def create_default_education_profile(cls) -> "RelevanceProfile": - """ - Erstelle ein Standard-Profil für Bildungsthemen. - - Dieses Profil ist für Lehrkräfte/Schulpersonal optimiert. - """ - profile = cls() - - # Bildungs-relevante Prioritäten - profile.add_priority( - "Inklusion", - weight=0.9, - keywords=["inklusiv", "Förderbedarf", "Behinderung", "Barrierefreiheit"], - description="Inklusive Bildung, Förderschulen, Nachteilsausgleich" - ) - profile.add_priority( - "Datenschutz Schule", - weight=0.85, - keywords=["DSGVO", "Schülerfotos", "Einwilligung", "personenbezogene Daten"], - description="DSGVO in Schulen, Datenschutz bei Klassenfotos" - ) - profile.add_priority( - "Schulrecht Bayern", - weight=0.8, - keywords=["BayEUG", "Schulordnung", "Kultusministerium", "Bayern"], - description="Bayerisches Schulrecht, Verordnungen" - ) - profile.add_priority( - "Digitalisierung Schule", - weight=0.7, - keywords=["DigitalPakt", "Tablet-Klasse", "Lernplattform"], - description="Digitale Medien im Unterricht" - ) - profile.add_priority( - "Elternarbeit", - weight=0.6, - keywords=["Elternbeirat", "Elternabend", "Kommunikation"], - description="Zusammenarbeit mit Eltern" - ) - - # Standard-Ausschlüsse - profile.exclusions = [ - "Stellenanzeige", - "Praktikum gesucht", - "Werbung", - "Pressemitteilung", # Oft generisch - ] - - # Policies - profile.policies = { - "prefer_german_sources": True, - "max_age_days": 30, # Ältere Alerts ignorieren - "min_content_length": 100, # Sehr kurze Snippets ignorieren - } - - return profile - - def __repr__(self) -> str: - return f"RelevanceProfile(id={self.id[:8]}, priorities={len(self.priorities)}, examples={len(self.positive_examples) + len(self.negative_examples)})" diff --git a/docs-src/backend/llm_gateway/models/__init__.py b/docs-src/backend/llm_gateway/models/__init__.py deleted file mode 100644 index 16b6a87..0000000 --- a/docs-src/backend/llm_gateway/models/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Pydantic Models für OpenAI-kompatible API. -""" - -from .chat import ( - ChatMessage, - ChatCompletionRequest, - ChatCompletionResponse, - ChatCompletionChunk, - ChatChoice, - ChatChoiceDelta, - Usage, - ToolCall, - FunctionCall, - Tool, - ToolFunction, -) - -__all__ = [ - "ChatMessage", - "ChatCompletionRequest", - "ChatCompletionResponse", - "ChatCompletionChunk", - "ChatChoice", - "ChatChoiceDelta", - "Usage", - "ToolCall", - "FunctionCall", - "Tool", - "ToolFunction", -] diff --git a/docs-src/backend/llm_gateway/models/chat.py b/docs-src/backend/llm_gateway/models/chat.py deleted file mode 100644 index 2b99448..0000000 --- a/docs-src/backend/llm_gateway/models/chat.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -OpenAI-kompatible Chat Completion Models. - -Basiert auf OpenAI API Spezifikation: -https://platform.openai.com/docs/api-reference/chat/create -""" - -from __future__ import annotations -from typing import Optional, Literal, Any, Union, List, Dict -from pydantic import BaseModel, Field -import time -import uuid - - -class FunctionCall(BaseModel): - """Function call in einer Tool-Anfrage.""" - name: str - arguments: str # JSON string - - -class ToolCall(BaseModel): - """Tool Call vom Modell.""" - id: str = Field(default_factory=lambda: f"call_{uuid.uuid4().hex[:12]}") - type: Literal["function"] = "function" - function: FunctionCall - - -class ChatMessage(BaseModel): - """Eine Nachricht im Chat.""" - role: Literal["system", "user", "assistant", "tool"] - content: Optional[str] = None - name: Optional[str] = None - tool_call_id: Optional[str] = None - tool_calls: Optional[list[ToolCall]] = None - - -class ToolFunction(BaseModel): - """Definition einer Tool-Funktion.""" - name: str - description: Optional[str] = None - parameters: dict[str, Any] = Field(default_factory=dict) - - -class Tool(BaseModel): - """Tool-Definition für Function Calling.""" - type: Literal["function"] = "function" - function: ToolFunction - - -class RequestMetadata(BaseModel): - """Zusätzliche Metadaten für die Anfrage.""" - playbook_id: Optional[str] = None - tenant_id: Optional[str] = None - user_id: Optional[str] = None - - -class ChatCompletionRequest(BaseModel): - """Request für Chat Completions.""" - model: str - messages: list[ChatMessage] - stream: bool = False - temperature: Optional[float] = Field(default=0.7, ge=0, le=2) - top_p: Optional[float] = Field(default=1.0, ge=0, le=1) - max_tokens: Optional[int] = Field(default=None, ge=1) - stop: Optional[Union[List[str], str]] = None - presence_penalty: Optional[float] = Field(default=0, ge=-2, le=2) - frequency_penalty: Optional[float] = Field(default=0, ge=-2, le=2) - user: Optional[str] = None - tools: Optional[list[Tool]] = None - tool_choice: Optional[Union[str, Dict[str, Any]]] = None - metadata: Optional[RequestMetadata] = None - - -class ChatChoice(BaseModel): - """Ein Choice in der Response.""" - index: int = 0 - message: ChatMessage - finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter"]] = None - - -class ChatChoiceDelta(BaseModel): - """Delta für Streaming Response.""" - role: Optional[str] = None - content: Optional[str] = None - tool_calls: Optional[list[ToolCall]] = None - - -class StreamChoice(BaseModel): - """Choice in Streaming Response.""" - index: int = 0 - delta: ChatChoiceDelta - finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter"]] = None - - -class Usage(BaseModel): - """Token Usage Statistiken.""" - prompt_tokens: int = 0 - completion_tokens: int = 0 - total_tokens: int = 0 - - -class ChatCompletionResponse(BaseModel): - """Response für Chat Completions (non-streaming).""" - id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex[:12]}") - object: Literal["chat.completion"] = "chat.completion" - created: int = Field(default_factory=lambda: int(time.time())) - model: str - choices: list[ChatChoice] - usage: Optional[Usage] = None - - -class ChatCompletionChunk(BaseModel): - """Chunk für Streaming Response.""" - id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex[:12]}") - object: Literal["chat.completion.chunk"] = "chat.completion.chunk" - created: int = Field(default_factory=lambda: int(time.time())) - model: str - choices: list[StreamChoice] - - -# Model Info -class ModelInfo(BaseModel): - """Information über ein verfügbares Modell.""" - id: str - object: Literal["model"] = "model" - created: int = Field(default_factory=lambda: int(time.time())) - owned_by: str = "breakpilot" - description: Optional[str] = None - context_length: int = 8192 - - -class ModelListResponse(BaseModel): - """Response für /v1/models.""" - object: Literal["list"] = "list" - data: list[ModelInfo] diff --git a/docs-src/backend/migrations/add_abitur_docs_tables.sql b/docs-src/backend/migrations/add_abitur_docs_tables.sql deleted file mode 100644 index b72ae46..0000000 --- a/docs-src/backend/migrations/add_abitur_docs_tables.sql +++ /dev/null @@ -1,155 +0,0 @@ --- ============================================================================ --- Abitur Documents Migration --- ============================================================================ --- Creates tables for storing Abitur documents (NiBiS, etc.) persistently. --- Run with: psql -h localhost -U breakpilot -d breakpilot_dev -f add_abitur_docs_tables.sql --- --- Tables created: --- 1. abitur_dokumente - Main document metadata --- 2. abitur_dokumente_chunks - Text chunks for RAG (optional, Qdrant primary) --- ============================================================================ - --- ============================================================================ --- ENUMS --- ============================================================================ - -DO $$ BEGIN - CREATE TYPE bundesland_enum AS ENUM ( - 'niedersachsen', 'bayern', 'baden_wuerttemberg', 'nordrhein_westfalen', - 'hessen', 'sachsen', 'thueringen', 'berlin', 'hamburg', - 'schleswig_holstein', 'bremen', 'brandenburg', 'mecklenburg_vorpommern', - 'sachsen_anhalt', 'rheinland_pfalz', 'saarland' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE abitur_fach_enum AS ENUM ( - 'deutsch', 'englisch', 'mathematik', 'biologie', 'chemie', 'physik', - 'geschichte', 'erdkunde', 'politik_wirtschaft', 'franzoesisch', 'spanisch', - 'latein', 'griechisch', 'kunst', 'musik', 'sport', 'informatik', - 'ev_religion', 'kath_religion', 'werte_normen', 'brc', 'bvw', - 'ernaehrung', 'mechatronik', 'gesundheit_pflege', 'paedagogik_psychologie' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE abitur_niveau_enum AS ENUM ('eA', 'gA'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE abitur_dok_typ_enum AS ENUM ( - 'aufgabe', 'erwartungshorizont', 'deckblatt', 'material', - 'hoerverstehen', 'sprachmittlung', 'bewertungsbogen' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE abitur_status_enum AS ENUM ( - 'pending', 'processing', 'recognized', 'confirmed', 'indexed', 'error' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - --- ============================================================================ --- MAIN TABLE: abitur_dokumente --- ============================================================================ - -CREATE TABLE IF NOT EXISTS abitur_dokumente ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - - -- File info - dateiname VARCHAR(500) NOT NULL, - original_dateiname VARCHAR(500) NOT NULL, - file_path VARCHAR(1000), -- MinIO path or local path - file_size BIGINT DEFAULT 0, - file_hash VARCHAR(64), -- SHA-256 for deduplication - - -- Metadata - bundesland bundesland_enum NOT NULL DEFAULT 'niedersachsen', - fach abitur_fach_enum NOT NULL, - jahr INTEGER NOT NULL CHECK (jahr >= 2000 AND jahr <= 2100), - niveau abitur_niveau_enum NOT NULL DEFAULT 'eA', - typ abitur_dok_typ_enum NOT NULL DEFAULT 'aufgabe', - aufgaben_nummer VARCHAR(20), -- I, II, III, 1, 2, etc. - variante VARCHAR(50), -- BG, Tech, Wirt, etc. - - -- Processing status - status abitur_status_enum NOT NULL DEFAULT 'pending', - confidence REAL DEFAULT 0.0, - - -- Vector store integration - indexed BOOLEAN DEFAULT FALSE, - vector_ids TEXT[], -- Qdrant vector IDs - qdrant_collection VARCHAR(100) DEFAULT 'bp_nibis_eh', - - -- Source tracking - source_dir VARCHAR(500), -- Original source directory - import_batch_id UUID, -- For batch imports - - -- Timestamps - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - indexed_at TIMESTAMPTZ -); - --- Indexes for common queries -CREATE INDEX IF NOT EXISTS idx_abitur_dok_bundesland ON abitur_dokumente(bundesland); -CREATE INDEX IF NOT EXISTS idx_abitur_dok_fach ON abitur_dokumente(fach); -CREATE INDEX IF NOT EXISTS idx_abitur_dok_jahr ON abitur_dokumente(jahr); -CREATE INDEX IF NOT EXISTS idx_abitur_dok_niveau ON abitur_dokumente(niveau); -CREATE INDEX IF NOT EXISTS idx_abitur_dok_typ ON abitur_dokumente(typ); -CREATE INDEX IF NOT EXISTS idx_abitur_dok_status ON abitur_dokumente(status); -CREATE INDEX IF NOT EXISTS idx_abitur_dok_indexed ON abitur_dokumente(indexed); -CREATE INDEX IF NOT EXISTS idx_abitur_dok_file_hash ON abitur_dokumente(file_hash); - --- Composite index for typical searches -CREATE INDEX IF NOT EXISTS idx_abitur_dok_search -ON abitur_dokumente(bundesland, fach, jahr, niveau); - --- ============================================================================ --- TRIGGER: Auto-update updated_at --- ============================================================================ - -CREATE OR REPLACE FUNCTION update_abitur_dok_timestamp() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trigger_abitur_dok_updated ON abitur_dokumente; -CREATE TRIGGER trigger_abitur_dok_updated - BEFORE UPDATE ON abitur_dokumente - FOR EACH ROW - EXECUTE FUNCTION update_abitur_dok_timestamp(); - --- ============================================================================ --- HELPER VIEWS --- ============================================================================ - -CREATE OR REPLACE VIEW v_abitur_dok_stats AS -SELECT - bundesland, - fach, - jahr, - COUNT(*) as total, - COUNT(*) FILTER (WHERE indexed = TRUE) as indexed_count, - COUNT(*) FILTER (WHERE typ = 'aufgabe') as aufgaben_count, - COUNT(*) FILTER (WHERE typ = 'erwartungshorizont') as ewh_count -FROM abitur_dokumente -GROUP BY bundesland, fach, jahr -ORDER BY jahr DESC, fach; - --- ============================================================================ --- SAMPLE DATA CHECK --- ============================================================================ - --- Show table structure --- \d abitur_dokumente - -SELECT 'Migration completed: abitur_dokumente table created' AS status; diff --git a/docs-src/backend/migrations/add_agent_core_tables.sql b/docs-src/backend/migrations/add_agent_core_tables.sql deleted file mode 100644 index 6995df9..0000000 --- a/docs-src/backend/migrations/add_agent_core_tables.sql +++ /dev/null @@ -1,293 +0,0 @@ --- Migration: Add Multi-Agent Architecture Tables --- Date: 2025-01-15 --- Description: Creates tables for agent sessions, memory store, and message audit - --- Enable required extensions -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; - --- ============================================================================ --- 1. Agent Sessions Table --- ============================================================================ --- Stores agent session data with state, context, and checkpoints -CREATE TABLE IF NOT EXISTS agent_sessions ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - agent_type VARCHAR(50) NOT NULL, - user_id UUID REFERENCES users(id) ON DELETE SET NULL, - state VARCHAR(20) NOT NULL DEFAULT 'active' - CHECK (state IN ('active', 'paused', 'completed', 'failed', 'deleted')), - context JSONB DEFAULT '{}'::jsonb, - checkpoints JSONB DEFAULT '[]'::jsonb, - metadata JSONB DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - last_heartbeat TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes for common queries -CREATE INDEX IF NOT EXISTS idx_agent_sessions_user - ON agent_sessions(user_id); -CREATE INDEX IF NOT EXISTS idx_agent_sessions_state - ON agent_sessions(state) WHERE state = 'active'; -CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_type - ON agent_sessions(agent_type); -CREATE INDEX IF NOT EXISTS idx_agent_sessions_heartbeat - ON agent_sessions(last_heartbeat); -CREATE INDEX IF NOT EXISTS idx_agent_sessions_created - ON agent_sessions(created_at DESC); - --- GIN index for JSONB context queries -CREATE INDEX IF NOT EXISTS idx_agent_sessions_context - ON agent_sessions USING GIN (context jsonb_path_ops); - --- Comments for documentation -COMMENT ON TABLE agent_sessions IS 'Stores agent session state and checkpoints for recovery'; -COMMENT ON COLUMN agent_sessions.agent_type IS 'Type: tutor-agent, grader-agent, quality-judge, alert-agent, orchestrator'; -COMMENT ON COLUMN agent_sessions.state IS 'Session state: active, paused, completed, failed, deleted'; -COMMENT ON COLUMN agent_sessions.context IS 'Session context data (entities, conversation state)'; -COMMENT ON COLUMN agent_sessions.checkpoints IS 'Recovery checkpoints as JSON array'; -COMMENT ON COLUMN agent_sessions.last_heartbeat IS 'Last heartbeat timestamp for liveness detection'; - --- ============================================================================ --- 2. Agent Memory Table --- ============================================================================ --- Long-term memory store for agents with TTL support -CREATE TABLE IF NOT EXISTS agent_memory ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - namespace VARCHAR(100) NOT NULL DEFAULT 'breakpilot', - key VARCHAR(500) NOT NULL, - value JSONB NOT NULL, - agent_id VARCHAR(50) NOT NULL, - access_count INTEGER DEFAULT 0, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - last_accessed TIMESTAMPTZ, - expires_at TIMESTAMPTZ, - metadata JSONB DEFAULT '{}'::jsonb, - - -- Unique constraint per namespace - CONSTRAINT agent_memory_namespace_key_unique UNIQUE (namespace, key) -); - --- Indexes for efficient queries -CREATE INDEX IF NOT EXISTS idx_agent_memory_namespace - ON agent_memory(namespace); -CREATE INDEX IF NOT EXISTS idx_agent_memory_agent - ON agent_memory(agent_id); -CREATE INDEX IF NOT EXISTS idx_agent_memory_expires - ON agent_memory(expires_at) WHERE expires_at IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_agent_memory_key_pattern - ON agent_memory(key varchar_pattern_ops); -CREATE INDEX IF NOT EXISTS idx_agent_memory_access_count - ON agent_memory(access_count DESC); - --- GIN index for value queries -CREATE INDEX IF NOT EXISTS idx_agent_memory_value - ON agent_memory USING GIN (value jsonb_path_ops); - --- Comments -COMMENT ON TABLE agent_memory IS 'Long-term memory store for agents with TTL'; -COMMENT ON COLUMN agent_memory.namespace IS 'Namespace for isolation (default: breakpilot)'; -COMMENT ON COLUMN agent_memory.key IS 'Memory key (e.g., evaluation:math:student123)'; -COMMENT ON COLUMN agent_memory.value IS 'Stored value as JSONB'; -COMMENT ON COLUMN agent_memory.access_count IS 'Number of times this memory was accessed'; -COMMENT ON COLUMN agent_memory.expires_at IS 'When this memory expires (NULL = never)'; - --- ============================================================================ --- 3. Agent Messages Table (Audit Trail) --- ============================================================================ --- Stores all inter-agent messages for audit and debugging -CREATE TABLE IF NOT EXISTS agent_messages ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - sender VARCHAR(50) NOT NULL, - receiver VARCHAR(50) NOT NULL, - message_type VARCHAR(50) NOT NULL, - payload JSONB NOT NULL, - priority INTEGER DEFAULT 1 CHECK (priority BETWEEN 0 AND 3), - correlation_id UUID, - reply_to VARCHAR(50), - created_at TIMESTAMPTZ DEFAULT NOW(), - - -- Partition hint for future partitioning - created_date DATE GENERATED ALWAYS AS (DATE(created_at)) STORED -); - --- Indexes for message queries -CREATE INDEX IF NOT EXISTS idx_agent_messages_correlation - ON agent_messages(correlation_id) WHERE correlation_id IS NOT NULL; -CREATE INDEX IF NOT EXISTS idx_agent_messages_sender - ON agent_messages(sender); -CREATE INDEX IF NOT EXISTS idx_agent_messages_receiver - ON agent_messages(receiver); -CREATE INDEX IF NOT EXISTS idx_agent_messages_type - ON agent_messages(message_type); -CREATE INDEX IF NOT EXISTS idx_agent_messages_created - ON agent_messages(created_at DESC); -CREATE INDEX IF NOT EXISTS idx_agent_messages_date - ON agent_messages(created_date); - --- Comments -COMMENT ON TABLE agent_messages IS 'Audit trail for inter-agent communication'; -COMMENT ON COLUMN agent_messages.priority IS '0=LOW, 1=NORMAL, 2=HIGH, 3=CRITICAL'; -COMMENT ON COLUMN agent_messages.correlation_id IS 'Links request/response pairs'; -COMMENT ON COLUMN agent_messages.created_date IS 'Partition column for future table partitioning'; - --- ============================================================================ --- 4. Helper Functions --- ============================================================================ - --- Function to clean up expired memories -CREATE OR REPLACE FUNCTION cleanup_expired_agent_memory() -RETURNS INTEGER AS $$ -DECLARE - deleted_count INTEGER; -BEGIN - DELETE FROM agent_memory - WHERE expires_at IS NOT NULL AND expires_at < NOW(); - - GET DIAGNOSTICS deleted_count = ROW_COUNT; - RETURN deleted_count; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION cleanup_expired_agent_memory() IS 'Removes expired memory entries, returns count'; - --- Function to clean up stale sessions -CREATE OR REPLACE FUNCTION cleanup_stale_agent_sessions(max_age_hours INTEGER DEFAULT 48) -RETURNS INTEGER AS $$ -DECLARE - updated_count INTEGER; -BEGIN - UPDATE agent_sessions - SET state = 'failed', - updated_at = NOW(), - context = context || '{"failure_reason": "heartbeat_timeout"}'::jsonb - WHERE state = 'active' - AND last_heartbeat < NOW() - (max_age_hours || ' hours')::INTERVAL; - - GET DIAGNOSTICS updated_count = ROW_COUNT; - RETURN updated_count; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION cleanup_stale_agent_sessions(INTEGER) IS 'Marks stale sessions as failed, returns count'; - --- Function to update session heartbeat -CREATE OR REPLACE FUNCTION update_session_heartbeat(session_uuid UUID) -RETURNS BOOLEAN AS $$ -BEGIN - UPDATE agent_sessions - SET last_heartbeat = NOW(), updated_at = NOW() - WHERE id = session_uuid AND state = 'active'; - - RETURN FOUND; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION update_session_heartbeat(UUID) IS 'Updates session heartbeat, returns true if found'; - --- ============================================================================ --- 5. Triggers --- ============================================================================ - --- Auto-update updated_at on agent_sessions -CREATE OR REPLACE FUNCTION trigger_set_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS agent_sessions_updated_at ON agent_sessions; -CREATE TRIGGER agent_sessions_updated_at - BEFORE UPDATE ON agent_sessions - FOR EACH ROW - EXECUTE FUNCTION trigger_set_updated_at(); - -DROP TRIGGER IF EXISTS agent_memory_updated_at ON agent_memory; -CREATE TRIGGER agent_memory_updated_at - BEFORE UPDATE ON agent_memory - FOR EACH ROW - EXECUTE FUNCTION trigger_set_updated_at(); - --- ============================================================================ --- 6. DSGVO Compliance: Audit Views --- ============================================================================ - --- View for session audit without PII -CREATE OR REPLACE VIEW v_agent_sessions_audit AS -SELECT - id, - agent_type, - -- Hash user_id for privacy - CASE - WHEN user_id IS NOT NULL - THEN encode(digest(user_id::text, 'sha256'), 'hex') - ELSE NULL - END AS user_id_hash, - state, - -- Only expose non-sensitive context keys - jsonb_build_object( - 'message_count', COALESCE((context->>'message_count')::int, 0), - 'intent_count', COALESCE(jsonb_array_length(context->'intent_history'), 0) - ) AS context_summary, - jsonb_array_length(checkpoints) AS checkpoint_count, - created_at, - updated_at, - last_heartbeat, - EXTRACT(EPOCH FROM (updated_at - created_at)) AS session_duration_seconds -FROM agent_sessions; - -COMMENT ON VIEW v_agent_sessions_audit IS 'Privacy-safe view of agent sessions for auditing'; - --- View for message audit -CREATE OR REPLACE VIEW v_agent_messages_daily_stats AS -SELECT - created_date, - sender, - receiver, - message_type, - COUNT(*) AS message_count, - AVG(priority) AS avg_priority -FROM agent_messages -GROUP BY created_date, sender, receiver, message_type; - -COMMENT ON VIEW v_agent_messages_daily_stats IS 'Daily statistics for inter-agent messages'; - --- ============================================================================ --- 7. Sample Data for Testing (Optional - Comment out in production) --- ============================================================================ -/* --- Uncomment to insert sample data for testing - -INSERT INTO agent_sessions (agent_type, state, context) -VALUES - ('tutor-agent', 'active', '{"subject": "math", "grade": 10}'::jsonb), - ('grader-agent', 'active', '{"exam_type": "vorabitur"}'::jsonb); - -INSERT INTO agent_memory (namespace, key, value, agent_id, expires_at) -VALUES - ('breakpilot', 'test:memory:1', '{"test": true}'::jsonb, 'tutor-agent', NOW() + INTERVAL '30 days'); -*/ - --- ============================================================================ --- 8. Grants (Adjust based on your user/role setup) --- ============================================================================ --- Uncomment and adjust for your environment -/* -GRANT SELECT, INSERT, UPDATE ON agent_sessions TO breakpilot_app; -GRANT SELECT, INSERT, UPDATE, DELETE ON agent_memory TO breakpilot_app; -GRANT SELECT, INSERT ON agent_messages TO breakpilot_app; -GRANT EXECUTE ON FUNCTION cleanup_expired_agent_memory() TO breakpilot_app; -GRANT EXECUTE ON FUNCTION cleanup_stale_agent_sessions(INTEGER) TO breakpilot_app; -GRANT EXECUTE ON FUNCTION update_session_heartbeat(UUID) TO breakpilot_app; -*/ - --- ============================================================================ --- Migration Complete --- ============================================================================ --- To verify migration: --- \dt agent_* --- \df cleanup_* --- \dv v_agent_* diff --git a/docs-src/backend/migrations/add_compliance_tables.sql b/docs-src/backend/migrations/add_compliance_tables.sql deleted file mode 100644 index 36e74c4..0000000 --- a/docs-src/backend/migrations/add_compliance_tables.sql +++ /dev/null @@ -1,807 +0,0 @@ --- ============================================================================ --- Compliance & Audit Framework Migration --- ============================================================================ --- This migration creates all tables required for the Compliance module. --- Run with: psql -h localhost -U breakpilot -d breakpilot -f add_compliance_tables.sql --- --- Tables created: --- 1. Core Compliance Framework (7 tables) --- 2. Service Module Registry (3 tables) --- 3. Audit Sessions & Sign-Off (2 tables) --- 4. ISO 27001 ISMS Models (11 tables) --- ============================================================================ - --- ============================================================================ --- ENUMS (PostgreSQL ENUM types) --- ============================================================================ - -DO $$ BEGIN - CREATE TYPE regulation_type AS ENUM ( - 'eu_regulation', 'eu_directive', 'de_law', 'bsi_standard', 'industry_standard' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE control_type AS ENUM ('preventive', 'detective', 'corrective'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE control_domain AS ENUM ( - 'gov', 'priv', 'iam', 'crypto', 'sdlc', 'ops', 'ai', 'cra', 'aud' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE control_status AS ENUM ('pass', 'partial', 'fail', 'n/a', 'planned'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE risk_level AS ENUM ('low', 'medium', 'high', 'critical'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE evidence_status AS ENUM ('valid', 'expired', 'pending', 'failed'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE export_status AS ENUM ('pending', 'generating', 'completed', 'failed'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE service_type AS ENUM ( - 'backend', 'database', 'ai', 'communication', 'storage', - 'infrastructure', 'monitoring', 'security' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE relevance_level AS ENUM ('critical', 'high', 'medium', 'low'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE audit_result AS ENUM ( - 'compliant', 'compliant_notes', 'non_compliant', 'not_applicable', 'pending' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE audit_session_status AS ENUM ('draft', 'in_progress', 'completed', 'archived'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE approval_status AS ENUM ('draft', 'under_review', 'approved', 'superseded'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE finding_type AS ENUM ('major', 'minor', 'ofi', 'positive'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE finding_status AS ENUM ( - 'open', 'in_progress', 'capa_pending', 'verification_pending', 'verified', 'closed' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE capa_type AS ENUM ('corrective', 'preventive', 'both'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - - --- ============================================================================ --- CORE COMPLIANCE TABLES --- ============================================================================ - --- Table 1: compliance_regulations -CREATE TABLE IF NOT EXISTS compliance_regulations ( - id VARCHAR(36) PRIMARY KEY, - code VARCHAR(20) UNIQUE NOT NULL, - name VARCHAR(200) NOT NULL, - full_name TEXT, - regulation_type regulation_type NOT NULL, - source_url VARCHAR(500), - local_pdf_path VARCHAR(500), - effective_date DATE, - description TEXT, - is_active BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_regulations_code ON compliance_regulations(code); - --- Table 2: compliance_requirements -CREATE TABLE IF NOT EXISTS compliance_requirements ( - id VARCHAR(36) PRIMARY KEY, - regulation_id VARCHAR(36) NOT NULL REFERENCES compliance_regulations(id), - article VARCHAR(50) NOT NULL, - paragraph VARCHAR(20), - requirement_id_external VARCHAR(50), - title VARCHAR(300) NOT NULL, - description TEXT, - requirement_text TEXT, - breakpilot_interpretation TEXT, - implementation_status VARCHAR(30) DEFAULT 'not_started', - implementation_details TEXT, - code_references JSONB, - documentation_links JSONB, - evidence_description TEXT, - evidence_artifacts JSONB, - auditor_notes TEXT, - audit_status VARCHAR(30) DEFAULT 'pending', - last_audit_date TIMESTAMP, - last_auditor VARCHAR(100), - is_applicable BOOLEAN DEFAULT TRUE, - applicability_reason TEXT, - priority INTEGER DEFAULT 2, - source_page INTEGER, - source_section VARCHAR(100), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_requirement_regulation_article ON compliance_requirements(regulation_id, article); -CREATE INDEX IF NOT EXISTS ix_requirement_audit_status ON compliance_requirements(audit_status); -CREATE INDEX IF NOT EXISTS ix_requirement_impl_status ON compliance_requirements(implementation_status); - --- Table 3: compliance_controls -CREATE TABLE IF NOT EXISTS compliance_controls ( - id VARCHAR(36) PRIMARY KEY, - control_id VARCHAR(20) UNIQUE NOT NULL, - domain control_domain NOT NULL, - control_type control_type NOT NULL, - title VARCHAR(300) NOT NULL, - description TEXT, - pass_criteria TEXT NOT NULL, - implementation_guidance TEXT, - code_reference VARCHAR(500), - documentation_url VARCHAR(500), - is_automated BOOLEAN DEFAULT FALSE, - automation_tool VARCHAR(100), - automation_config JSONB, - status control_status DEFAULT 'planned', - status_notes TEXT, - owner VARCHAR(100), - review_frequency_days INTEGER DEFAULT 90, - last_reviewed_at TIMESTAMP, - next_review_at TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_control_id ON compliance_controls(control_id); -CREATE INDEX IF NOT EXISTS ix_control_domain_status ON compliance_controls(domain, status); - --- Table 4: compliance_control_mappings -CREATE TABLE IF NOT EXISTS compliance_control_mappings ( - id VARCHAR(36) PRIMARY KEY, - requirement_id VARCHAR(36) NOT NULL REFERENCES compliance_requirements(id), - control_id VARCHAR(36) NOT NULL REFERENCES compliance_controls(id), - coverage_level VARCHAR(20) DEFAULT 'full', - notes TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE UNIQUE INDEX IF NOT EXISTS ix_mapping_req_ctrl ON compliance_control_mappings(requirement_id, control_id); - --- Table 5: compliance_evidence -CREATE TABLE IF NOT EXISTS compliance_evidence ( - id VARCHAR(36) PRIMARY KEY, - control_id VARCHAR(36) NOT NULL REFERENCES compliance_controls(id), - evidence_type VARCHAR(50) NOT NULL, - title VARCHAR(300) NOT NULL, - description TEXT, - artifact_path VARCHAR(500), - artifact_url VARCHAR(500), - artifact_hash VARCHAR(64), - file_size_bytes INTEGER, - mime_type VARCHAR(100), - valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - valid_until TIMESTAMP, - status evidence_status DEFAULT 'valid', - source VARCHAR(100), - ci_job_id VARCHAR(100), - uploaded_by VARCHAR(100), - collected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_evidence_control_type ON compliance_evidence(control_id, evidence_type); -CREATE INDEX IF NOT EXISTS ix_evidence_status ON compliance_evidence(status); - --- Table 6: compliance_risks -CREATE TABLE IF NOT EXISTS compliance_risks ( - id VARCHAR(36) PRIMARY KEY, - risk_id VARCHAR(20) UNIQUE NOT NULL, - title VARCHAR(300) NOT NULL, - description TEXT, - category VARCHAR(50) NOT NULL, - likelihood INTEGER NOT NULL, - impact INTEGER NOT NULL, - inherent_risk risk_level NOT NULL, - mitigating_controls JSONB, - residual_likelihood INTEGER, - residual_impact INTEGER, - residual_risk risk_level, - owner VARCHAR(100), - status VARCHAR(20) DEFAULT 'open', - treatment_plan TEXT, - identified_date DATE DEFAULT CURRENT_DATE, - review_date DATE, - last_assessed_at TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_risk_id ON compliance_risks(risk_id); -CREATE INDEX IF NOT EXISTS ix_risk_category_status ON compliance_risks(category, status); -CREATE INDEX IF NOT EXISTS ix_risk_inherent ON compliance_risks(inherent_risk); - --- Table 7: compliance_audit_exports -CREATE TABLE IF NOT EXISTS compliance_audit_exports ( - id VARCHAR(36) PRIMARY KEY, - export_type VARCHAR(50) NOT NULL, - export_name VARCHAR(200), - included_regulations JSONB, - included_domains JSONB, - date_range_start DATE, - date_range_end DATE, - requested_by VARCHAR(100) NOT NULL, - requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - completed_at TIMESTAMP, - file_path VARCHAR(500), - file_hash VARCHAR(64), - file_size_bytes INTEGER, - status export_status DEFAULT 'pending', - error_message TEXT, - total_controls INTEGER, - total_evidence INTEGER, - compliance_score FLOAT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - - --- ============================================================================ --- SERVICE MODULE REGISTRY TABLES --- ============================================================================ - --- Table 8: compliance_service_modules -CREATE TABLE IF NOT EXISTS compliance_service_modules ( - id VARCHAR(36) PRIMARY KEY, - name VARCHAR(100) UNIQUE NOT NULL, - display_name VARCHAR(200) NOT NULL, - description TEXT, - service_type service_type NOT NULL, - port INTEGER, - technology_stack JSONB, - repository_path VARCHAR(500), - docker_image VARCHAR(200), - data_categories JSONB, - processes_pii BOOLEAN DEFAULT FALSE, - processes_health_data BOOLEAN DEFAULT FALSE, - ai_components BOOLEAN DEFAULT FALSE, - is_active BOOLEAN DEFAULT TRUE, - criticality VARCHAR(20) DEFAULT 'medium', - compliance_score FLOAT, - last_compliance_check TIMESTAMP, - owner_team VARCHAR(100), - owner_contact VARCHAR(200), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_module_name ON compliance_service_modules(name); -CREATE INDEX IF NOT EXISTS ix_module_type_active ON compliance_service_modules(service_type, is_active); - --- Table 9: compliance_module_regulations -CREATE TABLE IF NOT EXISTS compliance_module_regulations ( - id VARCHAR(36) PRIMARY KEY, - module_id VARCHAR(36) NOT NULL REFERENCES compliance_service_modules(id), - regulation_id VARCHAR(36) NOT NULL REFERENCES compliance_regulations(id), - relevance_level relevance_level DEFAULT 'medium', - notes TEXT, - applicable_articles JSONB, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE UNIQUE INDEX IF NOT EXISTS ix_module_regulation ON compliance_module_regulations(module_id, regulation_id); - --- Table 10: compliance_module_risks -CREATE TABLE IF NOT EXISTS compliance_module_risks ( - id VARCHAR(36) PRIMARY KEY, - module_id VARCHAR(36) NOT NULL REFERENCES compliance_service_modules(id), - risk_id VARCHAR(36) NOT NULL REFERENCES compliance_risks(id), - module_likelihood INTEGER, - module_impact INTEGER, - module_risk_level risk_level, - assessment_notes TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE UNIQUE INDEX IF NOT EXISTS ix_module_risk ON compliance_module_risks(module_id, risk_id); - - --- ============================================================================ --- AUDIT SESSION & SIGN-OFF TABLES --- ============================================================================ - --- Table 11: compliance_audit_sessions -CREATE TABLE IF NOT EXISTS compliance_audit_sessions ( - id VARCHAR(36) PRIMARY KEY, - name VARCHAR(200) NOT NULL, - description TEXT, - auditor_name VARCHAR(100) NOT NULL, - auditor_email VARCHAR(200), - auditor_organization VARCHAR(200), - status audit_session_status DEFAULT 'draft', - regulation_ids JSONB, - total_items INTEGER DEFAULT 0, - completed_items INTEGER DEFAULT 0, - compliant_count INTEGER DEFAULT 0, - non_compliant_count INTEGER DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - started_at TIMESTAMP, - completed_at TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_audit_session_status ON compliance_audit_sessions(status); -CREATE INDEX IF NOT EXISTS ix_audit_session_auditor ON compliance_audit_sessions(auditor_name); - --- Table 12: compliance_audit_signoffs -CREATE TABLE IF NOT EXISTS compliance_audit_signoffs ( - id VARCHAR(36) PRIMARY KEY, - session_id VARCHAR(36) NOT NULL REFERENCES compliance_audit_sessions(id), - requirement_id VARCHAR(36) NOT NULL REFERENCES compliance_requirements(id), - result audit_result DEFAULT 'pending', - notes TEXT, - evidence_ids JSONB, - signature_hash VARCHAR(64), - signed_at TIMESTAMP, - signed_by VARCHAR(100), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE UNIQUE INDEX IF NOT EXISTS ix_signoff_session_requirement ON compliance_audit_signoffs(session_id, requirement_id); -CREATE INDEX IF NOT EXISTS ix_signoff_result ON compliance_audit_signoffs(result); - - --- ============================================================================ --- ISO 27001 ISMS TABLES --- ============================================================================ - --- Table 13: compliance_isms_scope -CREATE TABLE IF NOT EXISTS compliance_isms_scope ( - id VARCHAR(36) PRIMARY KEY, - version VARCHAR(20) NOT NULL DEFAULT '1.0', - scope_statement TEXT NOT NULL, - included_locations JSONB, - included_processes JSONB, - included_services JSONB, - excluded_items JSONB, - exclusion_justification TEXT, - organizational_boundary TEXT, - physical_boundary TEXT, - technical_boundary TEXT, - status approval_status DEFAULT 'draft', - approved_by VARCHAR(100), - approved_at TIMESTAMP, - approval_signature VARCHAR(64), - effective_date DATE, - review_date DATE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - created_by VARCHAR(100), - updated_by VARCHAR(100) -); - -CREATE INDEX IF NOT EXISTS ix_isms_scope_status ON compliance_isms_scope(status); - --- Table 14: compliance_isms_context -CREATE TABLE IF NOT EXISTS compliance_isms_context ( - id VARCHAR(36) PRIMARY KEY, - version VARCHAR(20) NOT NULL DEFAULT '1.0', - internal_issues JSONB, - external_issues JSONB, - interested_parties JSONB, - regulatory_requirements JSONB, - contractual_requirements JSONB, - swot_strengths JSONB, - swot_weaknesses JSONB, - swot_opportunities JSONB, - swot_threats JSONB, - status approval_status DEFAULT 'draft', - approved_by VARCHAR(100), - approved_at TIMESTAMP, - last_reviewed_at TIMESTAMP, - next_review_date DATE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Table 15: compliance_isms_policies -CREATE TABLE IF NOT EXISTS compliance_isms_policies ( - id VARCHAR(36) PRIMARY KEY, - policy_id VARCHAR(30) UNIQUE NOT NULL, - title VARCHAR(200) NOT NULL, - policy_type VARCHAR(50) NOT NULL, - description TEXT, - policy_text TEXT NOT NULL, - applies_to JSONB, - version VARCHAR(20) NOT NULL DEFAULT '1.0', - status approval_status DEFAULT 'draft', - authored_by VARCHAR(100), - reviewed_by VARCHAR(100), - approved_by VARCHAR(100), - approved_at TIMESTAMP, - approval_signature VARCHAR(64), - effective_date DATE, - review_frequency_months INTEGER DEFAULT 12, - next_review_date DATE, - parent_policy_id VARCHAR(36) REFERENCES compliance_isms_policies(id), - related_controls JSONB, - document_path VARCHAR(500), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_policy_id ON compliance_isms_policies(policy_id); -CREATE INDEX IF NOT EXISTS ix_policy_type_status ON compliance_isms_policies(policy_type, status); - --- Table 16: compliance_security_objectives -CREATE TABLE IF NOT EXISTS compliance_security_objectives ( - id VARCHAR(36) PRIMARY KEY, - objective_id VARCHAR(30) UNIQUE NOT NULL, - title VARCHAR(200) NOT NULL, - description TEXT, - category VARCHAR(50), - specific TEXT, - measurable TEXT, - achievable TEXT, - relevant TEXT, - time_bound TEXT, - kpi_name VARCHAR(100), - kpi_target VARCHAR(100), - kpi_current VARCHAR(100), - kpi_unit VARCHAR(50), - measurement_frequency VARCHAR(50), - owner VARCHAR(100), - accountable VARCHAR(100), - status VARCHAR(30) DEFAULT 'active', - progress_percentage INTEGER DEFAULT 0, - target_date DATE, - achieved_date DATE, - related_controls JSONB, - related_risks JSONB, - approved_by VARCHAR(100), - approved_at TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_objective_id ON compliance_security_objectives(objective_id); -CREATE INDEX IF NOT EXISTS ix_objective_status ON compliance_security_objectives(status); -CREATE INDEX IF NOT EXISTS ix_objective_category ON compliance_security_objectives(category); - --- Table 17: compliance_soa (Statement of Applicability) -CREATE TABLE IF NOT EXISTS compliance_soa ( - id VARCHAR(36) PRIMARY KEY, - annex_a_control VARCHAR(20) NOT NULL, - annex_a_title VARCHAR(300) NOT NULL, - annex_a_category VARCHAR(100), - is_applicable BOOLEAN NOT NULL, - applicability_justification TEXT NOT NULL, - implementation_status VARCHAR(30) DEFAULT 'planned', - implementation_notes TEXT, - breakpilot_control_ids JSONB, - coverage_level VARCHAR(20) DEFAULT 'full', - evidence_description TEXT, - evidence_ids JSONB, - risk_assessment_notes TEXT, - compensating_controls TEXT, - reviewed_by VARCHAR(100), - reviewed_at TIMESTAMP, - approved_by VARCHAR(100), - approved_at TIMESTAMP, - version VARCHAR(20) DEFAULT '1.0', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE UNIQUE INDEX IF NOT EXISTS ix_soa_annex_control ON compliance_soa(annex_a_control); -CREATE INDEX IF NOT EXISTS ix_soa_applicable ON compliance_soa(is_applicable); -CREATE INDEX IF NOT EXISTS ix_soa_status ON compliance_soa(implementation_status); - --- Table 18: compliance_internal_audits (MUST be before audit_findings due to FK) -CREATE TABLE IF NOT EXISTS compliance_internal_audits ( - id VARCHAR(36) PRIMARY KEY, - audit_id VARCHAR(30) UNIQUE NOT NULL, - title VARCHAR(200) NOT NULL, - audit_type VARCHAR(50) NOT NULL, - scope_description TEXT NOT NULL, - iso_chapters_covered JSONB, - annex_a_controls_covered JSONB, - processes_covered JSONB, - departments_covered JSONB, - criteria TEXT, - planned_date DATE NOT NULL, - actual_start_date DATE, - actual_end_date DATE, - lead_auditor VARCHAR(100) NOT NULL, - audit_team JSONB, - auditee_representatives JSONB, - status VARCHAR(30) DEFAULT 'planned', - total_findings INTEGER DEFAULT 0, - major_findings INTEGER DEFAULT 0, - minor_findings INTEGER DEFAULT 0, - ofi_count INTEGER DEFAULT 0, - positive_observations INTEGER DEFAULT 0, - audit_conclusion TEXT, - overall_assessment VARCHAR(30), - report_date DATE, - report_document_path VARCHAR(500), - report_approved_by VARCHAR(100), - report_approved_at TIMESTAMP, - follow_up_audit_required BOOLEAN DEFAULT FALSE, - follow_up_audit_id VARCHAR(36), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_internal_audit_id ON compliance_internal_audits(audit_id); -CREATE INDEX IF NOT EXISTS ix_internal_audit_date ON compliance_internal_audits(planned_date); -CREATE INDEX IF NOT EXISTS ix_internal_audit_status ON compliance_internal_audits(status); - --- Table 19: compliance_audit_findings -CREATE TABLE IF NOT EXISTS compliance_audit_findings ( - id VARCHAR(36) PRIMARY KEY, - finding_id VARCHAR(30) UNIQUE NOT NULL, - audit_session_id VARCHAR(36) REFERENCES compliance_audit_sessions(id), - internal_audit_id VARCHAR(36) REFERENCES compliance_internal_audits(id), - finding_type finding_type NOT NULL, - iso_chapter VARCHAR(20), - annex_a_control VARCHAR(20), - title VARCHAR(300) NOT NULL, - description TEXT NOT NULL, - objective_evidence TEXT NOT NULL, - root_cause TEXT, - root_cause_method VARCHAR(50), - impact_description TEXT, - affected_processes JSONB, - affected_assets JSONB, - status finding_status DEFAULT 'open', - owner VARCHAR(100), - auditor VARCHAR(100), - identified_date DATE NOT NULL DEFAULT CURRENT_DATE, - due_date DATE, - closed_date DATE, - verification_method TEXT, - verified_by VARCHAR(100), - verified_at TIMESTAMP, - verification_evidence TEXT, - closure_notes TEXT, - closed_by VARCHAR(100), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_finding_id ON compliance_audit_findings(finding_id); -CREATE INDEX IF NOT EXISTS ix_finding_type_status ON compliance_audit_findings(finding_type, status); -CREATE INDEX IF NOT EXISTS ix_finding_due_date ON compliance_audit_findings(due_date); - --- Table 20: compliance_corrective_actions -CREATE TABLE IF NOT EXISTS compliance_corrective_actions ( - id VARCHAR(36) PRIMARY KEY, - capa_id VARCHAR(30) UNIQUE NOT NULL, - finding_id VARCHAR(36) NOT NULL REFERENCES compliance_audit_findings(id), - capa_type capa_type NOT NULL, - title VARCHAR(300) NOT NULL, - description TEXT NOT NULL, - expected_outcome TEXT, - assigned_to VARCHAR(100) NOT NULL, - approved_by VARCHAR(100), - planned_start DATE, - planned_completion DATE NOT NULL, - actual_completion DATE, - status VARCHAR(30) DEFAULT 'planned', - progress_percentage INTEGER DEFAULT 0, - estimated_effort_hours INTEGER, - actual_effort_hours INTEGER, - resources_required TEXT, - implementation_evidence TEXT, - evidence_ids JSONB, - effectiveness_criteria TEXT, - effectiveness_verified BOOLEAN DEFAULT FALSE, - effectiveness_verification_date DATE, - effectiveness_notes TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_capa_id ON compliance_corrective_actions(capa_id); -CREATE INDEX IF NOT EXISTS ix_capa_status ON compliance_corrective_actions(status); -CREATE INDEX IF NOT EXISTS ix_capa_due ON compliance_corrective_actions(planned_completion); - --- Table 21: compliance_management_reviews -CREATE TABLE IF NOT EXISTS compliance_management_reviews ( - id VARCHAR(36) PRIMARY KEY, - review_id VARCHAR(30) UNIQUE NOT NULL, - title VARCHAR(200) NOT NULL, - review_date DATE NOT NULL, - review_period_start DATE, - review_period_end DATE, - chairperson VARCHAR(100) NOT NULL, - attendees JSONB, - input_previous_actions TEXT, - input_isms_changes TEXT, - input_security_performance TEXT, - input_interested_party_feedback TEXT, - input_risk_assessment_results TEXT, - input_improvement_opportunities TEXT, - input_policy_effectiveness TEXT, - input_objective_achievement TEXT, - input_resource_adequacy TEXT, - output_improvement_decisions TEXT, - output_isms_changes TEXT, - output_resource_needs TEXT, - action_items JSONB, - isms_effectiveness_rating VARCHAR(20), - key_decisions TEXT, - status VARCHAR(30) DEFAULT 'draft', - approved_by VARCHAR(100), - approved_at TIMESTAMP, - minutes_document_path VARCHAR(500), - next_review_date DATE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_mgmt_review_id ON compliance_management_reviews(review_id); -CREATE INDEX IF NOT EXISTS ix_mgmt_review_date ON compliance_management_reviews(review_date); -CREATE INDEX IF NOT EXISTS ix_mgmt_review_status ON compliance_management_reviews(status); - --- Table 22: compliance_audit_trail -CREATE TABLE IF NOT EXISTS compliance_audit_trail ( - id VARCHAR(36) PRIMARY KEY, - entity_type VARCHAR(50) NOT NULL, - entity_id VARCHAR(36) NOT NULL, - entity_name VARCHAR(200), - action VARCHAR(20) NOT NULL, - field_changed VARCHAR(100), - old_value TEXT, - new_value TEXT, - change_summary TEXT, - performed_by VARCHAR(100) NOT NULL, - performed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - ip_address VARCHAR(45), - user_agent VARCHAR(500), - session_id VARCHAR(100), - checksum VARCHAR(64), - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_audit_trail_entity ON compliance_audit_trail(entity_type, entity_id); -CREATE INDEX IF NOT EXISTS ix_audit_trail_time ON compliance_audit_trail(performed_at); -CREATE INDEX IF NOT EXISTS ix_audit_trail_user ON compliance_audit_trail(performed_by); - --- Table 23: compliance_isms_readiness -CREATE TABLE IF NOT EXISTS compliance_isms_readiness ( - id VARCHAR(36) PRIMARY KEY, - check_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - triggered_by VARCHAR(100), - overall_status VARCHAR(20) NOT NULL, - certification_possible BOOLEAN NOT NULL, - chapter_4_status VARCHAR(20), - chapter_5_status VARCHAR(20), - chapter_6_status VARCHAR(20), - chapter_7_status VARCHAR(20), - chapter_8_status VARCHAR(20), - chapter_9_status VARCHAR(20), - chapter_10_status VARCHAR(20), - potential_majors JSONB, - potential_minors JSONB, - improvement_opportunities JSONB, - readiness_score FLOAT, - documentation_score FLOAT, - implementation_score FLOAT, - evidence_score FLOAT, - priority_actions JSONB, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX IF NOT EXISTS ix_readiness_date ON compliance_isms_readiness(check_date); -CREATE INDEX IF NOT EXISTS ix_readiness_status ON compliance_isms_readiness(overall_status); - - --- ============================================================================ --- UPDATE TIMESTAMPS TRIGGER --- ============================================================================ - -CREATE OR REPLACE FUNCTION update_compliance_timestamp() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = CURRENT_TIMESTAMP; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Apply trigger to all compliance tables with updated_at -DO $$ -DECLARE - t TEXT; -BEGIN - FOR t IN - SELECT table_name - FROM information_schema.columns - WHERE table_schema = 'public' - AND column_name = 'updated_at' - AND table_name LIKE 'compliance_%' - LOOP - EXECUTE format(' - DROP TRIGGER IF EXISTS trigger_%I_updated_at ON %I; - CREATE TRIGGER trigger_%I_updated_at - BEFORE UPDATE ON %I - FOR EACH ROW EXECUTE FUNCTION update_compliance_timestamp(); - ', t, t, t, t); - END LOOP; -END $$; - - --- ============================================================================ --- CLEANUP FUNCTIONS --- ============================================================================ - --- Function to cleanup expired evidence -CREATE OR REPLACE FUNCTION cleanup_expired_compliance_evidence() -RETURNS INTEGER AS $$ -DECLARE - deleted_count INTEGER; -BEGIN - UPDATE compliance_evidence - SET status = 'expired' - WHERE valid_until < CURRENT_TIMESTAMP - AND status = 'valid'; - - GET DIAGNOSTICS deleted_count = ROW_COUNT; - RETURN deleted_count; -END; -$$ LANGUAGE plpgsql; - - --- ============================================================================ --- GRANT PERMISSIONS --- ============================================================================ - --- Grant permissions to the application user (adjust username as needed) --- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot; --- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot; - - --- ============================================================================ --- MIGRATION COMPLETE --- ============================================================================ - -SELECT 'Compliance migration completed. ' || - (SELECT COUNT(*) FROM information_schema.tables - WHERE table_schema = 'public' AND table_name LIKE 'compliance_%') || - ' tables created.' AS status; diff --git a/docs-src/backend/migrations/add_game_tables.sql b/docs-src/backend/migrations/add_game_tables.sql deleted file mode 100644 index efcd541..0000000 --- a/docs-src/backend/migrations/add_game_tables.sql +++ /dev/null @@ -1,241 +0,0 @@ --- ============================================== --- Breakpilot Drive - Game Tables Migration --- ============================================== --- Run this migration to add game-related tables to PostgreSQL. --- --- Execute with: --- psql -h localhost -U breakpilot -d breakpilot -f add_game_tables.sql - --- Enable UUID extension if not already enabled -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- ============================================== --- Student Learning State --- ============================================== --- Tracks the learning progress of each student across subjects. --- This is the core table for adaptive difficulty. - -CREATE TABLE IF NOT EXISTS student_learning_state ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - student_id UUID NOT NULL, - overall_level INTEGER DEFAULT 3 CHECK (overall_level >= 1 AND overall_level <= 5), - math_level DECIMAL(3,2) DEFAULT 3.0 CHECK (math_level >= 1.0 AND math_level <= 5.0), - german_level DECIMAL(3,2) DEFAULT 3.0 CHECK (german_level >= 1.0 AND german_level <= 5.0), - english_level DECIMAL(3,2) DEFAULT 3.0 CHECK (english_level >= 1.0 AND english_level <= 5.0), - total_play_time_minutes INTEGER DEFAULT 0, - total_sessions INTEGER DEFAULT 0, - questions_answered INTEGER DEFAULT 0, - questions_correct INTEGER DEFAULT 0, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - CONSTRAINT unique_student_learning UNIQUE(student_id) -); - --- Index for fast lookups by student_id -CREATE INDEX IF NOT EXISTS idx_learning_state_student ON student_learning_state(student_id); - --- Comment on table -COMMENT ON TABLE student_learning_state IS 'Tracks learning progress for Breakpilot Drive game'; -COMMENT ON COLUMN student_learning_state.overall_level IS 'Overall difficulty level 1-5 (1=Beginner/Grade 2-3, 5=Expert/Grade 6+)'; -COMMENT ON COLUMN student_learning_state.math_level IS 'Math subject proficiency level'; -COMMENT ON COLUMN student_learning_state.german_level IS 'German subject proficiency level'; -COMMENT ON COLUMN student_learning_state.english_level IS 'English subject proficiency level'; - - --- ============================================== --- Game Sessions --- ============================================== --- Records each game session played by a student. - -CREATE TABLE IF NOT EXISTS game_sessions ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - student_id UUID NOT NULL, - game_mode VARCHAR(20) NOT NULL CHECK (game_mode IN ('video', 'audio')), - duration_seconds INTEGER NOT NULL CHECK (duration_seconds >= 0), - distance_traveled DECIMAL(10,2), - score INTEGER NOT NULL DEFAULT 0, - questions_answered INTEGER DEFAULT 0, - questions_correct INTEGER DEFAULT 0, - difficulty_level INTEGER NOT NULL CHECK (difficulty_level >= 1 AND difficulty_level <= 5), - started_at TIMESTAMPTZ NOT NULL, - ended_at TIMESTAMPTZ DEFAULT NOW(), - metadata JSONB, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes for common queries -CREATE INDEX IF NOT EXISTS idx_game_sessions_student ON game_sessions(student_id); -CREATE INDEX IF NOT EXISTS idx_game_sessions_date ON game_sessions(ended_at); -CREATE INDEX IF NOT EXISTS idx_game_sessions_score ON game_sessions(score DESC); - --- Comment on table -COMMENT ON TABLE game_sessions IS 'Records individual game sessions for Breakpilot Drive'; -COMMENT ON COLUMN game_sessions.game_mode IS 'Game mode: video (visual) or audio (voice-guided)'; -COMMENT ON COLUMN game_sessions.distance_traveled IS 'Distance traveled in game units'; -COMMENT ON COLUMN game_sessions.metadata IS 'Additional session data in JSON format'; - - --- ============================================== --- Game Quiz Answers --- ============================================== --- Tracks individual quiz answers for detailed analytics. - -CREATE TABLE IF NOT EXISTS game_quiz_answers ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - session_id UUID REFERENCES game_sessions(id) ON DELETE CASCADE, - question_id VARCHAR(100) NOT NULL, - subject VARCHAR(50) NOT NULL CHECK (subject IN ('math', 'german', 'english', 'general')), - difficulty INTEGER NOT NULL CHECK (difficulty >= 1 AND difficulty <= 5), - is_correct BOOLEAN NOT NULL, - answer_time_ms INTEGER CHECK (answer_time_ms >= 0), - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes for analytics queries -CREATE INDEX IF NOT EXISTS idx_quiz_answers_session ON game_quiz_answers(session_id); -CREATE INDEX IF NOT EXISTS idx_quiz_answers_subject ON game_quiz_answers(subject); -CREATE INDEX IF NOT EXISTS idx_quiz_answers_correct ON game_quiz_answers(is_correct); - --- Comment on table -COMMENT ON TABLE game_quiz_answers IS 'Individual quiz answer records for learning analytics'; - - --- ============================================== --- Trigger: Update updated_at timestamp --- ============================================== -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ language 'plpgsql'; - --- Apply trigger to student_learning_state -DROP TRIGGER IF EXISTS update_student_learning_state_updated_at ON student_learning_state; -CREATE TRIGGER update_student_learning_state_updated_at - BEFORE UPDATE ON student_learning_state - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); - - --- ============================================== --- View: Student Summary Statistics --- ============================================== -CREATE OR REPLACE VIEW game_student_summary AS -SELECT - sls.student_id, - sls.overall_level, - sls.math_level, - sls.german_level, - sls.english_level, - sls.total_play_time_minutes, - sls.total_sessions, - sls.questions_answered, - sls.questions_correct, - CASE WHEN sls.questions_answered > 0 - THEN ROUND((sls.questions_correct::DECIMAL / sls.questions_answered) * 100, 1) - ELSE 0 END as accuracy_percent, - COALESCE(recent.recent_score, 0) as recent_score, - COALESCE(recent.recent_sessions, 0) as sessions_last_7_days -FROM student_learning_state sls -LEFT JOIN ( - SELECT - student_id, - SUM(score) as recent_score, - COUNT(*) as recent_sessions - FROM game_sessions - WHERE ended_at > NOW() - INTERVAL '7 days' - GROUP BY student_id -) recent ON sls.student_id = recent.student_id; - -COMMENT ON VIEW game_student_summary IS 'Summary statistics for each student including recent activity'; - - --- ============================================== --- View: Daily Leaderboard --- ============================================== -CREATE OR REPLACE VIEW game_daily_leaderboard AS -SELECT - student_id, - SUM(score) as total_score, - COUNT(*) as session_count, - SUM(questions_correct) as total_correct, - SUM(questions_answered) as total_questions, - RANK() OVER (ORDER BY SUM(score) DESC) as rank -FROM game_sessions -WHERE ended_at > NOW() - INTERVAL '1 day' -GROUP BY student_id -ORDER BY total_score DESC; - -COMMENT ON VIEW game_daily_leaderboard IS 'Daily leaderboard for Breakpilot Drive'; - - --- ============================================== --- Function: Calculate Level Adjustment --- ============================================== --- Returns the recommended level adjustment based on recent performance. --- Returns: -1 (decrease), 0 (keep), 1 (increase) - -CREATE OR REPLACE FUNCTION calculate_level_adjustment(p_student_id UUID) -RETURNS INTEGER AS $$ -DECLARE - v_recent_accuracy DECIMAL; - v_recent_questions INTEGER; -BEGIN - -- Get accuracy from last 10 questions - SELECT - CASE WHEN COUNT(*) > 0 - THEN SUM(CASE WHEN is_correct THEN 1 ELSE 0 END)::DECIMAL / COUNT(*) - ELSE 0 END, - COUNT(*) - INTO v_recent_accuracy, v_recent_questions - FROM ( - SELECT is_correct - FROM game_quiz_answers qa - JOIN game_sessions gs ON qa.session_id = gs.id - WHERE gs.student_id = p_student_id - ORDER BY qa.created_at DESC - LIMIT 10 - ) recent; - - -- Need at least 5 questions for adjustment - IF v_recent_questions < 5 THEN - RETURN 0; - END IF; - - -- High accuracy (>=80%) -> increase level - IF v_recent_accuracy >= 0.8 THEN - RETURN 1; - END IF; - - -- Low accuracy (<40%) -> decrease level - IF v_recent_accuracy < 0.4 THEN - RETURN -1; - END IF; - - -- Keep current level - RETURN 0; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION calculate_level_adjustment IS 'Calculates recommended difficulty adjustment based on recent performance'; - - --- ============================================== --- Grant Permissions (adjust user as needed) --- ============================================== --- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot; --- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot; --- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO breakpilot; - - --- ============================================== --- Migration Complete Message --- ============================================== -DO $$ -BEGIN - RAISE NOTICE 'Breakpilot Drive game tables created successfully!'; - RAISE NOTICE 'Tables: student_learning_state, game_sessions, game_quiz_answers'; - RAISE NOTICE 'Views: game_student_summary, game_daily_leaderboard'; -END $$; diff --git a/docs-src/backend/migrations/add_recording_transcription_tables.sql b/docs-src/backend/migrations/add_recording_transcription_tables.sql deleted file mode 100644 index 2eefc81..0000000 --- a/docs-src/backend/migrations/add_recording_transcription_tables.sql +++ /dev/null @@ -1,409 +0,0 @@ --- ============================================== --- Jitsi Recordings & Transcription Tables Migration --- ============================================== --- Run this migration to add recording and transcription tables. --- --- Execute with: --- psql -h localhost -U breakpilot -d breakpilot_db -f add_recording_transcription_tables.sql - --- Enable UUID extension if not already enabled -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- ============================================== --- Meeting Recording Consents (DSGVO) --- ============================================== --- Tracks consent for meeting recordings. --- All participants must consent before recording starts. - -CREATE TABLE IF NOT EXISTS meeting_recording_consents ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - meeting_id VARCHAR(255) NOT NULL, - user_id UUID, - consent_type VARCHAR(50) NOT NULL CHECK (consent_type IN ('opt_in', 'announced', 'implicit')), - all_participants_consented BOOLEAN DEFAULT FALSE, - participant_count INTEGER DEFAULT 0, - consented_count INTEGER DEFAULT 0, - consented_at TIMESTAMPTZ, - withdrawn_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_recording_consents_meeting ON meeting_recording_consents(meeting_id); -CREATE INDEX IF NOT EXISTS idx_recording_consents_user ON meeting_recording_consents(user_id); - --- Comments -COMMENT ON TABLE meeting_recording_consents IS 'DSGVO-compliant consent tracking for meeting recordings'; -COMMENT ON COLUMN meeting_recording_consents.consent_type IS 'Type: opt_in (explicit), announced (verbally announced), implicit (policy-based)'; -COMMENT ON COLUMN meeting_recording_consents.withdrawn_at IS 'Set when consent is withdrawn (soft delete)'; - - --- ============================================== --- Recordings --- ============================================== --- Stores metadata for recorded meetings. - -CREATE TABLE IF NOT EXISTS recordings ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - meeting_id VARCHAR(255) NOT NULL, - jibri_session_id VARCHAR(255), - title VARCHAR(500), - storage_path VARCHAR(1000) NOT NULL, - audio_path VARCHAR(1000), - file_size_bytes BIGINT, - duration_seconds INTEGER, - participant_count INTEGER DEFAULT 0, - status VARCHAR(50) NOT NULL DEFAULT 'uploaded' CHECK (status IN ('uploaded', 'processing', 'ready', 'failed', 'deleted')), - created_by UUID, - recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - retention_days INTEGER DEFAULT 365, - deleted_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_recordings_meeting ON recordings(meeting_id); -CREATE INDEX IF NOT EXISTS idx_recordings_status ON recordings(status); -CREATE INDEX IF NOT EXISTS idx_recordings_created_by ON recordings(created_by); -CREATE INDEX IF NOT EXISTS idx_recordings_recorded_at ON recordings(recorded_at); - --- Comments -COMMENT ON TABLE recordings IS 'Jitsi meeting recordings stored in MinIO'; -COMMENT ON COLUMN recordings.storage_path IS 'Path in MinIO bucket: recordings/{recording_name}/video.mp4'; -COMMENT ON COLUMN recordings.audio_path IS 'Extracted audio for transcription: recordings/{recording_name}/audio.wav'; -COMMENT ON COLUMN recordings.retention_days IS 'Days until automatic deletion (DSGVO compliance)'; -COMMENT ON COLUMN recordings.deleted_at IS 'Soft delete timestamp for DSGVO audit trail'; - - --- ============================================== --- Transcriptions --- ============================================== --- Stores transcription metadata and full text. - -CREATE TABLE IF NOT EXISTS transcriptions ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - recording_id UUID NOT NULL REFERENCES recordings(id) ON DELETE CASCADE, - language VARCHAR(10) NOT NULL DEFAULT 'de', - model VARCHAR(100) NOT NULL DEFAULT 'large-v3', - status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'queued', 'processing', 'completed', 'failed')), - full_text TEXT, - word_count INTEGER DEFAULT 0, - confidence_score FLOAT, - vtt_path VARCHAR(1000), - srt_path VARCHAR(1000), - json_path VARCHAR(1000), - error_message TEXT, - processing_started_at TIMESTAMPTZ, - processing_completed_at TIMESTAMPTZ, - processing_duration_seconds INTEGER, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_transcriptions_recording ON transcriptions(recording_id); -CREATE INDEX IF NOT EXISTS idx_transcriptions_status ON transcriptions(status); -CREATE INDEX IF NOT EXISTS idx_transcriptions_language ON transcriptions(language); - --- Full-text search index for transcription content -CREATE INDEX IF NOT EXISTS idx_transcriptions_fulltext ON transcriptions USING gin(to_tsvector('german', COALESCE(full_text, ''))); - --- Comments -COMMENT ON TABLE transcriptions IS 'Whisper transcriptions with speaker diarization'; -COMMENT ON COLUMN transcriptions.model IS 'Whisper model used: tiny, base, small, medium, large-v3'; -COMMENT ON COLUMN transcriptions.vtt_path IS 'WebVTT subtitle file path in MinIO'; -COMMENT ON COLUMN transcriptions.srt_path IS 'SRT subtitle file path in MinIO'; -COMMENT ON COLUMN transcriptions.json_path IS 'Full JSON with segments and speakers in MinIO'; - - --- ============================================== --- Transcription Segments --- ============================================== --- Individual speech segments with speaker identification. - -CREATE TABLE IF NOT EXISTS transcription_segments ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - transcription_id UUID NOT NULL REFERENCES transcriptions(id) ON DELETE CASCADE, - segment_index INTEGER NOT NULL, - start_time_ms INTEGER NOT NULL, - end_time_ms INTEGER NOT NULL, - text TEXT NOT NULL, - speaker_id VARCHAR(50), - speaker_name VARCHAR(255), - confidence FLOAT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_segments_transcription ON transcription_segments(transcription_id); -CREATE INDEX IF NOT EXISTS idx_segments_speaker ON transcription_segments(speaker_id); -CREATE INDEX IF NOT EXISTS idx_segments_time ON transcription_segments(start_time_ms, end_time_ms); - --- Full-text search on segments -CREATE INDEX IF NOT EXISTS idx_segments_fulltext ON transcription_segments USING gin(to_tsvector('german', text)); - --- Comments -COMMENT ON TABLE transcription_segments IS 'Individual speech segments with timestamps and speaker IDs'; -COMMENT ON COLUMN transcription_segments.speaker_id IS 'pyannote speaker ID: SPEAKER_00, SPEAKER_01, etc.'; -COMMENT ON COLUMN transcription_segments.speaker_name IS 'Optionally mapped to actual participant name'; - - --- ============================================== --- Recording Audit Log (DSGVO) --- ============================================== --- Tracks all access and modifications for compliance. - -CREATE TABLE IF NOT EXISTS recording_audit_log ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - recording_id UUID, - transcription_id UUID, - user_id UUID, - action VARCHAR(100) NOT NULL CHECK (action IN ( - 'created', 'viewed', 'downloaded', 'shared', - 'transcription_started', 'transcription_completed', - 'deleted', 'retention_expired', 'consent_withdrawn' - )), - ip_address INET, - user_agent TEXT, - metadata JSONB, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_audit_recording ON recording_audit_log(recording_id); -CREATE INDEX IF NOT EXISTS idx_audit_transcription ON recording_audit_log(transcription_id); -CREATE INDEX IF NOT EXISTS idx_audit_user ON recording_audit_log(user_id); -CREATE INDEX IF NOT EXISTS idx_audit_action ON recording_audit_log(action); -CREATE INDEX IF NOT EXISTS idx_audit_created ON recording_audit_log(created_at); - --- Comments -COMMENT ON TABLE recording_audit_log IS 'DSGVO audit trail for all recording access'; -COMMENT ON COLUMN recording_audit_log.action IS 'Type of action performed'; -COMMENT ON COLUMN recording_audit_log.metadata IS 'Additional context (e.g., reason for deletion)'; - - --- ============================================== --- Transcription Queue (RQ Job Tracking) --- ============================================== --- Tracks pending and completed transcription jobs. - -CREATE TABLE IF NOT EXISTS transcription_queue ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - transcription_id UUID NOT NULL REFERENCES transcriptions(id) ON DELETE CASCADE, - job_id VARCHAR(255), - priority INTEGER DEFAULT 0, - status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'queued', 'processing', 'completed', 'failed', 'cancelled')), - worker_id VARCHAR(255), - attempts INTEGER DEFAULT 0, - max_attempts INTEGER DEFAULT 3, - error_message TEXT, - queued_at TIMESTAMPTZ, - started_at TIMESTAMPTZ, - completed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_queue_status ON transcription_queue(status); -CREATE INDEX IF NOT EXISTS idx_queue_priority ON transcription_queue(priority DESC, created_at ASC); -CREATE INDEX IF NOT EXISTS idx_queue_job ON transcription_queue(job_id); - --- Comments -COMMENT ON TABLE transcription_queue IS 'RQ job queue tracking for transcription workers'; - - --- ============================================== --- Trigger: Update updated_at timestamp --- ============================================== -CREATE OR REPLACE FUNCTION update_recording_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE 'plpgsql'; - --- Apply triggers -DROP TRIGGER IF EXISTS update_recordings_updated_at ON recordings; -CREATE TRIGGER update_recordings_updated_at - BEFORE UPDATE ON recordings - FOR EACH ROW - EXECUTE FUNCTION update_recording_updated_at(); - -DROP TRIGGER IF EXISTS update_transcriptions_updated_at ON transcriptions; -CREATE TRIGGER update_transcriptions_updated_at - BEFORE UPDATE ON transcriptions - FOR EACH ROW - EXECUTE FUNCTION update_recording_updated_at(); - -DROP TRIGGER IF EXISTS update_consents_updated_at ON meeting_recording_consents; -CREATE TRIGGER update_consents_updated_at - BEFORE UPDATE ON meeting_recording_consents - FOR EACH ROW - EXECUTE FUNCTION update_recording_updated_at(); - -DROP TRIGGER IF EXISTS update_queue_updated_at ON transcription_queue; -CREATE TRIGGER update_queue_updated_at - BEFORE UPDATE ON transcription_queue - FOR EACH ROW - EXECUTE FUNCTION update_recording_updated_at(); - - --- ============================================== --- View: Recording Overview --- ============================================== -CREATE OR REPLACE VIEW recording_overview AS -SELECT - r.id, - r.meeting_id, - r.title, - r.status as recording_status, - r.duration_seconds, - r.participant_count, - r.recorded_at, - r.retention_days, - r.recorded_at + (r.retention_days || ' days')::INTERVAL as retention_expires_at, - t.id as transcription_id, - t.status as transcription_status, - t.language, - t.word_count, - t.confidence_score, - c.all_participants_consented, - c.consent_type -FROM recordings r -LEFT JOIN transcriptions t ON t.recording_id = r.id -LEFT JOIN meeting_recording_consents c ON c.meeting_id = r.meeting_id -WHERE r.deleted_at IS NULL; - -COMMENT ON VIEW recording_overview IS 'Combined view of recordings with transcription and consent status'; - - --- ============================================== --- View: Pending Transcriptions --- ============================================== -CREATE OR REPLACE VIEW pending_transcriptions AS -SELECT - t.id, - t.recording_id, - r.storage_path, - r.audio_path, - t.language, - t.model, - q.priority, - q.attempts, - q.max_attempts, - q.created_at as queued_at -FROM transcriptions t -JOIN recordings r ON r.id = t.recording_id -LEFT JOIN transcription_queue q ON q.transcription_id = t.id -WHERE t.status IN ('pending', 'queued') - AND r.status = 'uploaded' - AND r.deleted_at IS NULL -ORDER BY q.priority DESC, q.created_at ASC; - -COMMENT ON VIEW pending_transcriptions IS 'Queue of transcriptions waiting to be processed'; - - --- ============================================== --- Function: Search Transcripts --- ============================================== -CREATE OR REPLACE FUNCTION search_transcripts( - p_query TEXT, - p_language VARCHAR(10) DEFAULT 'de', - p_limit INTEGER DEFAULT 20 -) -RETURNS TABLE ( - transcription_id UUID, - recording_id UUID, - meeting_id VARCHAR(255), - title VARCHAR(500), - segment_id UUID, - segment_text TEXT, - start_time_ms INTEGER, - end_time_ms INTEGER, - speaker_id VARCHAR(50), - relevance FLOAT -) AS $$ -BEGIN - RETURN QUERY - SELECT - t.id as transcription_id, - r.id as recording_id, - r.meeting_id, - r.title, - s.id as segment_id, - s.text as segment_text, - s.start_time_ms, - s.end_time_ms, - s.speaker_id, - ts_rank(to_tsvector('german', s.text), plainto_tsquery('german', p_query))::FLOAT as relevance - FROM transcription_segments s - JOIN transcriptions t ON t.id = s.transcription_id - JOIN recordings r ON r.id = t.recording_id - WHERE t.language = p_language - AND t.status = 'completed' - AND r.deleted_at IS NULL - AND to_tsvector('german', s.text) @@ plainto_tsquery('german', p_query) - ORDER BY relevance DESC - LIMIT p_limit; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION search_transcripts IS 'Full-text search across all transcription segments'; - - --- ============================================== --- Function: Cleanup Expired Recordings --- ============================================== --- Call periodically to soft-delete recordings past retention. - -CREATE OR REPLACE FUNCTION cleanup_expired_recordings() -RETURNS INTEGER AS $$ -DECLARE - v_count INTEGER; -BEGIN - WITH expired AS ( - UPDATE recordings - SET status = 'deleted', - deleted_at = NOW() - WHERE deleted_at IS NULL - AND status != 'deleted' - AND recorded_at + (retention_days || ' days')::INTERVAL < NOW() - RETURNING id - ) - SELECT COUNT(*) INTO v_count FROM expired; - - -- Log the cleanup action - IF v_count > 0 THEN - INSERT INTO recording_audit_log (action, metadata) - VALUES ('retention_expired', jsonb_build_object('count', v_count, 'timestamp', NOW())); - END IF; - - RETURN v_count; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION cleanup_expired_recordings IS 'Soft-deletes recordings past their retention period'; - - --- ============================================== --- Grant Permissions --- ============================================== --- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot; --- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot; --- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO breakpilot; - - --- ============================================== --- Migration Complete Message --- ============================================== -DO $$ -BEGIN - RAISE NOTICE 'Recording & Transcription tables created successfully!'; - RAISE NOTICE 'Tables: meeting_recording_consents, recordings, transcriptions, transcription_segments, recording_audit_log, transcription_queue'; - RAISE NOTICE 'Views: recording_overview, pending_transcriptions'; - RAISE NOTICE 'Functions: search_transcripts, cleanup_expired_recordings'; -END $$; diff --git a/docs-src/backend/migrations/add_unit_tables.sql b/docs-src/backend/migrations/add_unit_tables.sql deleted file mode 100644 index 1139c1f..0000000 --- a/docs-src/backend/migrations/add_unit_tables.sql +++ /dev/null @@ -1,410 +0,0 @@ --- ============================================== --- Breakpilot Drive - Educational Unit Tables Migration --- ============================================== --- Adds tables for the contextual learning unit system. --- Supports FlightPath and StationLoop templates. --- --- Execute with: --- psql -h localhost -U breakpilot -d breakpilot -f add_unit_tables.sql - --- Enable UUID extension if not already enabled -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- ============================================== --- Unit Definitions --- ============================================== --- Stores the configuration for each learning unit. --- JSON definition contains stops, interactions, vocab, etc. - -CREATE TABLE IF NOT EXISTS unit_definitions ( - unit_id VARCHAR(100) PRIMARY KEY, - template VARCHAR(50) NOT NULL CHECK (template IN ('flight_path', 'station_loop')), - version VARCHAR(20) NOT NULL, - locale VARCHAR(10)[] DEFAULT ARRAY['de-DE'], - grade_band VARCHAR(10)[] DEFAULT ARRAY['5', '6'], - duration_minutes INTEGER NOT NULL CHECK (duration_minutes >= 3 AND duration_minutes <= 20), - difficulty VARCHAR(20) DEFAULT 'base' CHECK (difficulty IN ('base', 'advanced')), - definition JSONB NOT NULL, - is_published BOOLEAN DEFAULT false, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Index for filtering -CREATE INDEX IF NOT EXISTS idx_unit_definitions_template ON unit_definitions(template); -CREATE INDEX IF NOT EXISTS idx_unit_definitions_published ON unit_definitions(is_published); -CREATE INDEX IF NOT EXISTS idx_unit_definitions_locale ON unit_definitions USING GIN(locale); -CREATE INDEX IF NOT EXISTS idx_unit_definitions_grade ON unit_definitions USING GIN(grade_band); - --- Comments -COMMENT ON TABLE unit_definitions IS 'Stores unit configurations for contextual learning experiences'; -COMMENT ON COLUMN unit_definitions.template IS 'Unit template type: flight_path (linear) or station_loop (hub-based)'; -COMMENT ON COLUMN unit_definitions.definition IS 'Complete JSON definition including stops, interactions, vocab, etc.'; -COMMENT ON COLUMN unit_definitions.grade_band IS 'Target grade levels (e.g., ["5", "6", "7"])'; - - --- ============================================== --- Unit Sessions --- ============================================== --- Records each unit session played by a student. - -CREATE TABLE IF NOT EXISTS unit_sessions ( - session_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - unit_id VARCHAR(100) NOT NULL REFERENCES unit_definitions(unit_id), - student_id UUID NOT NULL, - locale VARCHAR(10) DEFAULT 'de-DE', - difficulty VARCHAR(20) DEFAULT 'base', - started_at TIMESTAMPTZ DEFAULT NOW(), - completed_at TIMESTAMPTZ, - aborted_at TIMESTAMPTZ, - duration_seconds INTEGER, - completion_rate DECIMAL(3,2) CHECK (completion_rate >= 0 AND completion_rate <= 1), - precheck_score DECIMAL(3,2) CHECK (precheck_score >= 0 AND precheck_score <= 1), - postcheck_score DECIMAL(3,2) CHECK (postcheck_score >= 0 AND postcheck_score <= 1), - stops_completed INTEGER DEFAULT 0, - total_stops INTEGER DEFAULT 0, - session_token VARCHAR(500), - metadata JSONB, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes for common queries -CREATE INDEX IF NOT EXISTS idx_unit_sessions_student ON unit_sessions(student_id); -CREATE INDEX IF NOT EXISTS idx_unit_sessions_unit ON unit_sessions(unit_id); -CREATE INDEX IF NOT EXISTS idx_unit_sessions_completed ON unit_sessions(completed_at); -CREATE INDEX IF NOT EXISTS idx_unit_sessions_started ON unit_sessions(started_at DESC); - --- Comments -COMMENT ON TABLE unit_sessions IS 'Records individual unit playthrough sessions'; -COMMENT ON COLUMN unit_sessions.completion_rate IS 'Percentage of stops completed (0.0 to 1.0)'; -COMMENT ON COLUMN unit_sessions.precheck_score IS 'Score from pre-unit diagnostic quiz (0.0 to 1.0)'; -COMMENT ON COLUMN unit_sessions.postcheck_score IS 'Score from post-unit diagnostic quiz (0.0 to 1.0)'; - - --- ============================================== --- Unit Telemetry Events --- ============================================== --- Stores detailed telemetry events from unit sessions. - -CREATE TABLE IF NOT EXISTS unit_telemetry ( - id BIGSERIAL PRIMARY KEY, - session_id UUID NOT NULL REFERENCES unit_sessions(session_id) ON DELETE CASCADE, - event_type VARCHAR(50) NOT NULL, - stop_id VARCHAR(100), - event_timestamp TIMESTAMPTZ DEFAULT NOW(), - metrics JSONB, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes for analytics -CREATE INDEX IF NOT EXISTS idx_unit_telemetry_session ON unit_telemetry(session_id); -CREATE INDEX IF NOT EXISTS idx_unit_telemetry_type ON unit_telemetry(event_type); -CREATE INDEX IF NOT EXISTS idx_unit_telemetry_stop ON unit_telemetry(stop_id); -CREATE INDEX IF NOT EXISTS idx_unit_telemetry_timestamp ON unit_telemetry(event_timestamp); - --- Partitioning hint (for production with high volume) --- Consider partitioning by created_at for older data cleanup - --- Comments -COMMENT ON TABLE unit_telemetry IS 'Detailed telemetry events from unit sessions'; -COMMENT ON COLUMN unit_telemetry.event_type IS 'Event type: stop_completed, hint_used, state_change, etc.'; -COMMENT ON COLUMN unit_telemetry.metrics IS 'Event-specific metrics in JSON format'; - - --- ============================================== --- Unit Stop Metrics --- ============================================== --- Aggregated metrics per stop per session. - -CREATE TABLE IF NOT EXISTS unit_stop_metrics ( - id BIGSERIAL PRIMARY KEY, - session_id UUID NOT NULL REFERENCES unit_sessions(session_id) ON DELETE CASCADE, - stop_id VARCHAR(100) NOT NULL, - completed BOOLEAN DEFAULT false, - success BOOLEAN, - attempts INTEGER DEFAULT 0, - time_seconds DECIMAL(10,2), - hints_used TEXT[], - completed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - CONSTRAINT unique_session_stop UNIQUE(session_id, stop_id) -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_stop_metrics_session ON unit_stop_metrics(session_id); -CREATE INDEX IF NOT EXISTS idx_stop_metrics_stop ON unit_stop_metrics(stop_id); -CREATE INDEX IF NOT EXISTS idx_stop_metrics_success ON unit_stop_metrics(success); - --- Comments -COMMENT ON TABLE unit_stop_metrics IS 'Aggregated metrics for each stop in a unit session'; - - --- ============================================== --- Unit Misconceptions --- ============================================== --- Tracks detected misconceptions per student. - -CREATE TABLE IF NOT EXISTS unit_misconceptions ( - id BIGSERIAL PRIMARY KEY, - student_id UUID NOT NULL, - unit_id VARCHAR(100) NOT NULL REFERENCES unit_definitions(unit_id), - misconception_id VARCHAR(100) NOT NULL, - stop_id VARCHAR(100), - detected_at TIMESTAMPTZ DEFAULT NOW(), - addressed BOOLEAN DEFAULT false, - addressed_at TIMESTAMPTZ, - session_id UUID REFERENCES unit_sessions(session_id), - CONSTRAINT unique_student_misconception UNIQUE(student_id, unit_id, misconception_id) -); - --- Indexes -CREATE INDEX IF NOT EXISTS idx_misconceptions_student ON unit_misconceptions(student_id); -CREATE INDEX IF NOT EXISTS idx_misconceptions_unit ON unit_misconceptions(unit_id); -CREATE INDEX IF NOT EXISTS idx_misconceptions_addressed ON unit_misconceptions(addressed); - --- Comments -COMMENT ON TABLE unit_misconceptions IS 'Tracks detected misconceptions for targeted remediation'; - - --- ============================================== --- Trigger: Update updated_at timestamp --- ============================================== --- Reuse existing function if available, otherwise create -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'update_updated_at_column') THEN - CREATE FUNCTION update_updated_at_column() - RETURNS TRIGGER AS $func$ - BEGIN - NEW.updated_at = NOW(); - RETURN NEW; - END; - $func$ LANGUAGE plpgsql; - END IF; -END $$; - --- Apply triggers -DROP TRIGGER IF EXISTS update_unit_definitions_updated_at ON unit_definitions; -CREATE TRIGGER update_unit_definitions_updated_at - BEFORE UPDATE ON unit_definitions - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); - -DROP TRIGGER IF EXISTS update_unit_sessions_updated_at ON unit_sessions; -CREATE TRIGGER update_unit_sessions_updated_at - BEFORE UPDATE ON unit_sessions - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); - - --- ============================================== --- View: Unit Session Summary --- ============================================== -CREATE OR REPLACE VIEW unit_session_summary AS -SELECT - us.session_id, - us.unit_id, - ud.template, - us.student_id, - us.started_at, - us.completed_at, - us.duration_seconds, - us.completion_rate, - us.precheck_score, - us.postcheck_score, - CASE - WHEN us.precheck_score IS NOT NULL AND us.postcheck_score IS NOT NULL - THEN us.postcheck_score - us.precheck_score - ELSE NULL - END as learning_gain, - us.stops_completed, - us.total_stops, - CASE WHEN us.completed_at IS NOT NULL THEN true ELSE false END as is_completed -FROM unit_sessions us -JOIN unit_definitions ud ON us.unit_id = ud.unit_id; - -COMMENT ON VIEW unit_session_summary IS 'Summary view of unit sessions with learning gain calculation'; - - --- ============================================== --- View: Unit Analytics by Student --- ============================================== -CREATE OR REPLACE VIEW unit_student_analytics AS -SELECT - student_id, - COUNT(DISTINCT unit_id) as units_attempted, - COUNT(*) as total_sessions, - COUNT(*) FILTER (WHERE completed_at IS NOT NULL) as completed_sessions, - AVG(completion_rate) as avg_completion_rate, - AVG(precheck_score) as avg_precheck_score, - AVG(postcheck_score) as avg_postcheck_score, - AVG(CASE - WHEN precheck_score IS NOT NULL AND postcheck_score IS NOT NULL - THEN postcheck_score - precheck_score - ELSE NULL - END) as avg_learning_gain, - SUM(duration_seconds) / 60 as total_minutes_played, - MAX(completed_at) as last_completed_at -FROM unit_sessions -GROUP BY student_id; - -COMMENT ON VIEW unit_student_analytics IS 'Aggregated analytics per student across all units'; - - --- ============================================== --- View: Unit Performance --- ============================================== -CREATE OR REPLACE VIEW unit_performance AS -SELECT - ud.unit_id, - ud.template, - ud.difficulty, - COUNT(us.session_id) as total_sessions, - COUNT(*) FILTER (WHERE us.completed_at IS NOT NULL) as completed_sessions, - ROUND( - COUNT(*) FILTER (WHERE us.completed_at IS NOT NULL)::DECIMAL / - NULLIF(COUNT(us.session_id), 0) * 100, - 1 - ) as completion_percent, - AVG(us.duration_seconds) / 60 as avg_duration_minutes, - AVG(us.completion_rate) as avg_completion_rate, - AVG(CASE - WHEN us.precheck_score IS NOT NULL AND us.postcheck_score IS NOT NULL - THEN us.postcheck_score - us.precheck_score - ELSE NULL - END) as avg_learning_gain -FROM unit_definitions ud -LEFT JOIN unit_sessions us ON ud.unit_id = us.unit_id -GROUP BY ud.unit_id, ud.template, ud.difficulty; - -COMMENT ON VIEW unit_performance IS 'Performance metrics per unit for content optimization'; - - --- ============================================== --- Function: Get Recommended Units for Student --- ============================================== -CREATE OR REPLACE FUNCTION get_recommended_units( - p_student_id UUID, - p_grade VARCHAR(10) DEFAULT NULL, - p_locale VARCHAR(10) DEFAULT 'de-DE', - p_limit INTEGER DEFAULT 5 -) -RETURNS TABLE ( - unit_id VARCHAR(100), - template VARCHAR(50), - difficulty VARCHAR(20), - reason TEXT -) AS $$ -BEGIN - RETURN QUERY - SELECT - ud.unit_id, - ud.template, - ud.difficulty, - CASE - -- Never played - WHEN NOT EXISTS ( - SELECT 1 FROM unit_sessions us - WHERE us.student_id = p_student_id AND us.unit_id = ud.unit_id - ) THEN 'Neu: Noch nicht gespielt' - -- Played but not completed - WHEN NOT EXISTS ( - SELECT 1 FROM unit_sessions us - WHERE us.student_id = p_student_id - AND us.unit_id = ud.unit_id - AND us.completed_at IS NOT NULL - ) THEN 'Fortsetzen: Noch nicht abgeschlossen' - -- Completed with low postcheck score - WHEN EXISTS ( - SELECT 1 FROM unit_sessions us - WHERE us.student_id = p_student_id - AND us.unit_id = ud.unit_id - AND us.postcheck_score < 0.6 - ) THEN 'Wiederholen: Verständnis vertiefen' - ELSE 'Abgeschlossen' - END as reason - FROM unit_definitions ud - WHERE ud.is_published = true - AND (p_locale = ANY(ud.locale) OR p_locale IS NULL) - AND (p_grade = ANY(ud.grade_band) OR p_grade IS NULL) - ORDER BY - -- Prioritize: new > incomplete > low score > completed - CASE - WHEN NOT EXISTS ( - SELECT 1 FROM unit_sessions us - WHERE us.student_id = p_student_id AND us.unit_id = ud.unit_id - ) THEN 1 - WHEN NOT EXISTS ( - SELECT 1 FROM unit_sessions us - WHERE us.student_id = p_student_id - AND us.unit_id = ud.unit_id - AND us.completed_at IS NOT NULL - ) THEN 2 - WHEN EXISTS ( - SELECT 1 FROM unit_sessions us - WHERE us.student_id = p_student_id - AND us.unit_id = ud.unit_id - AND us.postcheck_score < 0.6 - ) THEN 3 - ELSE 4 - END, - ud.unit_id - LIMIT p_limit; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION get_recommended_units IS 'Returns recommended units for a student based on completion status'; - - --- ============================================== --- Sample Data: Demo Unit Definition --- ============================================== --- Insert a demo unit for testing (will be replaced by real content) - -INSERT INTO unit_definitions (unit_id, template, version, locale, grade_band, duration_minutes, difficulty, definition, is_published) -VALUES ( - 'demo_unit_v1', - 'flight_path', - '1.0.0', - ARRAY['de-DE'], - ARRAY['5', '6', '7'], - 5, - 'base', - '{ - "unit_id": "demo_unit_v1", - "template": "flight_path", - "version": "1.0.0", - "learning_objectives": ["Demo: Grundfunktion testen", "Demo: Navigation verstehen"], - "stops": [ - {"stop_id": "stop_1", "label": {"de-DE": "Start"}, "interaction": {"type": "aim_and_pass"}}, - {"stop_id": "stop_2", "label": {"de-DE": "Mitte"}, "interaction": {"type": "aim_and_pass"}}, - {"stop_id": "stop_3", "label": {"de-DE": "Ende"}, "interaction": {"type": "aim_and_pass"}} - ], - "teacher_controls": {"allow_skip": true, "allow_replay": true} - }'::jsonb, - true -) -ON CONFLICT (unit_id) DO UPDATE SET - definition = EXCLUDED.definition, - updated_at = NOW(); - - --- ============================================== --- Grant Permissions (adjust user as needed) --- ============================================== --- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO breakpilot; --- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO breakpilot; --- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO breakpilot; - - --- ============================================== --- Migration Complete Message --- ============================================== -DO $$ -BEGIN - RAISE NOTICE 'Breakpilot Drive unit tables created successfully!'; - RAISE NOTICE 'Tables: unit_definitions, unit_sessions, unit_telemetry, unit_stop_metrics, unit_misconceptions'; - RAISE NOTICE 'Views: unit_session_summary, unit_student_analytics, unit_performance'; - RAISE NOTICE 'Functions: get_recommended_units'; -END $$; diff --git a/docs-src/breakpilot-compliance-sdk/hardware/mac-mini/init-db.sql b/docs-src/breakpilot-compliance-sdk/hardware/mac-mini/init-db.sql deleted file mode 100644 index b08d0b0..0000000 --- a/docs-src/breakpilot-compliance-sdk/hardware/mac-mini/init-db.sql +++ /dev/null @@ -1,204 +0,0 @@ --- BreakPilot Compliance SDK - Database Initialization --- Mac Mini Deployment - --- Create extensions -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; - --- Schema: SDK State -CREATE TABLE IF NOT EXISTS sdk_state ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL UNIQUE, - state JSONB NOT NULL DEFAULT '{}', - version INTEGER NOT NULL DEFAULT 1, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); - --- Schema: Consents -CREATE TABLE IF NOT EXISTS consents ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - user_id VARCHAR(255) NOT NULL, - purpose VARCHAR(50) NOT NULL, - granted BOOLEAN NOT NULL DEFAULT false, - source VARCHAR(100), - ip_address VARCHAR(45), - user_agent TEXT, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - revoked_at TIMESTAMP WITH TIME ZONE, - - INDEX idx_consents_tenant (tenant_id), - INDEX idx_consents_user (tenant_id, user_id) -); - --- Schema: DSR Requests -CREATE TABLE IF NOT EXISTS dsr_requests ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - request_type VARCHAR(50) NOT NULL, - email VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - status VARCHAR(50) NOT NULL DEFAULT 'PENDING', - notes TEXT, - submitted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - deadline TIMESTAMP WITH TIME ZONE, - completed_at TIMESTAMP WITH TIME ZONE, - - INDEX idx_dsr_tenant (tenant_id), - INDEX idx_dsr_status (status) -); - --- Schema: Processing Activities (VVT) -CREATE TABLE IF NOT EXISTS processing_activities ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - purpose TEXT, - legal_basis VARCHAR(100), - data_categories TEXT[], - data_subjects TEXT[], - recipients TEXT[], - retention_period VARCHAR(100), - security_measures TEXT, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - INDEX idx_activities_tenant (tenant_id) -); - --- Schema: TOMs -CREATE TABLE IF NOT EXISTS toms ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - category VARCHAR(50) NOT NULL, - title VARCHAR(255) NOT NULL, - description TEXT, - implementation_status VARCHAR(50) DEFAULT 'PLANNED', - responsible VARCHAR(255), - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - INDEX idx_toms_tenant (tenant_id) -); - --- Schema: Controls -CREATE TABLE IF NOT EXISTS controls ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - control_id VARCHAR(50) NOT NULL, - name VARCHAR(255) NOT NULL, - domain VARCHAR(50), - description TEXT, - implementation_status VARCHAR(50) DEFAULT 'NOT_IMPLEMENTED', - responsible VARCHAR(255), - evidence_ids UUID[], - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - INDEX idx_controls_tenant (tenant_id), - UNIQUE (tenant_id, control_id) -); - --- Schema: Evidence -CREATE TABLE IF NOT EXISTS evidence ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - title VARCHAR(255) NOT NULL, - type VARCHAR(50) NOT NULL, - file_path VARCHAR(500), - description TEXT, - valid_from TIMESTAMP WITH TIME ZONE, - valid_until TIMESTAMP WITH TIME ZONE, - uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - INDEX idx_evidence_tenant (tenant_id) -); - --- Schema: Risks -CREATE TABLE IF NOT EXISTS risks ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - title VARCHAR(255) NOT NULL, - description TEXT, - likelihood INTEGER CHECK (likelihood BETWEEN 1 AND 5), - impact INTEGER CHECK (impact BETWEEN 1 AND 5), - severity VARCHAR(20), - status VARCHAR(50) DEFAULT 'IDENTIFIED', - mitigation TEXT, - control_ids UUID[], - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - INDEX idx_risks_tenant (tenant_id) -); - --- Schema: Security Findings -CREATE TABLE IF NOT EXISTS security_findings ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - tool VARCHAR(50) NOT NULL, - severity VARCHAR(20) NOT NULL, - title VARCHAR(255) NOT NULL, - description TEXT, - file_path VARCHAR(500), - line_number INTEGER, - recommendation TEXT, - status VARCHAR(50) DEFAULT 'OPEN', - cve VARCHAR(50), - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - INDEX idx_findings_tenant (tenant_id), - INDEX idx_findings_severity (severity) -); - --- Schema: Audit Log -CREATE TABLE IF NOT EXISTS audit_log ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tenant_id VARCHAR(255) NOT NULL, - user_id VARCHAR(255), - action VARCHAR(100) NOT NULL, - resource_type VARCHAR(100), - resource_id VARCHAR(255), - details JSONB, - ip_address VARCHAR(45), - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - INDEX idx_audit_tenant (tenant_id), - INDEX idx_audit_created (created_at) -); - --- Function: Update timestamp -CREATE OR REPLACE FUNCTION update_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = CURRENT_TIMESTAMP; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Triggers for updated_at -CREATE TRIGGER trg_sdk_state_updated - BEFORE UPDATE ON sdk_state - FOR EACH ROW EXECUTE FUNCTION update_updated_at(); - -CREATE TRIGGER trg_activities_updated - BEFORE UPDATE ON processing_activities - FOR EACH ROW EXECUTE FUNCTION update_updated_at(); - -CREATE TRIGGER trg_toms_updated - BEFORE UPDATE ON toms - FOR EACH ROW EXECUTE FUNCTION update_updated_at(); - -CREATE TRIGGER trg_controls_updated - BEFORE UPDATE ON controls - FOR EACH ROW EXECUTE FUNCTION update_updated_at(); - -CREATE TRIGGER trg_risks_updated - BEFORE UPDATE ON risks - FOR EACH ROW EXECUTE FUNCTION update_updated_at(); - --- Initial data -INSERT INTO sdk_state (tenant_id, state) -VALUES ('default', '{"completedSteps": [], "currentStep": "overview"}') -ON CONFLICT (tenant_id) DO NOTHING; diff --git a/docs-src/breakpilot-drive/Build/.gitkeep b/docs-src/breakpilot-drive/Build/.gitkeep deleted file mode 100644 index 1c21f4d..0000000 --- a/docs-src/breakpilot-drive/Build/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -# Placeholder fuer Unity WebGL Build -# Dieser Ordner wird vom Unity Build Process befuellt diff --git a/docs-src/consent-sdk/dist/angular/index.d.mts b/docs-src/consent-sdk/dist/angular/index.d.mts deleted file mode 100644 index a89bfe6..0000000 --- a/docs-src/consent-sdk/dist/angular/index.d.mts +++ /dev/null @@ -1,390 +0,0 @@ -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} - -/** - * Angular Integration fuer @breakpilot/consent-sdk - * - * @example - * ```typescript - * // app.module.ts - * import { ConsentModule } from '@breakpilot/consent-sdk/angular'; - * - * @NgModule({ - * imports: [ - * ConsentModule.forRoot({ - * apiEndpoint: 'https://consent.example.com/api/v1', - * siteId: 'site_abc123', - * }), - * ], - * }) - * export class AppModule {} - * ``` - */ - -/** - * ConsentService Interface fuer Angular DI - * - * @example - * ```typescript - * @Component({...}) - * export class MyComponent { - * constructor(private consent: ConsentService) { - * if (this.consent.hasConsent('analytics')) { - * // Analytics laden - * } - * } - * } - * ``` - */ -interface IConsentService { - /** Initialisiert? */ - readonly isInitialized: boolean; - /** Laedt noch? */ - readonly isLoading: boolean; - /** Banner sichtbar? */ - readonly isBannerVisible: boolean; - /** Aktueller Consent-Zustand */ - readonly consent: ConsentState | null; - /** Muss Consent eingeholt werden? */ - readonly needsConsent: boolean; - /** Prueft Consent fuer Kategorie */ - hasConsent(category: ConsentCategory): boolean; - /** Alle akzeptieren */ - acceptAll(): Promise; - /** Alle ablehnen */ - rejectAll(): Promise; - /** Auswahl speichern */ - saveSelection(categories: Partial): Promise; - /** Banner anzeigen */ - showBanner(): void; - /** Banner ausblenden */ - hideBanner(): void; - /** Einstellungen oeffnen */ - showSettings(): void; -} -/** - * ConsentService - Angular Service Wrapper - * - * Diese Klasse kann als Angular Service registriert werden: - * - * @example - * ```typescript - * // consent.service.ts - * import { Injectable } from '@angular/core'; - * import { ConsentServiceBase } from '@breakpilot/consent-sdk/angular'; - * - * @Injectable({ providedIn: 'root' }) - * export class ConsentService extends ConsentServiceBase { - * constructor() { - * super({ - * apiEndpoint: environment.consentApiEndpoint, - * siteId: environment.siteId, - * }); - * } - * } - * ``` - */ -declare class ConsentServiceBase implements IConsentService { - private manager; - private _consent; - private _isInitialized; - private _isLoading; - private _isBannerVisible; - private changeCallbacks; - private bannerShowCallbacks; - private bannerHideCallbacks; - constructor(config: ConsentConfig); - get isInitialized(): boolean; - get isLoading(): boolean; - get isBannerVisible(): boolean; - get consent(): ConsentState | null; - get needsConsent(): boolean; - hasConsent(category: ConsentCategory): boolean; - acceptAll(): Promise; - rejectAll(): Promise; - saveSelection(categories: Partial): Promise; - showBanner(): void; - hideBanner(): void; - showSettings(): void; - /** - * Registriert Callback fuer Consent-Aenderungen - * (fuer Angular Change Detection) - */ - onConsentChange(callback: (consent: ConsentState) => void): () => void; - /** - * Registriert Callback wenn Banner angezeigt wird - */ - onBannerShow(callback: () => void): () => void; - /** - * Registriert Callback wenn Banner ausgeblendet wird - */ - onBannerHide(callback: () => void): () => void; - private setupEventListeners; - private initialize; -} -/** - * Konfiguration fuer ConsentModule.forRoot() - */ -interface ConsentModuleConfig extends ConsentConfig { -} -/** - * Token fuer Dependency Injection - * Verwendung mit Angular @Inject(): - * - * @example - * ```typescript - * constructor(@Inject(CONSENT_CONFIG) private config: ConsentConfig) {} - * ``` - */ -declare const CONSENT_CONFIG = "CONSENT_CONFIG"; -declare const CONSENT_SERVICE = "CONSENT_SERVICE"; -/** - * Factory fuer ConsentService - * - * @example - * ```typescript - * // app.module.ts - * providers: [ - * { provide: CONSENT_CONFIG, useValue: { apiEndpoint: '...', siteId: '...' } }, - * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] }, - * ] - * ``` - */ -declare function consentServiceFactory(config: ConsentConfig): ConsentServiceBase; -/** - * ConsentModule - Angular Module - * - * Dies ist eine Template-Definition. Fuer echte Angular-Nutzung - * muss ein separates Angular Library Package erstellt werden. - * - * @example - * ```typescript - * // In einem Angular Library Package: - * @NgModule({ - * declarations: [ConsentBannerComponent, ConsentGateDirective], - * exports: [ConsentBannerComponent, ConsentGateDirective], - * }) - * export class ConsentModule { - * static forRoot(config: ConsentModuleConfig): ModuleWithProviders { - * return { - * ngModule: ConsentModule, - * providers: [ - * { provide: CONSENT_CONFIG, useValue: config }, - * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] }, - * ], - * }; - * } - * } - * ``` - */ -declare const ConsentModuleDefinition: { - /** - * Providers fuer Root-Module - */ - forRoot: (config: ConsentModuleConfig) => { - provide: string; - useValue: ConsentModuleConfig; - }; -}; -/** - * ConsentBannerComponent Template - * - * Fuer Angular Library Implementation: - * - * @example - * ```typescript - * @Component({ - * selector: 'bp-consent-banner', - * template: CONSENT_BANNER_TEMPLATE, - * styles: [CONSENT_BANNER_STYLES], - * }) - * export class ConsentBannerComponent { - * constructor(public consent: ConsentService) {} - * } - * ``` - */ -declare const CONSENT_BANNER_TEMPLATE = "\n\n \n\n"; -/** - * ConsentGateDirective Template - * - * @example - * ```typescript - * @Directive({ - * selector: '[bpConsentGate]', - * }) - * export class ConsentGateDirective implements OnInit, OnDestroy { - * @Input('bpConsentGate') category!: ConsentCategory; - * - * private unsubscribe?: () => void; - * - * constructor( - * private templateRef: TemplateRef, - * private viewContainer: ViewContainerRef, - * private consent: ConsentService - * ) {} - * - * ngOnInit() { - * this.updateView(); - * this.unsubscribe = this.consent.onConsentChange(() => this.updateView()); - * } - * - * ngOnDestroy() { - * this.unsubscribe?.(); - * } - * - * private updateView() { - * if (this.consent.hasConsent(this.category)) { - * this.viewContainer.createEmbeddedView(this.templateRef); - * } else { - * this.viewContainer.clear(); - * } - * } - * } - * ``` - */ -declare const CONSENT_GATE_USAGE = "\n\n
\n \n
\n\n\n\n \n\n\n

Bitte akzeptieren Sie Marketing-Cookies.

\n
\n"; - -export { CONSENT_BANNER_TEMPLATE, CONSENT_CONFIG, CONSENT_GATE_USAGE, CONSENT_SERVICE, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentModuleConfig, ConsentModuleDefinition, ConsentServiceBase, type ConsentState, type IConsentService, consentServiceFactory }; diff --git a/docs-src/consent-sdk/dist/angular/index.d.ts b/docs-src/consent-sdk/dist/angular/index.d.ts deleted file mode 100644 index a89bfe6..0000000 --- a/docs-src/consent-sdk/dist/angular/index.d.ts +++ /dev/null @@ -1,390 +0,0 @@ -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} - -/** - * Angular Integration fuer @breakpilot/consent-sdk - * - * @example - * ```typescript - * // app.module.ts - * import { ConsentModule } from '@breakpilot/consent-sdk/angular'; - * - * @NgModule({ - * imports: [ - * ConsentModule.forRoot({ - * apiEndpoint: 'https://consent.example.com/api/v1', - * siteId: 'site_abc123', - * }), - * ], - * }) - * export class AppModule {} - * ``` - */ - -/** - * ConsentService Interface fuer Angular DI - * - * @example - * ```typescript - * @Component({...}) - * export class MyComponent { - * constructor(private consent: ConsentService) { - * if (this.consent.hasConsent('analytics')) { - * // Analytics laden - * } - * } - * } - * ``` - */ -interface IConsentService { - /** Initialisiert? */ - readonly isInitialized: boolean; - /** Laedt noch? */ - readonly isLoading: boolean; - /** Banner sichtbar? */ - readonly isBannerVisible: boolean; - /** Aktueller Consent-Zustand */ - readonly consent: ConsentState | null; - /** Muss Consent eingeholt werden? */ - readonly needsConsent: boolean; - /** Prueft Consent fuer Kategorie */ - hasConsent(category: ConsentCategory): boolean; - /** Alle akzeptieren */ - acceptAll(): Promise; - /** Alle ablehnen */ - rejectAll(): Promise; - /** Auswahl speichern */ - saveSelection(categories: Partial): Promise; - /** Banner anzeigen */ - showBanner(): void; - /** Banner ausblenden */ - hideBanner(): void; - /** Einstellungen oeffnen */ - showSettings(): void; -} -/** - * ConsentService - Angular Service Wrapper - * - * Diese Klasse kann als Angular Service registriert werden: - * - * @example - * ```typescript - * // consent.service.ts - * import { Injectable } from '@angular/core'; - * import { ConsentServiceBase } from '@breakpilot/consent-sdk/angular'; - * - * @Injectable({ providedIn: 'root' }) - * export class ConsentService extends ConsentServiceBase { - * constructor() { - * super({ - * apiEndpoint: environment.consentApiEndpoint, - * siteId: environment.siteId, - * }); - * } - * } - * ``` - */ -declare class ConsentServiceBase implements IConsentService { - private manager; - private _consent; - private _isInitialized; - private _isLoading; - private _isBannerVisible; - private changeCallbacks; - private bannerShowCallbacks; - private bannerHideCallbacks; - constructor(config: ConsentConfig); - get isInitialized(): boolean; - get isLoading(): boolean; - get isBannerVisible(): boolean; - get consent(): ConsentState | null; - get needsConsent(): boolean; - hasConsent(category: ConsentCategory): boolean; - acceptAll(): Promise; - rejectAll(): Promise; - saveSelection(categories: Partial): Promise; - showBanner(): void; - hideBanner(): void; - showSettings(): void; - /** - * Registriert Callback fuer Consent-Aenderungen - * (fuer Angular Change Detection) - */ - onConsentChange(callback: (consent: ConsentState) => void): () => void; - /** - * Registriert Callback wenn Banner angezeigt wird - */ - onBannerShow(callback: () => void): () => void; - /** - * Registriert Callback wenn Banner ausgeblendet wird - */ - onBannerHide(callback: () => void): () => void; - private setupEventListeners; - private initialize; -} -/** - * Konfiguration fuer ConsentModule.forRoot() - */ -interface ConsentModuleConfig extends ConsentConfig { -} -/** - * Token fuer Dependency Injection - * Verwendung mit Angular @Inject(): - * - * @example - * ```typescript - * constructor(@Inject(CONSENT_CONFIG) private config: ConsentConfig) {} - * ``` - */ -declare const CONSENT_CONFIG = "CONSENT_CONFIG"; -declare const CONSENT_SERVICE = "CONSENT_SERVICE"; -/** - * Factory fuer ConsentService - * - * @example - * ```typescript - * // app.module.ts - * providers: [ - * { provide: CONSENT_CONFIG, useValue: { apiEndpoint: '...', siteId: '...' } }, - * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] }, - * ] - * ``` - */ -declare function consentServiceFactory(config: ConsentConfig): ConsentServiceBase; -/** - * ConsentModule - Angular Module - * - * Dies ist eine Template-Definition. Fuer echte Angular-Nutzung - * muss ein separates Angular Library Package erstellt werden. - * - * @example - * ```typescript - * // In einem Angular Library Package: - * @NgModule({ - * declarations: [ConsentBannerComponent, ConsentGateDirective], - * exports: [ConsentBannerComponent, ConsentGateDirective], - * }) - * export class ConsentModule { - * static forRoot(config: ConsentModuleConfig): ModuleWithProviders { - * return { - * ngModule: ConsentModule, - * providers: [ - * { provide: CONSENT_CONFIG, useValue: config }, - * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] }, - * ], - * }; - * } - * } - * ``` - */ -declare const ConsentModuleDefinition: { - /** - * Providers fuer Root-Module - */ - forRoot: (config: ConsentModuleConfig) => { - provide: string; - useValue: ConsentModuleConfig; - }; -}; -/** - * ConsentBannerComponent Template - * - * Fuer Angular Library Implementation: - * - * @example - * ```typescript - * @Component({ - * selector: 'bp-consent-banner', - * template: CONSENT_BANNER_TEMPLATE, - * styles: [CONSENT_BANNER_STYLES], - * }) - * export class ConsentBannerComponent { - * constructor(public consent: ConsentService) {} - * } - * ``` - */ -declare const CONSENT_BANNER_TEMPLATE = "\n\n \n\n"; -/** - * ConsentGateDirective Template - * - * @example - * ```typescript - * @Directive({ - * selector: '[bpConsentGate]', - * }) - * export class ConsentGateDirective implements OnInit, OnDestroy { - * @Input('bpConsentGate') category!: ConsentCategory; - * - * private unsubscribe?: () => void; - * - * constructor( - * private templateRef: TemplateRef, - * private viewContainer: ViewContainerRef, - * private consent: ConsentService - * ) {} - * - * ngOnInit() { - * this.updateView(); - * this.unsubscribe = this.consent.onConsentChange(() => this.updateView()); - * } - * - * ngOnDestroy() { - * this.unsubscribe?.(); - * } - * - * private updateView() { - * if (this.consent.hasConsent(this.category)) { - * this.viewContainer.createEmbeddedView(this.templateRef); - * } else { - * this.viewContainer.clear(); - * } - * } - * } - * ``` - */ -declare const CONSENT_GATE_USAGE = "\n\n
\n \n
\n\n\n\n \n\n\n

Bitte akzeptieren Sie Marketing-Cookies.

\n
\n"; - -export { CONSENT_BANNER_TEMPLATE, CONSENT_CONFIG, CONSENT_GATE_USAGE, CONSENT_SERVICE, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentModuleConfig, ConsentModuleDefinition, ConsentServiceBase, type ConsentState, type IConsentService, consentServiceFactory }; diff --git a/docs-src/consent-sdk/dist/angular/index.js b/docs-src/consent-sdk/dist/angular/index.js deleted file mode 100644 index 1ebc54a..0000000 --- a/docs-src/consent-sdk/dist/angular/index.js +++ /dev/null @@ -1,1330 +0,0 @@ -"use strict"; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - -// src/angular/index.ts -var index_exports = {}; -__export(index_exports, { - CONSENT_BANNER_TEMPLATE: () => CONSENT_BANNER_TEMPLATE, - CONSENT_CONFIG: () => CONSENT_CONFIG, - CONSENT_GATE_USAGE: () => CONSENT_GATE_USAGE, - CONSENT_SERVICE: () => CONSENT_SERVICE, - ConsentModuleDefinition: () => ConsentModuleDefinition, - ConsentServiceBase: () => ConsentServiceBase, - consentServiceFactory: () => consentServiceFactory -}); -module.exports = __toCommonJS(index_exports); - -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; - -// src/angular/index.ts -var ConsentServiceBase = class { - constructor(config) { - this._consent = null; - this._isInitialized = false; - this._isLoading = true; - this._isBannerVisible = false; - // Callbacks fuer Angular Change Detection - this.changeCallbacks = []; - this.bannerShowCallbacks = []; - this.bannerHideCallbacks = []; - this.manager = new ConsentManager(config); - this.setupEventListeners(); - this.initialize(); - } - // --------------------------------------------------------------------------- - // Getters - // --------------------------------------------------------------------------- - get isInitialized() { - return this._isInitialized; - } - get isLoading() { - return this._isLoading; - } - get isBannerVisible() { - return this._isBannerVisible; - } - get consent() { - return this._consent; - } - get needsConsent() { - return this.manager.needsConsent(); - } - // --------------------------------------------------------------------------- - // Methods - // --------------------------------------------------------------------------- - hasConsent(category) { - return this.manager.hasConsent(category); - } - async acceptAll() { - await this.manager.acceptAll(); - } - async rejectAll() { - await this.manager.rejectAll(); - } - async saveSelection(categories) { - await this.manager.setConsent(categories); - this.manager.hideBanner(); - } - showBanner() { - this.manager.showBanner(); - } - hideBanner() { - this.manager.hideBanner(); - } - showSettings() { - this.manager.showSettings(); - } - // --------------------------------------------------------------------------- - // Change Detection Support - // --------------------------------------------------------------------------- - /** - * Registriert Callback fuer Consent-Aenderungen - * (fuer Angular Change Detection) - */ - onConsentChange(callback) { - this.changeCallbacks.push(callback); - return () => { - const index = this.changeCallbacks.indexOf(callback); - if (index > -1) { - this.changeCallbacks.splice(index, 1); - } - }; - } - /** - * Registriert Callback wenn Banner angezeigt wird - */ - onBannerShow(callback) { - this.bannerShowCallbacks.push(callback); - return () => { - const index = this.bannerShowCallbacks.indexOf(callback); - if (index > -1) { - this.bannerShowCallbacks.splice(index, 1); - } - }; - } - /** - * Registriert Callback wenn Banner ausgeblendet wird - */ - onBannerHide(callback) { - this.bannerHideCallbacks.push(callback); - return () => { - const index = this.bannerHideCallbacks.indexOf(callback); - if (index > -1) { - this.bannerHideCallbacks.splice(index, 1); - } - }; - } - // --------------------------------------------------------------------------- - // Internal - // --------------------------------------------------------------------------- - setupEventListeners() { - this.manager.on("change", (consent) => { - this._consent = consent; - this.changeCallbacks.forEach((cb) => cb(consent)); - }); - this.manager.on("banner_show", () => { - this._isBannerVisible = true; - this.bannerShowCallbacks.forEach((cb) => cb()); - }); - this.manager.on("banner_hide", () => { - this._isBannerVisible = false; - this.bannerHideCallbacks.forEach((cb) => cb()); - }); - } - async initialize() { - try { - await this.manager.init(); - this._consent = this.manager.getConsent(); - this._isInitialized = true; - this._isBannerVisible = this.manager.isBannerVisible(); - } catch (error) { - console.error("Failed to initialize ConsentManager:", error); - } finally { - this._isLoading = false; - } - } -}; -var CONSENT_CONFIG = "CONSENT_CONFIG"; -var CONSENT_SERVICE = "CONSENT_SERVICE"; -function consentServiceFactory(config) { - return new ConsentServiceBase(config); -} -var ConsentModuleDefinition = { - /** - * Providers fuer Root-Module - */ - forRoot: (config) => ({ - provide: CONSENT_CONFIG, - useValue: config - }) -}; -var CONSENT_BANNER_TEMPLATE = ` - -`; -var CONSENT_GATE_USAGE = ` - -
- -
- - - - - - -

Bitte akzeptieren Sie Marketing-Cookies.

-
-`; -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - CONSENT_BANNER_TEMPLATE, - CONSENT_CONFIG, - CONSENT_GATE_USAGE, - CONSENT_SERVICE, - ConsentModuleDefinition, - ConsentServiceBase, - consentServiceFactory -}); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/angular/index.js.map b/docs-src/consent-sdk/dist/angular/index.js.map deleted file mode 100644 index d45f9b8..0000000 --- a/docs-src/consent-sdk/dist/angular/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/angular/index.ts","../../src/core/ConsentStorage.ts","../../src/core/ScriptBlocker.ts","../../src/core/ConsentAPI.ts","../../src/utils/EventEmitter.ts","../../src/utils/fingerprint.ts","../../src/version.ts","../../src/core/ConsentManager.ts"],"sourcesContent":["/**\n * Angular Integration fuer @breakpilot/consent-sdk\n *\n * @example\n * ```typescript\n * // app.module.ts\n * import { ConsentModule } from '@breakpilot/consent-sdk/angular';\n *\n * @NgModule({\n * imports: [\n * ConsentModule.forRoot({\n * apiEndpoint: 'https://consent.example.com/api/v1',\n * siteId: 'site_abc123',\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n\n// =============================================================================\n// NOTE: Angular SDK Structure\n// =============================================================================\n//\n// Angular hat ein komplexeres Build-System (ngc, ng-packagr).\n// Diese Datei definiert die Schnittstelle - fuer Production muss ein\n// separates Angular Library Package erstellt werden:\n//\n// ng generate library @breakpilot/consent-sdk-angular\n//\n// Die folgende Implementation ist fuer direkten Import vorgesehen.\n// =============================================================================\n\nimport { ConsentManager } from '../core/ConsentManager';\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n} from '../types';\n\n// =============================================================================\n// Angular Service Interface\n// =============================================================================\n\n/**\n * ConsentService Interface fuer Angular DI\n *\n * @example\n * ```typescript\n * @Component({...})\n * export class MyComponent {\n * constructor(private consent: ConsentService) {\n * if (this.consent.hasConsent('analytics')) {\n * // Analytics laden\n * }\n * }\n * }\n * ```\n */\nexport interface IConsentService {\n /** Initialisiert? */\n readonly isInitialized: boolean;\n\n /** Laedt noch? */\n readonly isLoading: boolean;\n\n /** Banner sichtbar? */\n readonly isBannerVisible: boolean;\n\n /** Aktueller Consent-Zustand */\n readonly consent: ConsentState | null;\n\n /** Muss Consent eingeholt werden? */\n readonly needsConsent: boolean;\n\n /** Prueft Consent fuer Kategorie */\n hasConsent(category: ConsentCategory): boolean;\n\n /** Alle akzeptieren */\n acceptAll(): Promise;\n\n /** Alle ablehnen */\n rejectAll(): Promise;\n\n /** Auswahl speichern */\n saveSelection(categories: Partial): Promise;\n\n /** Banner anzeigen */\n showBanner(): void;\n\n /** Banner ausblenden */\n hideBanner(): void;\n\n /** Einstellungen oeffnen */\n showSettings(): void;\n}\n\n// =============================================================================\n// ConsentService Implementation\n// =============================================================================\n\n/**\n * ConsentService - Angular Service Wrapper\n *\n * Diese Klasse kann als Angular Service registriert werden:\n *\n * @example\n * ```typescript\n * // consent.service.ts\n * import { Injectable } from '@angular/core';\n * import { ConsentServiceBase } from '@breakpilot/consent-sdk/angular';\n *\n * @Injectable({ providedIn: 'root' })\n * export class ConsentService extends ConsentServiceBase {\n * constructor() {\n * super({\n * apiEndpoint: environment.consentApiEndpoint,\n * siteId: environment.siteId,\n * });\n * }\n * }\n * ```\n */\nexport class ConsentServiceBase implements IConsentService {\n private manager: ConsentManager;\n private _consent: ConsentState | null = null;\n private _isInitialized = false;\n private _isLoading = true;\n private _isBannerVisible = false;\n\n // Callbacks fuer Angular Change Detection\n private changeCallbacks: Array<(consent: ConsentState) => void> = [];\n private bannerShowCallbacks: Array<() => void> = [];\n private bannerHideCallbacks: Array<() => void> = [];\n\n constructor(config: ConsentConfig) {\n this.manager = new ConsentManager(config);\n this.setupEventListeners();\n this.initialize();\n }\n\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get isLoading(): boolean {\n return this._isLoading;\n }\n\n get isBannerVisible(): boolean {\n return this._isBannerVisible;\n }\n\n get consent(): ConsentState | null {\n return this._consent;\n }\n\n get needsConsent(): boolean {\n return this.manager.needsConsent();\n }\n\n // ---------------------------------------------------------------------------\n // Methods\n // ---------------------------------------------------------------------------\n\n hasConsent(category: ConsentCategory): boolean {\n return this.manager.hasConsent(category);\n }\n\n async acceptAll(): Promise {\n await this.manager.acceptAll();\n }\n\n async rejectAll(): Promise {\n await this.manager.rejectAll();\n }\n\n async saveSelection(categories: Partial): Promise {\n await this.manager.setConsent(categories);\n this.manager.hideBanner();\n }\n\n showBanner(): void {\n this.manager.showBanner();\n }\n\n hideBanner(): void {\n this.manager.hideBanner();\n }\n\n showSettings(): void {\n this.manager.showSettings();\n }\n\n // ---------------------------------------------------------------------------\n // Change Detection Support\n // ---------------------------------------------------------------------------\n\n /**\n * Registriert Callback fuer Consent-Aenderungen\n * (fuer Angular Change Detection)\n */\n onConsentChange(callback: (consent: ConsentState) => void): () => void {\n this.changeCallbacks.push(callback);\n return () => {\n const index = this.changeCallbacks.indexOf(callback);\n if (index > -1) {\n this.changeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Registriert Callback wenn Banner angezeigt wird\n */\n onBannerShow(callback: () => void): () => void {\n this.bannerShowCallbacks.push(callback);\n return () => {\n const index = this.bannerShowCallbacks.indexOf(callback);\n if (index > -1) {\n this.bannerShowCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Registriert Callback wenn Banner ausgeblendet wird\n */\n onBannerHide(callback: () => void): () => void {\n this.bannerHideCallbacks.push(callback);\n return () => {\n const index = this.bannerHideCallbacks.indexOf(callback);\n if (index > -1) {\n this.bannerHideCallbacks.splice(index, 1);\n }\n };\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private setupEventListeners(): void {\n this.manager.on('change', (consent) => {\n this._consent = consent;\n this.changeCallbacks.forEach((cb) => cb(consent));\n });\n\n this.manager.on('banner_show', () => {\n this._isBannerVisible = true;\n this.bannerShowCallbacks.forEach((cb) => cb());\n });\n\n this.manager.on('banner_hide', () => {\n this._isBannerVisible = false;\n this.bannerHideCallbacks.forEach((cb) => cb());\n });\n }\n\n private async initialize(): Promise {\n try {\n await this.manager.init();\n this._consent = this.manager.getConsent();\n this._isInitialized = true;\n this._isBannerVisible = this.manager.isBannerVisible();\n } catch (error) {\n console.error('Failed to initialize ConsentManager:', error);\n } finally {\n this._isLoading = false;\n }\n }\n}\n\n// =============================================================================\n// Angular Module Configuration\n// =============================================================================\n\n/**\n * Konfiguration fuer ConsentModule.forRoot()\n */\nexport interface ConsentModuleConfig extends ConsentConfig {}\n\n/**\n * Token fuer Dependency Injection\n * Verwendung mit Angular @Inject():\n *\n * @example\n * ```typescript\n * constructor(@Inject(CONSENT_CONFIG) private config: ConsentConfig) {}\n * ```\n */\nexport const CONSENT_CONFIG = 'CONSENT_CONFIG';\nexport const CONSENT_SERVICE = 'CONSENT_SERVICE';\n\n// =============================================================================\n// Factory Functions fuer Angular DI\n// =============================================================================\n\n/**\n * Factory fuer ConsentService\n *\n * @example\n * ```typescript\n * // app.module.ts\n * providers: [\n * { provide: CONSENT_CONFIG, useValue: { apiEndpoint: '...', siteId: '...' } },\n * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },\n * ]\n * ```\n */\nexport function consentServiceFactory(config: ConsentConfig): ConsentServiceBase {\n return new ConsentServiceBase(config);\n}\n\n// =============================================================================\n// Angular Module Definition (Template)\n// =============================================================================\n\n/**\n * ConsentModule - Angular Module\n *\n * Dies ist eine Template-Definition. Fuer echte Angular-Nutzung\n * muss ein separates Angular Library Package erstellt werden.\n *\n * @example\n * ```typescript\n * // In einem Angular Library Package:\n * @NgModule({\n * declarations: [ConsentBannerComponent, ConsentGateDirective],\n * exports: [ConsentBannerComponent, ConsentGateDirective],\n * })\n * export class ConsentModule {\n * static forRoot(config: ConsentModuleConfig): ModuleWithProviders {\n * return {\n * ngModule: ConsentModule,\n * providers: [\n * { provide: CONSENT_CONFIG, useValue: config },\n * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },\n * ],\n * };\n * }\n * }\n * ```\n */\nexport const ConsentModuleDefinition = {\n /**\n * Providers fuer Root-Module\n */\n forRoot: (config: ConsentModuleConfig) => ({\n provide: CONSENT_CONFIG,\n useValue: config,\n }),\n};\n\n// =============================================================================\n// Component Templates (fuer Angular Library)\n// =============================================================================\n\n/**\n * ConsentBannerComponent Template\n *\n * Fuer Angular Library Implementation:\n *\n * @example\n * ```typescript\n * @Component({\n * selector: 'bp-consent-banner',\n * template: CONSENT_BANNER_TEMPLATE,\n * styles: [CONSENT_BANNER_STYLES],\n * })\n * export class ConsentBannerComponent {\n * constructor(public consent: ConsentService) {}\n * }\n * ```\n */\nexport const CONSENT_BANNER_TEMPLATE = `\n\n \n\n`;\n\n/**\n * ConsentGateDirective Template\n *\n * @example\n * ```typescript\n * @Directive({\n * selector: '[bpConsentGate]',\n * })\n * export class ConsentGateDirective implements OnInit, OnDestroy {\n * @Input('bpConsentGate') category!: ConsentCategory;\n *\n * private unsubscribe?: () => void;\n *\n * constructor(\n * private templateRef: TemplateRef,\n * private viewContainer: ViewContainerRef,\n * private consent: ConsentService\n * ) {}\n *\n * ngOnInit() {\n * this.updateView();\n * this.unsubscribe = this.consent.onConsentChange(() => this.updateView());\n * }\n *\n * ngOnDestroy() {\n * this.unsubscribe?.();\n * }\n *\n * private updateView() {\n * if (this.consent.hasConsent(this.category)) {\n * this.viewContainer.createEmbeddedView(this.templateRef);\n * } else {\n * this.viewContainer.clear();\n * }\n * }\n * }\n * ```\n */\nexport const CONSENT_GATE_USAGE = `\n\n
\n \n
\n\n\n\n \n\n\n

Bitte akzeptieren Sie Marketing-Cookies.

\n
\n`;\n\n// =============================================================================\n// RxJS Observable Wrapper (Optional)\n// =============================================================================\n\n/**\n * RxJS Observable Wrapper fuer ConsentService\n *\n * Fuer Projekte die RxJS bevorzugen:\n *\n * @example\n * ```typescript\n * import { BehaviorSubject, Observable } from 'rxjs';\n *\n * export class ConsentServiceRx extends ConsentServiceBase {\n * private consentSubject = new BehaviorSubject(null);\n * private bannerVisibleSubject = new BehaviorSubject(false);\n *\n * consent$ = this.consentSubject.asObservable();\n * isBannerVisible$ = this.bannerVisibleSubject.asObservable();\n *\n * constructor(config: ConsentConfig) {\n * super(config);\n * this.onConsentChange((c) => this.consentSubject.next(c));\n * this.onBannerShow(() => this.bannerVisibleSubject.next(true));\n * this.onBannerHide(() => this.bannerVisibleSubject.next(false));\n * }\n * }\n * ```\n */\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport type { ConsentConfig, ConsentState, ConsentCategory, ConsentCategories };\n","/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/angular/index.mjs b/docs-src/consent-sdk/dist/angular/index.mjs deleted file mode 100644 index 3241274..0000000 --- a/docs-src/consent-sdk/dist/angular/index.mjs +++ /dev/null @@ -1,1297 +0,0 @@ -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; - -// src/angular/index.ts -var ConsentServiceBase = class { - constructor(config) { - this._consent = null; - this._isInitialized = false; - this._isLoading = true; - this._isBannerVisible = false; - // Callbacks fuer Angular Change Detection - this.changeCallbacks = []; - this.bannerShowCallbacks = []; - this.bannerHideCallbacks = []; - this.manager = new ConsentManager(config); - this.setupEventListeners(); - this.initialize(); - } - // --------------------------------------------------------------------------- - // Getters - // --------------------------------------------------------------------------- - get isInitialized() { - return this._isInitialized; - } - get isLoading() { - return this._isLoading; - } - get isBannerVisible() { - return this._isBannerVisible; - } - get consent() { - return this._consent; - } - get needsConsent() { - return this.manager.needsConsent(); - } - // --------------------------------------------------------------------------- - // Methods - // --------------------------------------------------------------------------- - hasConsent(category) { - return this.manager.hasConsent(category); - } - async acceptAll() { - await this.manager.acceptAll(); - } - async rejectAll() { - await this.manager.rejectAll(); - } - async saveSelection(categories) { - await this.manager.setConsent(categories); - this.manager.hideBanner(); - } - showBanner() { - this.manager.showBanner(); - } - hideBanner() { - this.manager.hideBanner(); - } - showSettings() { - this.manager.showSettings(); - } - // --------------------------------------------------------------------------- - // Change Detection Support - // --------------------------------------------------------------------------- - /** - * Registriert Callback fuer Consent-Aenderungen - * (fuer Angular Change Detection) - */ - onConsentChange(callback) { - this.changeCallbacks.push(callback); - return () => { - const index = this.changeCallbacks.indexOf(callback); - if (index > -1) { - this.changeCallbacks.splice(index, 1); - } - }; - } - /** - * Registriert Callback wenn Banner angezeigt wird - */ - onBannerShow(callback) { - this.bannerShowCallbacks.push(callback); - return () => { - const index = this.bannerShowCallbacks.indexOf(callback); - if (index > -1) { - this.bannerShowCallbacks.splice(index, 1); - } - }; - } - /** - * Registriert Callback wenn Banner ausgeblendet wird - */ - onBannerHide(callback) { - this.bannerHideCallbacks.push(callback); - return () => { - const index = this.bannerHideCallbacks.indexOf(callback); - if (index > -1) { - this.bannerHideCallbacks.splice(index, 1); - } - }; - } - // --------------------------------------------------------------------------- - // Internal - // --------------------------------------------------------------------------- - setupEventListeners() { - this.manager.on("change", (consent) => { - this._consent = consent; - this.changeCallbacks.forEach((cb) => cb(consent)); - }); - this.manager.on("banner_show", () => { - this._isBannerVisible = true; - this.bannerShowCallbacks.forEach((cb) => cb()); - }); - this.manager.on("banner_hide", () => { - this._isBannerVisible = false; - this.bannerHideCallbacks.forEach((cb) => cb()); - }); - } - async initialize() { - try { - await this.manager.init(); - this._consent = this.manager.getConsent(); - this._isInitialized = true; - this._isBannerVisible = this.manager.isBannerVisible(); - } catch (error) { - console.error("Failed to initialize ConsentManager:", error); - } finally { - this._isLoading = false; - } - } -}; -var CONSENT_CONFIG = "CONSENT_CONFIG"; -var CONSENT_SERVICE = "CONSENT_SERVICE"; -function consentServiceFactory(config) { - return new ConsentServiceBase(config); -} -var ConsentModuleDefinition = { - /** - * Providers fuer Root-Module - */ - forRoot: (config) => ({ - provide: CONSENT_CONFIG, - useValue: config - }) -}; -var CONSENT_BANNER_TEMPLATE = ` - -`; -var CONSENT_GATE_USAGE = ` - -
- -
- - - - - - -

Bitte akzeptieren Sie Marketing-Cookies.

-
-`; -export { - CONSENT_BANNER_TEMPLATE, - CONSENT_CONFIG, - CONSENT_GATE_USAGE, - CONSENT_SERVICE, - ConsentModuleDefinition, - ConsentServiceBase, - consentServiceFactory -}; -//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/angular/index.mjs.map b/docs-src/consent-sdk/dist/angular/index.mjs.map deleted file mode 100644 index 639fd84..0000000 --- a/docs-src/consent-sdk/dist/angular/index.mjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/core/ConsentStorage.ts","../../src/core/ScriptBlocker.ts","../../src/core/ConsentAPI.ts","../../src/utils/EventEmitter.ts","../../src/utils/fingerprint.ts","../../src/version.ts","../../src/core/ConsentManager.ts","../../src/angular/index.ts"],"sourcesContent":["/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n","/**\n * Angular Integration fuer @breakpilot/consent-sdk\n *\n * @example\n * ```typescript\n * // app.module.ts\n * import { ConsentModule } from '@breakpilot/consent-sdk/angular';\n *\n * @NgModule({\n * imports: [\n * ConsentModule.forRoot({\n * apiEndpoint: 'https://consent.example.com/api/v1',\n * siteId: 'site_abc123',\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n\n// =============================================================================\n// NOTE: Angular SDK Structure\n// =============================================================================\n//\n// Angular hat ein komplexeres Build-System (ngc, ng-packagr).\n// Diese Datei definiert die Schnittstelle - fuer Production muss ein\n// separates Angular Library Package erstellt werden:\n//\n// ng generate library @breakpilot/consent-sdk-angular\n//\n// Die folgende Implementation ist fuer direkten Import vorgesehen.\n// =============================================================================\n\nimport { ConsentManager } from '../core/ConsentManager';\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n} from '../types';\n\n// =============================================================================\n// Angular Service Interface\n// =============================================================================\n\n/**\n * ConsentService Interface fuer Angular DI\n *\n * @example\n * ```typescript\n * @Component({...})\n * export class MyComponent {\n * constructor(private consent: ConsentService) {\n * if (this.consent.hasConsent('analytics')) {\n * // Analytics laden\n * }\n * }\n * }\n * ```\n */\nexport interface IConsentService {\n /** Initialisiert? */\n readonly isInitialized: boolean;\n\n /** Laedt noch? */\n readonly isLoading: boolean;\n\n /** Banner sichtbar? */\n readonly isBannerVisible: boolean;\n\n /** Aktueller Consent-Zustand */\n readonly consent: ConsentState | null;\n\n /** Muss Consent eingeholt werden? */\n readonly needsConsent: boolean;\n\n /** Prueft Consent fuer Kategorie */\n hasConsent(category: ConsentCategory): boolean;\n\n /** Alle akzeptieren */\n acceptAll(): Promise;\n\n /** Alle ablehnen */\n rejectAll(): Promise;\n\n /** Auswahl speichern */\n saveSelection(categories: Partial): Promise;\n\n /** Banner anzeigen */\n showBanner(): void;\n\n /** Banner ausblenden */\n hideBanner(): void;\n\n /** Einstellungen oeffnen */\n showSettings(): void;\n}\n\n// =============================================================================\n// ConsentService Implementation\n// =============================================================================\n\n/**\n * ConsentService - Angular Service Wrapper\n *\n * Diese Klasse kann als Angular Service registriert werden:\n *\n * @example\n * ```typescript\n * // consent.service.ts\n * import { Injectable } from '@angular/core';\n * import { ConsentServiceBase } from '@breakpilot/consent-sdk/angular';\n *\n * @Injectable({ providedIn: 'root' })\n * export class ConsentService extends ConsentServiceBase {\n * constructor() {\n * super({\n * apiEndpoint: environment.consentApiEndpoint,\n * siteId: environment.siteId,\n * });\n * }\n * }\n * ```\n */\nexport class ConsentServiceBase implements IConsentService {\n private manager: ConsentManager;\n private _consent: ConsentState | null = null;\n private _isInitialized = false;\n private _isLoading = true;\n private _isBannerVisible = false;\n\n // Callbacks fuer Angular Change Detection\n private changeCallbacks: Array<(consent: ConsentState) => void> = [];\n private bannerShowCallbacks: Array<() => void> = [];\n private bannerHideCallbacks: Array<() => void> = [];\n\n constructor(config: ConsentConfig) {\n this.manager = new ConsentManager(config);\n this.setupEventListeners();\n this.initialize();\n }\n\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get isLoading(): boolean {\n return this._isLoading;\n }\n\n get isBannerVisible(): boolean {\n return this._isBannerVisible;\n }\n\n get consent(): ConsentState | null {\n return this._consent;\n }\n\n get needsConsent(): boolean {\n return this.manager.needsConsent();\n }\n\n // ---------------------------------------------------------------------------\n // Methods\n // ---------------------------------------------------------------------------\n\n hasConsent(category: ConsentCategory): boolean {\n return this.manager.hasConsent(category);\n }\n\n async acceptAll(): Promise {\n await this.manager.acceptAll();\n }\n\n async rejectAll(): Promise {\n await this.manager.rejectAll();\n }\n\n async saveSelection(categories: Partial): Promise {\n await this.manager.setConsent(categories);\n this.manager.hideBanner();\n }\n\n showBanner(): void {\n this.manager.showBanner();\n }\n\n hideBanner(): void {\n this.manager.hideBanner();\n }\n\n showSettings(): void {\n this.manager.showSettings();\n }\n\n // ---------------------------------------------------------------------------\n // Change Detection Support\n // ---------------------------------------------------------------------------\n\n /**\n * Registriert Callback fuer Consent-Aenderungen\n * (fuer Angular Change Detection)\n */\n onConsentChange(callback: (consent: ConsentState) => void): () => void {\n this.changeCallbacks.push(callback);\n return () => {\n const index = this.changeCallbacks.indexOf(callback);\n if (index > -1) {\n this.changeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Registriert Callback wenn Banner angezeigt wird\n */\n onBannerShow(callback: () => void): () => void {\n this.bannerShowCallbacks.push(callback);\n return () => {\n const index = this.bannerShowCallbacks.indexOf(callback);\n if (index > -1) {\n this.bannerShowCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Registriert Callback wenn Banner ausgeblendet wird\n */\n onBannerHide(callback: () => void): () => void {\n this.bannerHideCallbacks.push(callback);\n return () => {\n const index = this.bannerHideCallbacks.indexOf(callback);\n if (index > -1) {\n this.bannerHideCallbacks.splice(index, 1);\n }\n };\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private setupEventListeners(): void {\n this.manager.on('change', (consent) => {\n this._consent = consent;\n this.changeCallbacks.forEach((cb) => cb(consent));\n });\n\n this.manager.on('banner_show', () => {\n this._isBannerVisible = true;\n this.bannerShowCallbacks.forEach((cb) => cb());\n });\n\n this.manager.on('banner_hide', () => {\n this._isBannerVisible = false;\n this.bannerHideCallbacks.forEach((cb) => cb());\n });\n }\n\n private async initialize(): Promise {\n try {\n await this.manager.init();\n this._consent = this.manager.getConsent();\n this._isInitialized = true;\n this._isBannerVisible = this.manager.isBannerVisible();\n } catch (error) {\n console.error('Failed to initialize ConsentManager:', error);\n } finally {\n this._isLoading = false;\n }\n }\n}\n\n// =============================================================================\n// Angular Module Configuration\n// =============================================================================\n\n/**\n * Konfiguration fuer ConsentModule.forRoot()\n */\nexport interface ConsentModuleConfig extends ConsentConfig {}\n\n/**\n * Token fuer Dependency Injection\n * Verwendung mit Angular @Inject():\n *\n * @example\n * ```typescript\n * constructor(@Inject(CONSENT_CONFIG) private config: ConsentConfig) {}\n * ```\n */\nexport const CONSENT_CONFIG = 'CONSENT_CONFIG';\nexport const CONSENT_SERVICE = 'CONSENT_SERVICE';\n\n// =============================================================================\n// Factory Functions fuer Angular DI\n// =============================================================================\n\n/**\n * Factory fuer ConsentService\n *\n * @example\n * ```typescript\n * // app.module.ts\n * providers: [\n * { provide: CONSENT_CONFIG, useValue: { apiEndpoint: '...', siteId: '...' } },\n * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },\n * ]\n * ```\n */\nexport function consentServiceFactory(config: ConsentConfig): ConsentServiceBase {\n return new ConsentServiceBase(config);\n}\n\n// =============================================================================\n// Angular Module Definition (Template)\n// =============================================================================\n\n/**\n * ConsentModule - Angular Module\n *\n * Dies ist eine Template-Definition. Fuer echte Angular-Nutzung\n * muss ein separates Angular Library Package erstellt werden.\n *\n * @example\n * ```typescript\n * // In einem Angular Library Package:\n * @NgModule({\n * declarations: [ConsentBannerComponent, ConsentGateDirective],\n * exports: [ConsentBannerComponent, ConsentGateDirective],\n * })\n * export class ConsentModule {\n * static forRoot(config: ConsentModuleConfig): ModuleWithProviders {\n * return {\n * ngModule: ConsentModule,\n * providers: [\n * { provide: CONSENT_CONFIG, useValue: config },\n * { provide: CONSENT_SERVICE, useFactory: consentServiceFactory, deps: [CONSENT_CONFIG] },\n * ],\n * };\n * }\n * }\n * ```\n */\nexport const ConsentModuleDefinition = {\n /**\n * Providers fuer Root-Module\n */\n forRoot: (config: ConsentModuleConfig) => ({\n provide: CONSENT_CONFIG,\n useValue: config,\n }),\n};\n\n// =============================================================================\n// Component Templates (fuer Angular Library)\n// =============================================================================\n\n/**\n * ConsentBannerComponent Template\n *\n * Fuer Angular Library Implementation:\n *\n * @example\n * ```typescript\n * @Component({\n * selector: 'bp-consent-banner',\n * template: CONSENT_BANNER_TEMPLATE,\n * styles: [CONSENT_BANNER_STYLES],\n * })\n * export class ConsentBannerComponent {\n * constructor(public consent: ConsentService) {}\n * }\n * ```\n */\nexport const CONSENT_BANNER_TEMPLATE = `\n\n \n\n`;\n\n/**\n * ConsentGateDirective Template\n *\n * @example\n * ```typescript\n * @Directive({\n * selector: '[bpConsentGate]',\n * })\n * export class ConsentGateDirective implements OnInit, OnDestroy {\n * @Input('bpConsentGate') category!: ConsentCategory;\n *\n * private unsubscribe?: () => void;\n *\n * constructor(\n * private templateRef: TemplateRef,\n * private viewContainer: ViewContainerRef,\n * private consent: ConsentService\n * ) {}\n *\n * ngOnInit() {\n * this.updateView();\n * this.unsubscribe = this.consent.onConsentChange(() => this.updateView());\n * }\n *\n * ngOnDestroy() {\n * this.unsubscribe?.();\n * }\n *\n * private updateView() {\n * if (this.consent.hasConsent(this.category)) {\n * this.viewContainer.createEmbeddedView(this.templateRef);\n * } else {\n * this.viewContainer.clear();\n * }\n * }\n * }\n * ```\n */\nexport const CONSENT_GATE_USAGE = `\n\n
\n \n
\n\n\n\n \n\n\n

Bitte akzeptieren Sie Marketing-Cookies.

\n
\n`;\n\n// =============================================================================\n// RxJS Observable Wrapper (Optional)\n// =============================================================================\n\n/**\n * RxJS Observable Wrapper fuer ConsentService\n *\n * Fuer Projekte die RxJS bevorzugen:\n *\n * @example\n * ```typescript\n * import { BehaviorSubject, Observable } from 'rxjs';\n *\n * export class ConsentServiceRx extends ConsentServiceBase {\n * private consentSubject = new BehaviorSubject(null);\n * private bannerVisibleSubject = new BehaviorSubject(false);\n *\n * consent$ = this.consentSubject.asObservable();\n * isBannerVisible$ = this.bannerVisibleSubject.asObservable();\n *\n * constructor(config: ConsentConfig) {\n * super(config);\n * this.onConsentChange((c) => this.consentSubject.next(c));\n * this.onBannerShow(() => this.bannerVisibleSubject.next(true));\n * this.onBannerHide(() => this.bannerVisibleSubject.next(false));\n * }\n * }\n * ```\n */\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport type { ConsentConfig, ConsentState, ConsentCategory, ConsentCategories };\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/index.d.mts b/docs-src/consent-sdk/dist/index.d.mts deleted file mode 100644 index 495baff..0000000 --- a/docs-src/consent-sdk/dist/index.d.mts +++ /dev/null @@ -1,707 +0,0 @@ -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * Minimaler Consent-Input fuer setConsent() - */ -type ConsentInput = Partial | { - categories?: Partial; - vendors?: ConsentVendors; -}; -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} -/** - * Cookie-Information - */ -interface CookieInfo { - /** Cookie-Name */ - name: string; - /** Cookie-Domain */ - domain: string; - /** Ablaufzeit (z.B. "2 Jahre", "Session") */ - expiration: string; - /** Speichertyp */ - type: 'http' | 'localStorage' | 'sessionStorage' | 'indexedDB'; - /** Beschreibung */ - description: string; -} -/** - * Vendor-Definition - */ -interface ConsentVendor { - /** Eindeutige Vendor-ID */ - id: string; - /** Anzeigename */ - name: string; - /** Kategorie */ - category: ConsentCategory; - /** IAB TCF Purposes (falls relevant) */ - purposes?: number[]; - /** Legitimate Interests */ - legitimateInterests?: number[]; - /** Cookie-Liste */ - cookies: CookieInfo[]; - /** Link zur Datenschutzerklaerung */ - privacyPolicyUrl: string; - /** Datenaufbewahrung */ - dataRetention?: string; - /** Datentransfer (z.B. "USA (EU-US DPF)", "EU") */ - dataTransfer?: string; -} -/** - * API-Antwort fuer Consent-Erstellung - */ -interface ConsentAPIResponse { - consentId: string; - timestamp: string; - expiresAt: string; - version: string; -} -/** - * API-Antwort fuer Site-Konfiguration - */ -interface SiteConfigResponse { - siteId: string; - siteName: string; - categories: CategoryConfig[]; - ui: ConsentUIConfig; - legal: LegalConfig; - tcf?: TCFConfig; -} -/** - * Kategorie-Konfiguration vom Server - */ -interface CategoryConfig { - id: ConsentCategory; - name: Record; - description: Record; - required: boolean; - vendors: ConsentVendor[]; -} -/** - * Rechtliche Konfiguration - */ -interface LegalConfig { - privacyPolicyUrl: string; - imprintUrl: string; - dpo?: { - name: string; - email: string; - }; -} -/** - * Event-Typen - */ -type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error'; -/** - * Event-Listener Callback - */ -type ConsentEventCallback = (data: T) => void; -/** - * Event-Daten fuer verschiedene Events - */ -type ConsentEventData = { - init: ConsentState | null; - change: ConsentState; - accept_all: ConsentState; - reject_all: ConsentState; - save_selection: ConsentState; - banner_show: undefined; - banner_hide: undefined; - settings_open: undefined; - settings_close: undefined; - vendor_enable: string; - vendor_disable: string; - error: Error; -}; -/** - * Storage-Adapter Interface - */ -interface ConsentStorageAdapter { - /** Consent laden */ - get(): ConsentState | null; - /** Consent speichern */ - set(consent: ConsentState): void; - /** Consent loeschen */ - clear(): void; - /** Pruefen ob Consent existiert */ - exists(): boolean; -} -/** - * Uebersetzungsstruktur - */ -interface ConsentTranslations { - title: string; - description: string; - acceptAll: string; - rejectAll: string; - settings: string; - saveSelection: string; - close: string; - categories: { - [K in ConsentCategory]: { - name: string; - description: string; - }; - }; - footer: { - privacyPolicy: string; - imprint: string; - cookieDetails: string; - }; - accessibility: { - closeButton: string; - categoryToggle: string; - requiredCategory: string; - }; -} -/** - * Alle unterstuetzten Sprachen - */ -type SupportedLanguage = 'de' | 'en' | 'fr' | 'es' | 'it' | 'nl' | 'pl' | 'pt' | 'cs' | 'da' | 'el' | 'fi' | 'hu' | 'ro' | 'sk' | 'sl' | 'sv'; - -/** - * ConsentManager - Hauptklasse fuer das Consent Management - * - * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. - */ - -/** - * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung - */ -declare class ConsentManager { - private config; - private storage; - private scriptBlocker; - private api; - private events; - private currentConsent; - private initialized; - private bannerVisible; - private deviceFingerprint; - constructor(config: ConsentConfig); - /** - * SDK initialisieren - */ - init(): Promise; - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category: ConsentCategory): boolean; - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId: string): boolean; - /** - * Aktuellen Consent-State abrufen - */ - getConsent(): ConsentState | null; - /** - * Consent setzen - */ - setConsent(input: ConsentInput): Promise; - /** - * Alle Kategorien akzeptieren - */ - acceptAll(): Promise; - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - rejectAll(): Promise; - /** - * Alle Einwilligungen widerrufen - */ - revokeAll(): Promise; - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - exportConsent(): Promise; - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent(): boolean; - /** - * Banner anzeigen - */ - showBanner(): void; - /** - * Banner verstecken - */ - hideBanner(): void; - /** - * Einstellungs-Modal oeffnen - */ - showSettings(): void; - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible(): boolean; - /** - * Event-Listener registrieren - */ - on(event: T, callback: ConsentEventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: T, callback: ConsentEventCallback): void; - /** - * Konfiguration zusammenfuehren - */ - private mergeConfig; - /** - * Consent-Input normalisieren - */ - private normalizeConsentInput; - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - private applyConsent; - /** - * Google Consent Mode v2 aktualisieren - */ - private updateGoogleConsentMode; - /** - * Pruefen ob Consent abgelaufen - */ - private isConsentExpired; - /** - * Event emittieren - */ - private emit; - /** - * Fehler behandeln - */ - private handleError; - /** - * Debug-Logging - */ - private log; - /** - * SDK-Version abrufen - */ - static getVersion(): string; -} - -/** - * ConsentStorage - Lokale Speicherung des Consent-Status - * - * Speichert Consent-Daten im localStorage mit HMAC-Signatur - * zur Manipulationserkennung. - */ - -/** - * ConsentStorage - Persistente Speicherung - */ -declare class ConsentStorage { - private config; - private storageKey; - constructor(config: ConsentConfig); - /** - * Consent laden - */ - get(): ConsentState | null; - /** - * Consent speichern - */ - set(consent: ConsentState): void; - /** - * Consent loeschen - */ - clear(): void; - /** - * Pruefen ob Consent existiert - */ - exists(): boolean; - /** - * Consent als Cookie setzen - */ - private setCookie; - /** - * Cookie loeschen - */ - private clearCookie; - /** - * Signatur generieren - */ - private generateSignature; - /** - * Signatur verifizieren - */ - private verifySignature; - /** - * Einfache Hash-Funktion (djb2) - */ - private simpleHash; - /** - * Debug-Logging - */ - private log; -} - -/** - * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird - * - * Verwendet das data-consent Attribut zur Identifikation von - * Skripten, die erst nach Consent geladen werden duerfen. - * - * Beispiel: - * - */ - -/** - * ScriptBlocker - Verwaltet Script-Blocking - */ -declare class ScriptBlocker { - private config; - private observer; - private enabledCategories; - private processedElements; - constructor(config: ConsentConfig); - /** - * Initialisieren und Observer starten - */ - init(): void; - /** - * Kategorie aktivieren - */ - enableCategory(category: ConsentCategory): void; - /** - * Kategorie deaktivieren - */ - disableCategory(category: ConsentCategory): void; - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll(): void; - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category: ConsentCategory): boolean; - /** - * Observer stoppen - */ - destroy(): void; - /** - * Bestehende Elemente verarbeiten - */ - private processExistingElements; - /** - * Element verarbeiten - */ - private processElement; - /** - * Script-Element verarbeiten - */ - private processScript; - /** - * iFrame-Element verarbeiten - */ - private processIframe; - /** - * Script aktivieren - */ - private activateScript; - /** - * iFrame aktivieren - */ - private activateIframe; - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - private showPlaceholder; - /** - * Alle Elemente einer Kategorie aktivieren - */ - private activateCategory; - /** - * Kategorie-Name fuer UI - */ - private getCategoryName; - /** - * Debug-Logging - */ - private log; -} - -/** - * ConsentAPI - Kommunikation mit dem Consent-Backend - * - * Sendet Consent-Entscheidungen an das Backend zur - * revisionssicheren Speicherung. - */ - -/** - * Request-Payload fuer Consent-Speicherung - */ -interface SaveConsentRequest { - siteId: string; - userId?: string; - deviceFingerprint: string; - consent: ConsentState; - metadata?: { - userAgent?: string; - language?: string; - screenResolution?: string; - platform?: string; - appVersion?: string; - }; -} -/** - * ConsentAPI - Backend-Kommunikation - */ -declare class ConsentAPI { - private config; - private baseUrl; - constructor(config: ConsentConfig); - /** - * Consent speichern - */ - saveConsent(request: SaveConsentRequest): Promise; - /** - * Consent abrufen - */ - getConsent(siteId: string, deviceFingerprint: string): Promise; - /** - * Consent widerrufen - */ - revokeConsent(consentId: string): Promise; - /** - * Site-Konfiguration abrufen - */ - getSiteConfig(siteId: string): Promise; - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - exportConsent(userId: string): Promise; - /** - * Fetch mit Standard-Headers - */ - private fetch; - /** - * Signatur-Headers generieren (HMAC) - */ - private getSignatureHeaders; - /** - * Einfache Hash-Funktion (djb2) - */ - private simpleHash; - /** - * Debug-Logging - */ - private log; -} - -/** - * EventEmitter - Typsicherer Event-Handler - */ -type EventCallback = (data: T) => void; -declare class EventEmitter = Record> { - private listeners; - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event: K, callback: EventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: K, callback: EventCallback): void; - /** - * Event emittieren - */ - emit(event: K, data: Events[K]): void; - /** - * Einmaligen Listener registrieren - */ - once(event: K, callback: EventCallback): () => void; - /** - * Alle Listener entfernen - */ - clear(): void; - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event: K): void; - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event: K): number; -} - -/** - * Device Fingerprinting - Datenschutzkonform - * - * Generiert einen anonymen Fingerprint OHNE: - * - Canvas Fingerprinting - * - WebGL Fingerprinting - * - Audio Fingerprinting - * - Hardware-spezifische IDs - * - * Verwendet nur: - * - User Agent - * - Sprache - * - Bildschirmaufloesung - * - Zeitzone - * - Platform - */ -/** - * Datenschutzkonformen Fingerprint generieren - * - * Der Fingerprint ist: - * - Nicht eindeutig (viele Nutzer teilen sich denselben) - * - Nicht persistent (aendert sich bei Browser-Updates) - * - Nicht invasiv (keine Canvas/WebGL/Audio) - * - Anonymisiert (SHA-256 Hash) - */ -declare function generateFingerprint(): Promise; -/** - * Synchrone Version (mit einfachem Hash) - */ -declare function generateFingerprintSync(): string; - -/** - * SDK Version - */ -declare const SDK_VERSION = "1.0.0"; - -export { type BannerLayout, type BannerPosition, type BannerTheme, type CategoryConfig, ConsentAPI, type ConsentAPIResponse, type ConsentBehaviorConfig, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentEventCallback, type ConsentEventData, type ConsentEventType, type ConsentInput, ConsentManager, type ConsentState, ConsentStorage, type ConsentStorageAdapter, type ConsentTranslations, type ConsentUIConfig, type ConsentVendor, type ConsentVendors, type CookieInfo, EventEmitter, type LegalConfig, type PWAConfig, SDK_VERSION, ScriptBlocker, type SiteConfigResponse, type SupportedLanguage, type TCFConfig, ConsentManager as default, generateFingerprint, generateFingerprintSync }; diff --git a/docs-src/consent-sdk/dist/index.d.ts b/docs-src/consent-sdk/dist/index.d.ts deleted file mode 100644 index 495baff..0000000 --- a/docs-src/consent-sdk/dist/index.d.ts +++ /dev/null @@ -1,707 +0,0 @@ -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * Minimaler Consent-Input fuer setConsent() - */ -type ConsentInput = Partial | { - categories?: Partial; - vendors?: ConsentVendors; -}; -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} -/** - * Cookie-Information - */ -interface CookieInfo { - /** Cookie-Name */ - name: string; - /** Cookie-Domain */ - domain: string; - /** Ablaufzeit (z.B. "2 Jahre", "Session") */ - expiration: string; - /** Speichertyp */ - type: 'http' | 'localStorage' | 'sessionStorage' | 'indexedDB'; - /** Beschreibung */ - description: string; -} -/** - * Vendor-Definition - */ -interface ConsentVendor { - /** Eindeutige Vendor-ID */ - id: string; - /** Anzeigename */ - name: string; - /** Kategorie */ - category: ConsentCategory; - /** IAB TCF Purposes (falls relevant) */ - purposes?: number[]; - /** Legitimate Interests */ - legitimateInterests?: number[]; - /** Cookie-Liste */ - cookies: CookieInfo[]; - /** Link zur Datenschutzerklaerung */ - privacyPolicyUrl: string; - /** Datenaufbewahrung */ - dataRetention?: string; - /** Datentransfer (z.B. "USA (EU-US DPF)", "EU") */ - dataTransfer?: string; -} -/** - * API-Antwort fuer Consent-Erstellung - */ -interface ConsentAPIResponse { - consentId: string; - timestamp: string; - expiresAt: string; - version: string; -} -/** - * API-Antwort fuer Site-Konfiguration - */ -interface SiteConfigResponse { - siteId: string; - siteName: string; - categories: CategoryConfig[]; - ui: ConsentUIConfig; - legal: LegalConfig; - tcf?: TCFConfig; -} -/** - * Kategorie-Konfiguration vom Server - */ -interface CategoryConfig { - id: ConsentCategory; - name: Record; - description: Record; - required: boolean; - vendors: ConsentVendor[]; -} -/** - * Rechtliche Konfiguration - */ -interface LegalConfig { - privacyPolicyUrl: string; - imprintUrl: string; - dpo?: { - name: string; - email: string; - }; -} -/** - * Event-Typen - */ -type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error'; -/** - * Event-Listener Callback - */ -type ConsentEventCallback = (data: T) => void; -/** - * Event-Daten fuer verschiedene Events - */ -type ConsentEventData = { - init: ConsentState | null; - change: ConsentState; - accept_all: ConsentState; - reject_all: ConsentState; - save_selection: ConsentState; - banner_show: undefined; - banner_hide: undefined; - settings_open: undefined; - settings_close: undefined; - vendor_enable: string; - vendor_disable: string; - error: Error; -}; -/** - * Storage-Adapter Interface - */ -interface ConsentStorageAdapter { - /** Consent laden */ - get(): ConsentState | null; - /** Consent speichern */ - set(consent: ConsentState): void; - /** Consent loeschen */ - clear(): void; - /** Pruefen ob Consent existiert */ - exists(): boolean; -} -/** - * Uebersetzungsstruktur - */ -interface ConsentTranslations { - title: string; - description: string; - acceptAll: string; - rejectAll: string; - settings: string; - saveSelection: string; - close: string; - categories: { - [K in ConsentCategory]: { - name: string; - description: string; - }; - }; - footer: { - privacyPolicy: string; - imprint: string; - cookieDetails: string; - }; - accessibility: { - closeButton: string; - categoryToggle: string; - requiredCategory: string; - }; -} -/** - * Alle unterstuetzten Sprachen - */ -type SupportedLanguage = 'de' | 'en' | 'fr' | 'es' | 'it' | 'nl' | 'pl' | 'pt' | 'cs' | 'da' | 'el' | 'fi' | 'hu' | 'ro' | 'sk' | 'sl' | 'sv'; - -/** - * ConsentManager - Hauptklasse fuer das Consent Management - * - * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. - */ - -/** - * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung - */ -declare class ConsentManager { - private config; - private storage; - private scriptBlocker; - private api; - private events; - private currentConsent; - private initialized; - private bannerVisible; - private deviceFingerprint; - constructor(config: ConsentConfig); - /** - * SDK initialisieren - */ - init(): Promise; - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category: ConsentCategory): boolean; - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId: string): boolean; - /** - * Aktuellen Consent-State abrufen - */ - getConsent(): ConsentState | null; - /** - * Consent setzen - */ - setConsent(input: ConsentInput): Promise; - /** - * Alle Kategorien akzeptieren - */ - acceptAll(): Promise; - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - rejectAll(): Promise; - /** - * Alle Einwilligungen widerrufen - */ - revokeAll(): Promise; - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - exportConsent(): Promise; - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent(): boolean; - /** - * Banner anzeigen - */ - showBanner(): void; - /** - * Banner verstecken - */ - hideBanner(): void; - /** - * Einstellungs-Modal oeffnen - */ - showSettings(): void; - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible(): boolean; - /** - * Event-Listener registrieren - */ - on(event: T, callback: ConsentEventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: T, callback: ConsentEventCallback): void; - /** - * Konfiguration zusammenfuehren - */ - private mergeConfig; - /** - * Consent-Input normalisieren - */ - private normalizeConsentInput; - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - private applyConsent; - /** - * Google Consent Mode v2 aktualisieren - */ - private updateGoogleConsentMode; - /** - * Pruefen ob Consent abgelaufen - */ - private isConsentExpired; - /** - * Event emittieren - */ - private emit; - /** - * Fehler behandeln - */ - private handleError; - /** - * Debug-Logging - */ - private log; - /** - * SDK-Version abrufen - */ - static getVersion(): string; -} - -/** - * ConsentStorage - Lokale Speicherung des Consent-Status - * - * Speichert Consent-Daten im localStorage mit HMAC-Signatur - * zur Manipulationserkennung. - */ - -/** - * ConsentStorage - Persistente Speicherung - */ -declare class ConsentStorage { - private config; - private storageKey; - constructor(config: ConsentConfig); - /** - * Consent laden - */ - get(): ConsentState | null; - /** - * Consent speichern - */ - set(consent: ConsentState): void; - /** - * Consent loeschen - */ - clear(): void; - /** - * Pruefen ob Consent existiert - */ - exists(): boolean; - /** - * Consent als Cookie setzen - */ - private setCookie; - /** - * Cookie loeschen - */ - private clearCookie; - /** - * Signatur generieren - */ - private generateSignature; - /** - * Signatur verifizieren - */ - private verifySignature; - /** - * Einfache Hash-Funktion (djb2) - */ - private simpleHash; - /** - * Debug-Logging - */ - private log; -} - -/** - * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird - * - * Verwendet das data-consent Attribut zur Identifikation von - * Skripten, die erst nach Consent geladen werden duerfen. - * - * Beispiel: - * - */ - -/** - * ScriptBlocker - Verwaltet Script-Blocking - */ -declare class ScriptBlocker { - private config; - private observer; - private enabledCategories; - private processedElements; - constructor(config: ConsentConfig); - /** - * Initialisieren und Observer starten - */ - init(): void; - /** - * Kategorie aktivieren - */ - enableCategory(category: ConsentCategory): void; - /** - * Kategorie deaktivieren - */ - disableCategory(category: ConsentCategory): void; - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll(): void; - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category: ConsentCategory): boolean; - /** - * Observer stoppen - */ - destroy(): void; - /** - * Bestehende Elemente verarbeiten - */ - private processExistingElements; - /** - * Element verarbeiten - */ - private processElement; - /** - * Script-Element verarbeiten - */ - private processScript; - /** - * iFrame-Element verarbeiten - */ - private processIframe; - /** - * Script aktivieren - */ - private activateScript; - /** - * iFrame aktivieren - */ - private activateIframe; - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - private showPlaceholder; - /** - * Alle Elemente einer Kategorie aktivieren - */ - private activateCategory; - /** - * Kategorie-Name fuer UI - */ - private getCategoryName; - /** - * Debug-Logging - */ - private log; -} - -/** - * ConsentAPI - Kommunikation mit dem Consent-Backend - * - * Sendet Consent-Entscheidungen an das Backend zur - * revisionssicheren Speicherung. - */ - -/** - * Request-Payload fuer Consent-Speicherung - */ -interface SaveConsentRequest { - siteId: string; - userId?: string; - deviceFingerprint: string; - consent: ConsentState; - metadata?: { - userAgent?: string; - language?: string; - screenResolution?: string; - platform?: string; - appVersion?: string; - }; -} -/** - * ConsentAPI - Backend-Kommunikation - */ -declare class ConsentAPI { - private config; - private baseUrl; - constructor(config: ConsentConfig); - /** - * Consent speichern - */ - saveConsent(request: SaveConsentRequest): Promise; - /** - * Consent abrufen - */ - getConsent(siteId: string, deviceFingerprint: string): Promise; - /** - * Consent widerrufen - */ - revokeConsent(consentId: string): Promise; - /** - * Site-Konfiguration abrufen - */ - getSiteConfig(siteId: string): Promise; - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - exportConsent(userId: string): Promise; - /** - * Fetch mit Standard-Headers - */ - private fetch; - /** - * Signatur-Headers generieren (HMAC) - */ - private getSignatureHeaders; - /** - * Einfache Hash-Funktion (djb2) - */ - private simpleHash; - /** - * Debug-Logging - */ - private log; -} - -/** - * EventEmitter - Typsicherer Event-Handler - */ -type EventCallback = (data: T) => void; -declare class EventEmitter = Record> { - private listeners; - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event: K, callback: EventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: K, callback: EventCallback): void; - /** - * Event emittieren - */ - emit(event: K, data: Events[K]): void; - /** - * Einmaligen Listener registrieren - */ - once(event: K, callback: EventCallback): () => void; - /** - * Alle Listener entfernen - */ - clear(): void; - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event: K): void; - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event: K): number; -} - -/** - * Device Fingerprinting - Datenschutzkonform - * - * Generiert einen anonymen Fingerprint OHNE: - * - Canvas Fingerprinting - * - WebGL Fingerprinting - * - Audio Fingerprinting - * - Hardware-spezifische IDs - * - * Verwendet nur: - * - User Agent - * - Sprache - * - Bildschirmaufloesung - * - Zeitzone - * - Platform - */ -/** - * Datenschutzkonformen Fingerprint generieren - * - * Der Fingerprint ist: - * - Nicht eindeutig (viele Nutzer teilen sich denselben) - * - Nicht persistent (aendert sich bei Browser-Updates) - * - Nicht invasiv (keine Canvas/WebGL/Audio) - * - Anonymisiert (SHA-256 Hash) - */ -declare function generateFingerprint(): Promise; -/** - * Synchrone Version (mit einfachem Hash) - */ -declare function generateFingerprintSync(): string; - -/** - * SDK Version - */ -declare const SDK_VERSION = "1.0.0"; - -export { type BannerLayout, type BannerPosition, type BannerTheme, type CategoryConfig, ConsentAPI, type ConsentAPIResponse, type ConsentBehaviorConfig, type ConsentCategories, type ConsentCategory, type ConsentConfig, type ConsentEventCallback, type ConsentEventData, type ConsentEventType, type ConsentInput, ConsentManager, type ConsentState, ConsentStorage, type ConsentStorageAdapter, type ConsentTranslations, type ConsentUIConfig, type ConsentVendor, type ConsentVendors, type CookieInfo, EventEmitter, type LegalConfig, type PWAConfig, SDK_VERSION, ScriptBlocker, type SiteConfigResponse, type SupportedLanguage, type TCFConfig, ConsentManager as default, generateFingerprint, generateFingerprintSync }; diff --git a/docs-src/consent-sdk/dist/index.js b/docs-src/consent-sdk/dist/index.js deleted file mode 100644 index ae34da6..0000000 --- a/docs-src/consent-sdk/dist/index.js +++ /dev/null @@ -1,1142 +0,0 @@ -"use strict"; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - -// src/index.ts -var index_exports = {}; -__export(index_exports, { - ConsentAPI: () => ConsentAPI, - ConsentManager: () => ConsentManager, - ConsentStorage: () => ConsentStorage, - EventEmitter: () => EventEmitter, - SDK_VERSION: () => SDK_VERSION, - ScriptBlocker: () => ScriptBlocker, - default: () => ConsentManager, - generateFingerprint: () => generateFingerprint, - generateFingerprintSync: () => generateFingerprintSync -}); -module.exports = __toCommonJS(index_exports); - -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} -function generateFingerprintSync() { - const components = getComponents(); - const combined = components.join("|"); - const hash = simpleHash(combined); - return `fp_${hash}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - ConsentAPI, - ConsentManager, - ConsentStorage, - EventEmitter, - SDK_VERSION, - ScriptBlocker, - generateFingerprint, - generateFingerprintSync -}); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/index.js.map b/docs-src/consent-sdk/dist/index.js.map deleted file mode 100644 index ed1d69b..0000000 --- a/docs-src/consent-sdk/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/index.ts","../src/core/ConsentStorage.ts","../src/core/ScriptBlocker.ts","../src/core/ConsentAPI.ts","../src/utils/EventEmitter.ts","../src/utils/fingerprint.ts","../src/version.ts","../src/core/ConsentManager.ts"],"sourcesContent":["/**\n * @breakpilot/consent-sdk\n *\n * DSGVO/TTDSG-konformes Consent Management SDK\n *\n * @example\n * ```typescript\n * import { ConsentManager } from '@breakpilot/consent-sdk';\n *\n * const consent = new ConsentManager({\n * apiEndpoint: 'https://consent.example.com/api/v1',\n * siteId: 'site_abc123',\n * });\n *\n * await consent.init();\n *\n * if (consent.hasConsent('analytics')) {\n * // Analytics laden\n * }\n * ```\n */\n\n// Core\nexport { ConsentManager } from './core/ConsentManager';\nexport { ConsentStorage } from './core/ConsentStorage';\nexport { ScriptBlocker } from './core/ScriptBlocker';\nexport { ConsentAPI } from './core/ConsentAPI';\n\n// Utils\nexport { EventEmitter } from './utils/EventEmitter';\nexport { generateFingerprint, generateFingerprintSync } from './utils/fingerprint';\n\n// Types\nexport type {\n // Categories\n ConsentCategory,\n ConsentCategories,\n ConsentVendors,\n\n // State\n ConsentState,\n ConsentInput,\n\n // Config\n ConsentConfig,\n ConsentUIConfig,\n ConsentBehaviorConfig,\n TCFConfig,\n PWAConfig,\n BannerPosition,\n BannerLayout,\n BannerTheme,\n\n // Vendors\n ConsentVendor,\n CookieInfo,\n\n // API\n ConsentAPIResponse,\n SiteConfigResponse,\n CategoryConfig,\n LegalConfig,\n\n // Events\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n\n // Storage\n ConsentStorageAdapter,\n\n // Translations\n ConsentTranslations,\n SupportedLanguage,\n} from './types';\n\n// Version\nexport { SDK_VERSION } from './version';\n\n// Default export\nexport { ConsentManager as default } from './core/ConsentManager';\n","/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/index.mjs b/docs-src/consent-sdk/dist/index.mjs deleted file mode 100644 index 0e489c1..0000000 --- a/docs-src/consent-sdk/dist/index.mjs +++ /dev/null @@ -1,1108 +0,0 @@ -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} -function generateFingerprintSync() { - const components = getComponents(); - const combined = components.join("|"); - const hash = simpleHash(combined); - return `fp_${hash}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; -export { - ConsentAPI, - ConsentManager, - ConsentStorage, - EventEmitter, - SDK_VERSION, - ScriptBlocker, - ConsentManager as default, - generateFingerprint, - generateFingerprintSync -}; -//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/index.mjs.map b/docs-src/consent-sdk/dist/index.mjs.map deleted file mode 100644 index 39ab2e2..0000000 --- a/docs-src/consent-sdk/dist/index.mjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/core/ConsentStorage.ts","../src/core/ScriptBlocker.ts","../src/core/ConsentAPI.ts","../src/utils/EventEmitter.ts","../src/utils/fingerprint.ts","../src/version.ts","../src/core/ConsentManager.ts"],"sourcesContent":["/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/react/index.d.mts b/docs-src/consent-sdk/dist/react/index.d.mts deleted file mode 100644 index 3d3e285..0000000 --- a/docs-src/consent-sdk/dist/react/index.d.mts +++ /dev/null @@ -1,450 +0,0 @@ -import * as react from 'react'; -import { FC, ReactNode } from 'react'; - -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * Minimaler Consent-Input fuer setConsent() - */ -type ConsentInput = Partial | { - categories?: Partial; - vendors?: ConsentVendors; -}; -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} -/** - * Event-Typen - */ -type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error'; -/** - * Event-Listener Callback - */ -type ConsentEventCallback = (data: T) => void; -/** - * Event-Daten fuer verschiedene Events - */ -type ConsentEventData = { - init: ConsentState | null; - change: ConsentState; - accept_all: ConsentState; - reject_all: ConsentState; - save_selection: ConsentState; - banner_show: undefined; - banner_hide: undefined; - settings_open: undefined; - settings_close: undefined; - vendor_enable: string; - vendor_disable: string; - error: Error; -}; - -/** - * ConsentManager - Hauptklasse fuer das Consent Management - * - * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. - */ - -/** - * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung - */ -declare class ConsentManager { - private config; - private storage; - private scriptBlocker; - private api; - private events; - private currentConsent; - private initialized; - private bannerVisible; - private deviceFingerprint; - constructor(config: ConsentConfig); - /** - * SDK initialisieren - */ - init(): Promise; - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category: ConsentCategory): boolean; - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId: string): boolean; - /** - * Aktuellen Consent-State abrufen - */ - getConsent(): ConsentState | null; - /** - * Consent setzen - */ - setConsent(input: ConsentInput): Promise; - /** - * Alle Kategorien akzeptieren - */ - acceptAll(): Promise; - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - rejectAll(): Promise; - /** - * Alle Einwilligungen widerrufen - */ - revokeAll(): Promise; - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - exportConsent(): Promise; - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent(): boolean; - /** - * Banner anzeigen - */ - showBanner(): void; - /** - * Banner verstecken - */ - hideBanner(): void; - /** - * Einstellungs-Modal oeffnen - */ - showSettings(): void; - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible(): boolean; - /** - * Event-Listener registrieren - */ - on(event: T, callback: ConsentEventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: T, callback: ConsentEventCallback): void; - /** - * Konfiguration zusammenfuehren - */ - private mergeConfig; - /** - * Consent-Input normalisieren - */ - private normalizeConsentInput; - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - private applyConsent; - /** - * Google Consent Mode v2 aktualisieren - */ - private updateGoogleConsentMode; - /** - * Pruefen ob Consent abgelaufen - */ - private isConsentExpired; - /** - * Event emittieren - */ - private emit; - /** - * Fehler behandeln - */ - private handleError; - /** - * Debug-Logging - */ - private log; - /** - * SDK-Version abrufen - */ - static getVersion(): string; -} - -interface ConsentContextValue { - /** ConsentManager Instanz */ - manager: ConsentManager | null; - /** Aktueller Consent-State */ - consent: ConsentState | null; - /** Ist SDK initialisiert? */ - isInitialized: boolean; - /** Wird geladen? */ - isLoading: boolean; - /** Ist Banner sichtbar? */ - isBannerVisible: boolean; - /** Wird Consent benoetigt? */ - needsConsent: boolean; - /** Consent fuer Kategorie pruefen */ - hasConsent: (category: ConsentCategory) => boolean; - /** Alle akzeptieren */ - acceptAll: () => Promise; - /** Alle ablehnen */ - rejectAll: () => Promise; - /** Auswahl speichern */ - saveSelection: (categories: Partial) => Promise; - /** Banner anzeigen */ - showBanner: () => void; - /** Banner verstecken */ - hideBanner: () => void; - /** Einstellungen oeffnen */ - showSettings: () => void; -} -declare const ConsentContext: react.Context; -interface ConsentProviderProps { - /** SDK-Konfiguration */ - config: ConsentConfig; - /** Kinder-Komponenten */ - children: ReactNode; -} -/** - * ConsentProvider - Stellt Consent-Kontext bereit - */ -declare const ConsentProvider: FC; -/** - * useConsent - Hook fuer Consent-Zugriff - * - * @example - * ```tsx - * const { hasConsent, acceptAll, rejectAll } = useConsent(); - * - * if (hasConsent('analytics')) { - * // Analytics laden - * } - * ``` - */ -declare function useConsent(): ConsentContextValue; -declare function useConsent(category: ConsentCategory): ConsentContextValue & { - allowed: boolean; -}; -/** - * useConsentManager - Direkter Zugriff auf ConsentManager - */ -declare function useConsentManager(): ConsentManager | null; -interface ConsentGateProps { - /** Erforderliche Kategorie */ - category: ConsentCategory; - /** Inhalt bei Consent */ - children: ReactNode; - /** Inhalt ohne Consent */ - placeholder?: ReactNode; - /** Fallback waehrend Laden */ - fallback?: ReactNode; -} -/** - * ConsentGate - Zeigt Inhalt nur bei Consent - * - * @example - * ```tsx - * } - * > - * - * - * ``` - */ -declare const ConsentGate: FC; -interface ConsentPlaceholderProps { - /** Kategorie */ - category: ConsentCategory; - /** Custom Nachricht */ - message?: string; - /** Custom Button-Text */ - buttonText?: string; - /** Custom Styling */ - className?: string; -} -/** - * ConsentPlaceholder - Placeholder fuer blockierten Inhalt - */ -declare const ConsentPlaceholder: FC; -interface ConsentBannerRenderProps { - /** Ist Banner sichtbar? */ - isVisible: boolean; - /** Aktueller Consent */ - consent: ConsentState | null; - /** Wird Consent benoetigt? */ - needsConsent: boolean; - /** Alle akzeptieren */ - onAcceptAll: () => void; - /** Alle ablehnen */ - onRejectAll: () => void; - /** Auswahl speichern */ - onSaveSelection: (categories: Partial) => void; - /** Einstellungen oeffnen */ - onShowSettings: () => void; - /** Banner schliessen */ - onClose: () => void; -} -interface ConsentBannerProps { - /** Render-Funktion fuer Custom UI */ - render?: (props: ConsentBannerRenderProps) => ReactNode; - /** Custom Styling */ - className?: string; -} -/** - * ConsentBanner - Headless Banner-Komponente - * - * Kann mit eigener UI gerendert werden oder nutzt Default-UI. - * - * @example - * ```tsx - * // Mit eigener UI - * ( - * isVisible && ( - *
- * - * - *
- * ) - * )} - * /> - * - * // Mit Default-UI - * - * ``` - */ -declare const ConsentBanner: FC; - -export { ConsentBanner, type ConsentBannerRenderProps, ConsentContext, type ConsentContextValue, ConsentGate, ConsentPlaceholder, ConsentProvider, useConsent, useConsentManager }; diff --git a/docs-src/consent-sdk/dist/react/index.d.ts b/docs-src/consent-sdk/dist/react/index.d.ts deleted file mode 100644 index 3d3e285..0000000 --- a/docs-src/consent-sdk/dist/react/index.d.ts +++ /dev/null @@ -1,450 +0,0 @@ -import * as react from 'react'; -import { FC, ReactNode } from 'react'; - -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * Minimaler Consent-Input fuer setConsent() - */ -type ConsentInput = Partial | { - categories?: Partial; - vendors?: ConsentVendors; -}; -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} -/** - * Event-Typen - */ -type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error'; -/** - * Event-Listener Callback - */ -type ConsentEventCallback = (data: T) => void; -/** - * Event-Daten fuer verschiedene Events - */ -type ConsentEventData = { - init: ConsentState | null; - change: ConsentState; - accept_all: ConsentState; - reject_all: ConsentState; - save_selection: ConsentState; - banner_show: undefined; - banner_hide: undefined; - settings_open: undefined; - settings_close: undefined; - vendor_enable: string; - vendor_disable: string; - error: Error; -}; - -/** - * ConsentManager - Hauptklasse fuer das Consent Management - * - * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. - */ - -/** - * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung - */ -declare class ConsentManager { - private config; - private storage; - private scriptBlocker; - private api; - private events; - private currentConsent; - private initialized; - private bannerVisible; - private deviceFingerprint; - constructor(config: ConsentConfig); - /** - * SDK initialisieren - */ - init(): Promise; - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category: ConsentCategory): boolean; - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId: string): boolean; - /** - * Aktuellen Consent-State abrufen - */ - getConsent(): ConsentState | null; - /** - * Consent setzen - */ - setConsent(input: ConsentInput): Promise; - /** - * Alle Kategorien akzeptieren - */ - acceptAll(): Promise; - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - rejectAll(): Promise; - /** - * Alle Einwilligungen widerrufen - */ - revokeAll(): Promise; - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - exportConsent(): Promise; - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent(): boolean; - /** - * Banner anzeigen - */ - showBanner(): void; - /** - * Banner verstecken - */ - hideBanner(): void; - /** - * Einstellungs-Modal oeffnen - */ - showSettings(): void; - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible(): boolean; - /** - * Event-Listener registrieren - */ - on(event: T, callback: ConsentEventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: T, callback: ConsentEventCallback): void; - /** - * Konfiguration zusammenfuehren - */ - private mergeConfig; - /** - * Consent-Input normalisieren - */ - private normalizeConsentInput; - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - private applyConsent; - /** - * Google Consent Mode v2 aktualisieren - */ - private updateGoogleConsentMode; - /** - * Pruefen ob Consent abgelaufen - */ - private isConsentExpired; - /** - * Event emittieren - */ - private emit; - /** - * Fehler behandeln - */ - private handleError; - /** - * Debug-Logging - */ - private log; - /** - * SDK-Version abrufen - */ - static getVersion(): string; -} - -interface ConsentContextValue { - /** ConsentManager Instanz */ - manager: ConsentManager | null; - /** Aktueller Consent-State */ - consent: ConsentState | null; - /** Ist SDK initialisiert? */ - isInitialized: boolean; - /** Wird geladen? */ - isLoading: boolean; - /** Ist Banner sichtbar? */ - isBannerVisible: boolean; - /** Wird Consent benoetigt? */ - needsConsent: boolean; - /** Consent fuer Kategorie pruefen */ - hasConsent: (category: ConsentCategory) => boolean; - /** Alle akzeptieren */ - acceptAll: () => Promise; - /** Alle ablehnen */ - rejectAll: () => Promise; - /** Auswahl speichern */ - saveSelection: (categories: Partial) => Promise; - /** Banner anzeigen */ - showBanner: () => void; - /** Banner verstecken */ - hideBanner: () => void; - /** Einstellungen oeffnen */ - showSettings: () => void; -} -declare const ConsentContext: react.Context; -interface ConsentProviderProps { - /** SDK-Konfiguration */ - config: ConsentConfig; - /** Kinder-Komponenten */ - children: ReactNode; -} -/** - * ConsentProvider - Stellt Consent-Kontext bereit - */ -declare const ConsentProvider: FC; -/** - * useConsent - Hook fuer Consent-Zugriff - * - * @example - * ```tsx - * const { hasConsent, acceptAll, rejectAll } = useConsent(); - * - * if (hasConsent('analytics')) { - * // Analytics laden - * } - * ``` - */ -declare function useConsent(): ConsentContextValue; -declare function useConsent(category: ConsentCategory): ConsentContextValue & { - allowed: boolean; -}; -/** - * useConsentManager - Direkter Zugriff auf ConsentManager - */ -declare function useConsentManager(): ConsentManager | null; -interface ConsentGateProps { - /** Erforderliche Kategorie */ - category: ConsentCategory; - /** Inhalt bei Consent */ - children: ReactNode; - /** Inhalt ohne Consent */ - placeholder?: ReactNode; - /** Fallback waehrend Laden */ - fallback?: ReactNode; -} -/** - * ConsentGate - Zeigt Inhalt nur bei Consent - * - * @example - * ```tsx - * } - * > - * - * - * ``` - */ -declare const ConsentGate: FC; -interface ConsentPlaceholderProps { - /** Kategorie */ - category: ConsentCategory; - /** Custom Nachricht */ - message?: string; - /** Custom Button-Text */ - buttonText?: string; - /** Custom Styling */ - className?: string; -} -/** - * ConsentPlaceholder - Placeholder fuer blockierten Inhalt - */ -declare const ConsentPlaceholder: FC; -interface ConsentBannerRenderProps { - /** Ist Banner sichtbar? */ - isVisible: boolean; - /** Aktueller Consent */ - consent: ConsentState | null; - /** Wird Consent benoetigt? */ - needsConsent: boolean; - /** Alle akzeptieren */ - onAcceptAll: () => void; - /** Alle ablehnen */ - onRejectAll: () => void; - /** Auswahl speichern */ - onSaveSelection: (categories: Partial) => void; - /** Einstellungen oeffnen */ - onShowSettings: () => void; - /** Banner schliessen */ - onClose: () => void; -} -interface ConsentBannerProps { - /** Render-Funktion fuer Custom UI */ - render?: (props: ConsentBannerRenderProps) => ReactNode; - /** Custom Styling */ - className?: string; -} -/** - * ConsentBanner - Headless Banner-Komponente - * - * Kann mit eigener UI gerendert werden oder nutzt Default-UI. - * - * @example - * ```tsx - * // Mit eigener UI - * ( - * isVisible && ( - *
- * - * - *
- * ) - * )} - * /> - * - * // Mit Default-UI - * - * ``` - */ -declare const ConsentBanner: FC; - -export { ConsentBanner, type ConsentBannerRenderProps, ConsentContext, type ConsentContextValue, ConsentGate, ConsentPlaceholder, ConsentProvider, useConsent, useConsentManager }; diff --git a/docs-src/consent-sdk/dist/react/index.js b/docs-src/consent-sdk/dist/react/index.js deleted file mode 100644 index f627568..0000000 --- a/docs-src/consent-sdk/dist/react/index.js +++ /dev/null @@ -1,1361 +0,0 @@ -"use strict"; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - -// src/react/index.tsx -var index_exports = {}; -__export(index_exports, { - ConsentBanner: () => ConsentBanner, - ConsentContext: () => ConsentContext, - ConsentGate: () => ConsentGate, - ConsentPlaceholder: () => ConsentPlaceholder, - ConsentProvider: () => ConsentProvider, - useConsent: () => useConsent, - useConsentManager: () => useConsentManager -}); -module.exports = __toCommonJS(index_exports); -var import_react = require("react"); - -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; - -// src/react/index.tsx -var import_jsx_runtime = require("react/jsx-runtime"); -var ConsentContext = (0, import_react.createContext)(null); -var ConsentProvider = ({ - config, - children -}) => { - const [manager, setManager] = (0, import_react.useState)(null); - const [consent, setConsent] = (0, import_react.useState)(null); - const [isInitialized, setIsInitialized] = (0, import_react.useState)(false); - const [isLoading, setIsLoading] = (0, import_react.useState)(true); - const [isBannerVisible, setIsBannerVisible] = (0, import_react.useState)(false); - (0, import_react.useEffect)(() => { - const consentManager = new ConsentManager(config); - setManager(consentManager); - const unsubChange = consentManager.on("change", (newConsent) => { - setConsent(newConsent); - }); - const unsubBannerShow = consentManager.on("banner_show", () => { - setIsBannerVisible(true); - }); - const unsubBannerHide = consentManager.on("banner_hide", () => { - setIsBannerVisible(false); - }); - consentManager.init().then(() => { - setConsent(consentManager.getConsent()); - setIsInitialized(true); - setIsLoading(false); - setIsBannerVisible(consentManager.isBannerVisible()); - }).catch((error) => { - console.error("Failed to initialize ConsentManager:", error); - setIsLoading(false); - }); - return () => { - unsubChange(); - unsubBannerShow(); - unsubBannerHide(); - }; - }, [config]); - const hasConsent = (0, import_react.useCallback)( - (category) => { - return manager?.hasConsent(category) ?? category === "essential"; - }, - [manager] - ); - const acceptAll = (0, import_react.useCallback)(async () => { - await manager?.acceptAll(); - }, [manager]); - const rejectAll = (0, import_react.useCallback)(async () => { - await manager?.rejectAll(); - }, [manager]); - const saveSelection = (0, import_react.useCallback)( - async (categories) => { - await manager?.setConsent(categories); - manager?.hideBanner(); - }, - [manager] - ); - const showBanner = (0, import_react.useCallback)(() => { - manager?.showBanner(); - }, [manager]); - const hideBanner = (0, import_react.useCallback)(() => { - manager?.hideBanner(); - }, [manager]); - const showSettings = (0, import_react.useCallback)(() => { - manager?.showSettings(); - }, [manager]); - const needsConsent = (0, import_react.useMemo)(() => { - return manager?.needsConsent() ?? true; - }, [manager, consent]); - const contextValue = (0, import_react.useMemo)( - () => ({ - manager, - consent, - isInitialized, - isLoading, - isBannerVisible, - needsConsent, - hasConsent, - acceptAll, - rejectAll, - saveSelection, - showBanner, - hideBanner, - showSettings - }), - [ - manager, - consent, - isInitialized, - isLoading, - isBannerVisible, - needsConsent, - hasConsent, - acceptAll, - rejectAll, - saveSelection, - showBanner, - hideBanner, - showSettings - ] - ); - return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConsentContext.Provider, { value: contextValue, children }); -}; -function useConsent(category) { - const context = (0, import_react.useContext)(ConsentContext); - if (!context) { - throw new Error("useConsent must be used within a ConsentProvider"); - } - if (category) { - return { - ...context, - allowed: context.hasConsent(category) - }; - } - return context; -} -function useConsentManager() { - const context = (0, import_react.useContext)(ConsentContext); - return context?.manager ?? null; -} -var ConsentGate = ({ - category, - children, - placeholder = null, - fallback = null -}) => { - const { hasConsent, isLoading } = useConsent(); - if (isLoading) { - return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback }); - } - if (!hasConsent(category)) { - return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: placeholder }); - } - return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children }); -}; -var ConsentPlaceholder = ({ - category, - message, - buttonText, - className = "" -}) => { - const { showSettings } = useConsent(); - const categoryNames = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - const defaultMessage = `Dieser Inhalt erfordert ${categoryNames[category]}.`; - return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `bp-consent-placeholder ${className}`, children: [ - /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: message || defaultMessage }), - /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: showSettings, children: buttonText || "Cookie-Einstellungen oeffnen" }) - ] }); -}; -var ConsentBanner = ({ render, className }) => { - const { - consent, - isBannerVisible, - needsConsent, - acceptAll, - rejectAll, - saveSelection, - showSettings, - hideBanner - } = useConsent(); - const renderProps = { - isVisible: isBannerVisible, - consent, - needsConsent, - onAcceptAll: acceptAll, - onRejectAll: rejectAll, - onSaveSelection: saveSelection, - onShowSettings: showSettings, - onClose: hideBanner - }; - if (render) { - return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: render(renderProps) }); - } - if (!isBannerVisible) { - return null; - } - return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( - "div", - { - className: `bp-consent-banner ${className || ""}`, - role: "dialog", - "aria-modal": "true", - "aria-label": "Cookie-Einstellungen", - children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bp-consent-banner-content", children: [ - /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Datenschutzeinstellungen" }), - /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Wir nutzen Cookies und aehnliche Technologien, um Ihnen ein optimales Nutzererlebnis zu bieten." }), - /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bp-consent-banner-actions", children: [ - /* @__PURE__ */ (0, import_jsx_runtime.jsx)( - "button", - { - type: "button", - className: "bp-consent-btn bp-consent-btn-reject", - onClick: rejectAll, - children: "Alle ablehnen" - } - ), - /* @__PURE__ */ (0, import_jsx_runtime.jsx)( - "button", - { - type: "button", - className: "bp-consent-btn bp-consent-btn-settings", - onClick: showSettings, - children: "Einstellungen" - } - ), - /* @__PURE__ */ (0, import_jsx_runtime.jsx)( - "button", - { - type: "button", - className: "bp-consent-btn bp-consent-btn-accept", - onClick: acceptAll, - children: "Alle akzeptieren" - } - ) - ] }) - ] }) - } - ); -}; -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - ConsentBanner, - ConsentContext, - ConsentGate, - ConsentPlaceholder, - ConsentProvider, - useConsent, - useConsentManager -}); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/react/index.js.map b/docs-src/consent-sdk/dist/react/index.js.map deleted file mode 100644 index 2820d6c..0000000 --- a/docs-src/consent-sdk/dist/react/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/react/index.tsx","../../src/core/ConsentStorage.ts","../../src/core/ScriptBlocker.ts","../../src/core/ConsentAPI.ts","../../src/utils/EventEmitter.ts","../../src/utils/fingerprint.ts","../../src/version.ts","../../src/core/ConsentManager.ts"],"sourcesContent":["/**\n * React Integration fuer @breakpilot/consent-sdk\n *\n * @example\n * ```tsx\n * import { ConsentProvider, useConsent, ConsentBanner } from '@breakpilot/consent-sdk/react';\n *\n * function App() {\n * return (\n * \n * \n * \n * \n * );\n * }\n * ```\n */\n\nimport {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useMemo,\n type ReactNode,\n type FC,\n} from 'react';\nimport { ConsentManager } from '../core/ConsentManager';\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n} from '../types';\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface ConsentContextValue {\n /** ConsentManager Instanz */\n manager: ConsentManager | null;\n\n /** Aktueller Consent-State */\n consent: ConsentState | null;\n\n /** Ist SDK initialisiert? */\n isInitialized: boolean;\n\n /** Wird geladen? */\n isLoading: boolean;\n\n /** Ist Banner sichtbar? */\n isBannerVisible: boolean;\n\n /** Wird Consent benoetigt? */\n needsConsent: boolean;\n\n /** Consent fuer Kategorie pruefen */\n hasConsent: (category: ConsentCategory) => boolean;\n\n /** Alle akzeptieren */\n acceptAll: () => Promise;\n\n /** Alle ablehnen */\n rejectAll: () => Promise;\n\n /** Auswahl speichern */\n saveSelection: (categories: Partial) => Promise;\n\n /** Banner anzeigen */\n showBanner: () => void;\n\n /** Banner verstecken */\n hideBanner: () => void;\n\n /** Einstellungen oeffnen */\n showSettings: () => void;\n}\n\nconst ConsentContext = createContext(null);\n\n// =============================================================================\n// Provider\n// =============================================================================\n\ninterface ConsentProviderProps {\n /** SDK-Konfiguration */\n config: ConsentConfig;\n\n /** Kinder-Komponenten */\n children: ReactNode;\n}\n\n/**\n * ConsentProvider - Stellt Consent-Kontext bereit\n */\nexport const ConsentProvider: FC = ({\n config,\n children,\n}) => {\n const [manager, setManager] = useState(null);\n const [consent, setConsent] = useState(null);\n const [isInitialized, setIsInitialized] = useState(false);\n const [isLoading, setIsLoading] = useState(true);\n const [isBannerVisible, setIsBannerVisible] = useState(false);\n\n // Manager erstellen und initialisieren\n useEffect(() => {\n const consentManager = new ConsentManager(config);\n setManager(consentManager);\n\n // Events abonnieren\n const unsubChange = consentManager.on('change', (newConsent) => {\n setConsent(newConsent);\n });\n\n const unsubBannerShow = consentManager.on('banner_show', () => {\n setIsBannerVisible(true);\n });\n\n const unsubBannerHide = consentManager.on('banner_hide', () => {\n setIsBannerVisible(false);\n });\n\n // Initialisieren\n consentManager\n .init()\n .then(() => {\n setConsent(consentManager.getConsent());\n setIsInitialized(true);\n setIsLoading(false);\n setIsBannerVisible(consentManager.isBannerVisible());\n })\n .catch((error) => {\n console.error('Failed to initialize ConsentManager:', error);\n setIsLoading(false);\n });\n\n // Cleanup\n return () => {\n unsubChange();\n unsubBannerShow();\n unsubBannerHide();\n };\n }, [config]);\n\n // Callback-Funktionen\n const hasConsent = useCallback(\n (category: ConsentCategory): boolean => {\n return manager?.hasConsent(category) ?? category === 'essential';\n },\n [manager]\n );\n\n const acceptAll = useCallback(async () => {\n await manager?.acceptAll();\n }, [manager]);\n\n const rejectAll = useCallback(async () => {\n await manager?.rejectAll();\n }, [manager]);\n\n const saveSelection = useCallback(\n async (categories: Partial) => {\n await manager?.setConsent(categories);\n manager?.hideBanner();\n },\n [manager]\n );\n\n const showBanner = useCallback(() => {\n manager?.showBanner();\n }, [manager]);\n\n const hideBanner = useCallback(() => {\n manager?.hideBanner();\n }, [manager]);\n\n const showSettings = useCallback(() => {\n manager?.showSettings();\n }, [manager]);\n\n const needsConsent = useMemo(() => {\n return manager?.needsConsent() ?? true;\n }, [manager, consent]);\n\n // Context-Wert\n const contextValue = useMemo(\n () => ({\n manager,\n consent,\n isInitialized,\n isLoading,\n isBannerVisible,\n needsConsent,\n hasConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showBanner,\n hideBanner,\n showSettings,\n }),\n [\n manager,\n consent,\n isInitialized,\n isLoading,\n isBannerVisible,\n needsConsent,\n hasConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showBanner,\n hideBanner,\n showSettings,\n ]\n );\n\n return (\n \n {children}\n \n );\n};\n\n// =============================================================================\n// Hooks\n// =============================================================================\n\n/**\n * useConsent - Hook fuer Consent-Zugriff\n *\n * @example\n * ```tsx\n * const { hasConsent, acceptAll, rejectAll } = useConsent();\n *\n * if (hasConsent('analytics')) {\n * // Analytics laden\n * }\n * ```\n */\nexport function useConsent(): ConsentContextValue;\nexport function useConsent(\n category: ConsentCategory\n): ConsentContextValue & { allowed: boolean };\nexport function useConsent(category?: ConsentCategory) {\n const context = useContext(ConsentContext);\n\n if (!context) {\n throw new Error('useConsent must be used within a ConsentProvider');\n }\n\n if (category) {\n return {\n ...context,\n allowed: context.hasConsent(category),\n };\n }\n\n return context;\n}\n\n/**\n * useConsentManager - Direkter Zugriff auf ConsentManager\n */\nexport function useConsentManager(): ConsentManager | null {\n const context = useContext(ConsentContext);\n return context?.manager ?? null;\n}\n\n// =============================================================================\n// Components\n// =============================================================================\n\ninterface ConsentGateProps {\n /** Erforderliche Kategorie */\n category: ConsentCategory;\n\n /** Inhalt bei Consent */\n children: ReactNode;\n\n /** Inhalt ohne Consent */\n placeholder?: ReactNode;\n\n /** Fallback waehrend Laden */\n fallback?: ReactNode;\n}\n\n/**\n * ConsentGate - Zeigt Inhalt nur bei Consent\n *\n * @example\n * ```tsx\n * }\n * >\n * \n * \n * ```\n */\nexport const ConsentGate: FC = ({\n category,\n children,\n placeholder = null,\n fallback = null,\n}) => {\n const { hasConsent, isLoading } = useConsent();\n\n if (isLoading) {\n return <>{fallback};\n }\n\n if (!hasConsent(category)) {\n return <>{placeholder};\n }\n\n return <>{children};\n};\n\ninterface ConsentPlaceholderProps {\n /** Kategorie */\n category: ConsentCategory;\n\n /** Custom Nachricht */\n message?: string;\n\n /** Custom Button-Text */\n buttonText?: string;\n\n /** Custom Styling */\n className?: string;\n}\n\n/**\n * ConsentPlaceholder - Placeholder fuer blockierten Inhalt\n */\nexport const ConsentPlaceholder: FC = ({\n category,\n message,\n buttonText,\n className = '',\n}) => {\n const { showSettings } = useConsent();\n\n const categoryNames: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n\n const defaultMessage = `Dieser Inhalt erfordert ${categoryNames[category]}.`;\n\n return (\n
\n

{message || defaultMessage}

\n \n
\n );\n};\n\n// =============================================================================\n// Banner Component (Headless)\n// =============================================================================\n\ninterface ConsentBannerRenderProps {\n /** Ist Banner sichtbar? */\n isVisible: boolean;\n\n /** Aktueller Consent */\n consent: ConsentState | null;\n\n /** Wird Consent benoetigt? */\n needsConsent: boolean;\n\n /** Alle akzeptieren */\n onAcceptAll: () => void;\n\n /** Alle ablehnen */\n onRejectAll: () => void;\n\n /** Auswahl speichern */\n onSaveSelection: (categories: Partial) => void;\n\n /** Einstellungen oeffnen */\n onShowSettings: () => void;\n\n /** Banner schliessen */\n onClose: () => void;\n}\n\ninterface ConsentBannerProps {\n /** Render-Funktion fuer Custom UI */\n render?: (props: ConsentBannerRenderProps) => ReactNode;\n\n /** Custom Styling */\n className?: string;\n}\n\n/**\n * ConsentBanner - Headless Banner-Komponente\n *\n * Kann mit eigener UI gerendert werden oder nutzt Default-UI.\n *\n * @example\n * ```tsx\n * // Mit eigener UI\n * (\n * isVisible && (\n *
\n * \n * \n *
\n * )\n * )}\n * />\n *\n * // Mit Default-UI\n * \n * ```\n */\nexport const ConsentBanner: FC = ({ render, className }) => {\n const {\n consent,\n isBannerVisible,\n needsConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showSettings,\n hideBanner,\n } = useConsent();\n\n const renderProps: ConsentBannerRenderProps = {\n isVisible: isBannerVisible,\n consent,\n needsConsent,\n onAcceptAll: acceptAll,\n onRejectAll: rejectAll,\n onSaveSelection: saveSelection,\n onShowSettings: showSettings,\n onClose: hideBanner,\n };\n\n // Custom Render\n if (render) {\n return <>{render(renderProps)};\n }\n\n // Default UI\n if (!isBannerVisible) {\n return null;\n }\n\n return (\n \n
\n

Datenschutzeinstellungen

\n

\n Wir nutzen Cookies und aehnliche Technologien, um Ihnen ein optimales\n Nutzererlebnis zu bieten.\n

\n\n
\n \n Alle ablehnen\n \n \n Einstellungen\n \n \n Alle akzeptieren\n \n
\n
\n \n );\n};\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport { ConsentContext };\nexport type { ConsentContextValue, ConsentBannerRenderProps };\n","/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/react/index.mjs b/docs-src/consent-sdk/dist/react/index.mjs deleted file mode 100644 index 9851d3a..0000000 --- a/docs-src/consent-sdk/dist/react/index.mjs +++ /dev/null @@ -1,1337 +0,0 @@ -// src/react/index.tsx -import { - createContext, - useContext, - useEffect, - useState, - useCallback, - useMemo -} from "react"; - -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; - -// src/react/index.tsx -import { Fragment, jsx, jsxs } from "react/jsx-runtime"; -var ConsentContext = createContext(null); -var ConsentProvider = ({ - config, - children -}) => { - const [manager, setManager] = useState(null); - const [consent, setConsent] = useState(null); - const [isInitialized, setIsInitialized] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [isBannerVisible, setIsBannerVisible] = useState(false); - useEffect(() => { - const consentManager = new ConsentManager(config); - setManager(consentManager); - const unsubChange = consentManager.on("change", (newConsent) => { - setConsent(newConsent); - }); - const unsubBannerShow = consentManager.on("banner_show", () => { - setIsBannerVisible(true); - }); - const unsubBannerHide = consentManager.on("banner_hide", () => { - setIsBannerVisible(false); - }); - consentManager.init().then(() => { - setConsent(consentManager.getConsent()); - setIsInitialized(true); - setIsLoading(false); - setIsBannerVisible(consentManager.isBannerVisible()); - }).catch((error) => { - console.error("Failed to initialize ConsentManager:", error); - setIsLoading(false); - }); - return () => { - unsubChange(); - unsubBannerShow(); - unsubBannerHide(); - }; - }, [config]); - const hasConsent = useCallback( - (category) => { - return manager?.hasConsent(category) ?? category === "essential"; - }, - [manager] - ); - const acceptAll = useCallback(async () => { - await manager?.acceptAll(); - }, [manager]); - const rejectAll = useCallback(async () => { - await manager?.rejectAll(); - }, [manager]); - const saveSelection = useCallback( - async (categories) => { - await manager?.setConsent(categories); - manager?.hideBanner(); - }, - [manager] - ); - const showBanner = useCallback(() => { - manager?.showBanner(); - }, [manager]); - const hideBanner = useCallback(() => { - manager?.hideBanner(); - }, [manager]); - const showSettings = useCallback(() => { - manager?.showSettings(); - }, [manager]); - const needsConsent = useMemo(() => { - return manager?.needsConsent() ?? true; - }, [manager, consent]); - const contextValue = useMemo( - () => ({ - manager, - consent, - isInitialized, - isLoading, - isBannerVisible, - needsConsent, - hasConsent, - acceptAll, - rejectAll, - saveSelection, - showBanner, - hideBanner, - showSettings - }), - [ - manager, - consent, - isInitialized, - isLoading, - isBannerVisible, - needsConsent, - hasConsent, - acceptAll, - rejectAll, - saveSelection, - showBanner, - hideBanner, - showSettings - ] - ); - return /* @__PURE__ */ jsx(ConsentContext.Provider, { value: contextValue, children }); -}; -function useConsent(category) { - const context = useContext(ConsentContext); - if (!context) { - throw new Error("useConsent must be used within a ConsentProvider"); - } - if (category) { - return { - ...context, - allowed: context.hasConsent(category) - }; - } - return context; -} -function useConsentManager() { - const context = useContext(ConsentContext); - return context?.manager ?? null; -} -var ConsentGate = ({ - category, - children, - placeholder = null, - fallback = null -}) => { - const { hasConsent, isLoading } = useConsent(); - if (isLoading) { - return /* @__PURE__ */ jsx(Fragment, { children: fallback }); - } - if (!hasConsent(category)) { - return /* @__PURE__ */ jsx(Fragment, { children: placeholder }); - } - return /* @__PURE__ */ jsx(Fragment, { children }); -}; -var ConsentPlaceholder = ({ - category, - message, - buttonText, - className = "" -}) => { - const { showSettings } = useConsent(); - const categoryNames = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - const defaultMessage = `Dieser Inhalt erfordert ${categoryNames[category]}.`; - return /* @__PURE__ */ jsxs("div", { className: `bp-consent-placeholder ${className}`, children: [ - /* @__PURE__ */ jsx("p", { children: message || defaultMessage }), - /* @__PURE__ */ jsx("button", { type: "button", onClick: showSettings, children: buttonText || "Cookie-Einstellungen oeffnen" }) - ] }); -}; -var ConsentBanner = ({ render, className }) => { - const { - consent, - isBannerVisible, - needsConsent, - acceptAll, - rejectAll, - saveSelection, - showSettings, - hideBanner - } = useConsent(); - const renderProps = { - isVisible: isBannerVisible, - consent, - needsConsent, - onAcceptAll: acceptAll, - onRejectAll: rejectAll, - onSaveSelection: saveSelection, - onShowSettings: showSettings, - onClose: hideBanner - }; - if (render) { - return /* @__PURE__ */ jsx(Fragment, { children: render(renderProps) }); - } - if (!isBannerVisible) { - return null; - } - return /* @__PURE__ */ jsx( - "div", - { - className: `bp-consent-banner ${className || ""}`, - role: "dialog", - "aria-modal": "true", - "aria-label": "Cookie-Einstellungen", - children: /* @__PURE__ */ jsxs("div", { className: "bp-consent-banner-content", children: [ - /* @__PURE__ */ jsx("h2", { children: "Datenschutzeinstellungen" }), - /* @__PURE__ */ jsx("p", { children: "Wir nutzen Cookies und aehnliche Technologien, um Ihnen ein optimales Nutzererlebnis zu bieten." }), - /* @__PURE__ */ jsxs("div", { className: "bp-consent-banner-actions", children: [ - /* @__PURE__ */ jsx( - "button", - { - type: "button", - className: "bp-consent-btn bp-consent-btn-reject", - onClick: rejectAll, - children: "Alle ablehnen" - } - ), - /* @__PURE__ */ jsx( - "button", - { - type: "button", - className: "bp-consent-btn bp-consent-btn-settings", - onClick: showSettings, - children: "Einstellungen" - } - ), - /* @__PURE__ */ jsx( - "button", - { - type: "button", - className: "bp-consent-btn bp-consent-btn-accept", - onClick: acceptAll, - children: "Alle akzeptieren" - } - ) - ] }) - ] }) - } - ); -}; -export { - ConsentBanner, - ConsentContext, - ConsentGate, - ConsentPlaceholder, - ConsentProvider, - useConsent, - useConsentManager -}; -//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/react/index.mjs.map b/docs-src/consent-sdk/dist/react/index.mjs.map deleted file mode 100644 index fa6c5db..0000000 --- a/docs-src/consent-sdk/dist/react/index.mjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/react/index.tsx","../../src/core/ConsentStorage.ts","../../src/core/ScriptBlocker.ts","../../src/core/ConsentAPI.ts","../../src/utils/EventEmitter.ts","../../src/utils/fingerprint.ts","../../src/version.ts","../../src/core/ConsentManager.ts"],"sourcesContent":["/**\n * React Integration fuer @breakpilot/consent-sdk\n *\n * @example\n * ```tsx\n * import { ConsentProvider, useConsent, ConsentBanner } from '@breakpilot/consent-sdk/react';\n *\n * function App() {\n * return (\n * \n * \n * \n * \n * );\n * }\n * ```\n */\n\nimport {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useMemo,\n type ReactNode,\n type FC,\n} from 'react';\nimport { ConsentManager } from '../core/ConsentManager';\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n} from '../types';\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface ConsentContextValue {\n /** ConsentManager Instanz */\n manager: ConsentManager | null;\n\n /** Aktueller Consent-State */\n consent: ConsentState | null;\n\n /** Ist SDK initialisiert? */\n isInitialized: boolean;\n\n /** Wird geladen? */\n isLoading: boolean;\n\n /** Ist Banner sichtbar? */\n isBannerVisible: boolean;\n\n /** Wird Consent benoetigt? */\n needsConsent: boolean;\n\n /** Consent fuer Kategorie pruefen */\n hasConsent: (category: ConsentCategory) => boolean;\n\n /** Alle akzeptieren */\n acceptAll: () => Promise;\n\n /** Alle ablehnen */\n rejectAll: () => Promise;\n\n /** Auswahl speichern */\n saveSelection: (categories: Partial) => Promise;\n\n /** Banner anzeigen */\n showBanner: () => void;\n\n /** Banner verstecken */\n hideBanner: () => void;\n\n /** Einstellungen oeffnen */\n showSettings: () => void;\n}\n\nconst ConsentContext = createContext(null);\n\n// =============================================================================\n// Provider\n// =============================================================================\n\ninterface ConsentProviderProps {\n /** SDK-Konfiguration */\n config: ConsentConfig;\n\n /** Kinder-Komponenten */\n children: ReactNode;\n}\n\n/**\n * ConsentProvider - Stellt Consent-Kontext bereit\n */\nexport const ConsentProvider: FC = ({\n config,\n children,\n}) => {\n const [manager, setManager] = useState(null);\n const [consent, setConsent] = useState(null);\n const [isInitialized, setIsInitialized] = useState(false);\n const [isLoading, setIsLoading] = useState(true);\n const [isBannerVisible, setIsBannerVisible] = useState(false);\n\n // Manager erstellen und initialisieren\n useEffect(() => {\n const consentManager = new ConsentManager(config);\n setManager(consentManager);\n\n // Events abonnieren\n const unsubChange = consentManager.on('change', (newConsent) => {\n setConsent(newConsent);\n });\n\n const unsubBannerShow = consentManager.on('banner_show', () => {\n setIsBannerVisible(true);\n });\n\n const unsubBannerHide = consentManager.on('banner_hide', () => {\n setIsBannerVisible(false);\n });\n\n // Initialisieren\n consentManager\n .init()\n .then(() => {\n setConsent(consentManager.getConsent());\n setIsInitialized(true);\n setIsLoading(false);\n setIsBannerVisible(consentManager.isBannerVisible());\n })\n .catch((error) => {\n console.error('Failed to initialize ConsentManager:', error);\n setIsLoading(false);\n });\n\n // Cleanup\n return () => {\n unsubChange();\n unsubBannerShow();\n unsubBannerHide();\n };\n }, [config]);\n\n // Callback-Funktionen\n const hasConsent = useCallback(\n (category: ConsentCategory): boolean => {\n return manager?.hasConsent(category) ?? category === 'essential';\n },\n [manager]\n );\n\n const acceptAll = useCallback(async () => {\n await manager?.acceptAll();\n }, [manager]);\n\n const rejectAll = useCallback(async () => {\n await manager?.rejectAll();\n }, [manager]);\n\n const saveSelection = useCallback(\n async (categories: Partial) => {\n await manager?.setConsent(categories);\n manager?.hideBanner();\n },\n [manager]\n );\n\n const showBanner = useCallback(() => {\n manager?.showBanner();\n }, [manager]);\n\n const hideBanner = useCallback(() => {\n manager?.hideBanner();\n }, [manager]);\n\n const showSettings = useCallback(() => {\n manager?.showSettings();\n }, [manager]);\n\n const needsConsent = useMemo(() => {\n return manager?.needsConsent() ?? true;\n }, [manager, consent]);\n\n // Context-Wert\n const contextValue = useMemo(\n () => ({\n manager,\n consent,\n isInitialized,\n isLoading,\n isBannerVisible,\n needsConsent,\n hasConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showBanner,\n hideBanner,\n showSettings,\n }),\n [\n manager,\n consent,\n isInitialized,\n isLoading,\n isBannerVisible,\n needsConsent,\n hasConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showBanner,\n hideBanner,\n showSettings,\n ]\n );\n\n return (\n \n {children}\n \n );\n};\n\n// =============================================================================\n// Hooks\n// =============================================================================\n\n/**\n * useConsent - Hook fuer Consent-Zugriff\n *\n * @example\n * ```tsx\n * const { hasConsent, acceptAll, rejectAll } = useConsent();\n *\n * if (hasConsent('analytics')) {\n * // Analytics laden\n * }\n * ```\n */\nexport function useConsent(): ConsentContextValue;\nexport function useConsent(\n category: ConsentCategory\n): ConsentContextValue & { allowed: boolean };\nexport function useConsent(category?: ConsentCategory) {\n const context = useContext(ConsentContext);\n\n if (!context) {\n throw new Error('useConsent must be used within a ConsentProvider');\n }\n\n if (category) {\n return {\n ...context,\n allowed: context.hasConsent(category),\n };\n }\n\n return context;\n}\n\n/**\n * useConsentManager - Direkter Zugriff auf ConsentManager\n */\nexport function useConsentManager(): ConsentManager | null {\n const context = useContext(ConsentContext);\n return context?.manager ?? null;\n}\n\n// =============================================================================\n// Components\n// =============================================================================\n\ninterface ConsentGateProps {\n /** Erforderliche Kategorie */\n category: ConsentCategory;\n\n /** Inhalt bei Consent */\n children: ReactNode;\n\n /** Inhalt ohne Consent */\n placeholder?: ReactNode;\n\n /** Fallback waehrend Laden */\n fallback?: ReactNode;\n}\n\n/**\n * ConsentGate - Zeigt Inhalt nur bei Consent\n *\n * @example\n * ```tsx\n * }\n * >\n * \n * \n * ```\n */\nexport const ConsentGate: FC = ({\n category,\n children,\n placeholder = null,\n fallback = null,\n}) => {\n const { hasConsent, isLoading } = useConsent();\n\n if (isLoading) {\n return <>{fallback};\n }\n\n if (!hasConsent(category)) {\n return <>{placeholder};\n }\n\n return <>{children};\n};\n\ninterface ConsentPlaceholderProps {\n /** Kategorie */\n category: ConsentCategory;\n\n /** Custom Nachricht */\n message?: string;\n\n /** Custom Button-Text */\n buttonText?: string;\n\n /** Custom Styling */\n className?: string;\n}\n\n/**\n * ConsentPlaceholder - Placeholder fuer blockierten Inhalt\n */\nexport const ConsentPlaceholder: FC = ({\n category,\n message,\n buttonText,\n className = '',\n}) => {\n const { showSettings } = useConsent();\n\n const categoryNames: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n\n const defaultMessage = `Dieser Inhalt erfordert ${categoryNames[category]}.`;\n\n return (\n
\n

{message || defaultMessage}

\n \n
\n );\n};\n\n// =============================================================================\n// Banner Component (Headless)\n// =============================================================================\n\ninterface ConsentBannerRenderProps {\n /** Ist Banner sichtbar? */\n isVisible: boolean;\n\n /** Aktueller Consent */\n consent: ConsentState | null;\n\n /** Wird Consent benoetigt? */\n needsConsent: boolean;\n\n /** Alle akzeptieren */\n onAcceptAll: () => void;\n\n /** Alle ablehnen */\n onRejectAll: () => void;\n\n /** Auswahl speichern */\n onSaveSelection: (categories: Partial) => void;\n\n /** Einstellungen oeffnen */\n onShowSettings: () => void;\n\n /** Banner schliessen */\n onClose: () => void;\n}\n\ninterface ConsentBannerProps {\n /** Render-Funktion fuer Custom UI */\n render?: (props: ConsentBannerRenderProps) => ReactNode;\n\n /** Custom Styling */\n className?: string;\n}\n\n/**\n * ConsentBanner - Headless Banner-Komponente\n *\n * Kann mit eigener UI gerendert werden oder nutzt Default-UI.\n *\n * @example\n * ```tsx\n * // Mit eigener UI\n * (\n * isVisible && (\n *
\n * \n * \n *
\n * )\n * )}\n * />\n *\n * // Mit Default-UI\n * \n * ```\n */\nexport const ConsentBanner: FC = ({ render, className }) => {\n const {\n consent,\n isBannerVisible,\n needsConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showSettings,\n hideBanner,\n } = useConsent();\n\n const renderProps: ConsentBannerRenderProps = {\n isVisible: isBannerVisible,\n consent,\n needsConsent,\n onAcceptAll: acceptAll,\n onRejectAll: rejectAll,\n onSaveSelection: saveSelection,\n onShowSettings: showSettings,\n onClose: hideBanner,\n };\n\n // Custom Render\n if (render) {\n return <>{render(renderProps)};\n }\n\n // Default UI\n if (!isBannerVisible) {\n return null;\n }\n\n return (\n \n
\n

Datenschutzeinstellungen

\n

\n Wir nutzen Cookies und aehnliche Technologien, um Ihnen ein optimales\n Nutzererlebnis zu bieten.\n

\n\n
\n \n Alle ablehnen\n \n \n Einstellungen\n \n \n Alle akzeptieren\n \n
\n
\n \n );\n};\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport { ConsentContext };\nexport type { ConsentContextValue, ConsentBannerRenderProps };\n","/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n"],"mappings":";AAkBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;;;AClBP,IAAM,cAAc;AACpB,IAAM,kBAAkB;AAcjB,IAAM,iBAAN,MAAqB;AAAA,EAI1B,YAAY,QAAuB;AACjC,SAAK,SAAS;AAEd,SAAK,aAAa,GAAG,WAAW,IAAI,OAAO,MAAM;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAA2B;AACzB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,MAAM,aAAa,QAAQ,KAAK,UAAU;AAChD,UAAI,CAAC,KAAK;AACR,eAAO;AAAA,MACT;AAEA,YAAM,SAAwB,KAAK,MAAM,GAAG;AAG5C,UAAI,OAAO,YAAY,iBAAiB;AACtC,aAAK,IAAI,oCAAoC;AAC7C,aAAK,MAAM;AACX,eAAO;AAAA,MACT;AAGA,UAAI,CAAC,KAAK,gBAAgB,OAAO,SAAS,OAAO,SAAS,GAAG;AAC3D,aAAK,IAAI,6BAA6B;AACtC,aAAK,MAAM;AACX,eAAO;AAAA,MACT;AAEA,aAAO,OAAO;AAAA,IAChB,SAAS,OAAO;AACd,WAAK,IAAI,2BAA2B,KAAK;AACzC,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA6B;AAC/B,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,QAAI;AACF,YAAM,YAAY,KAAK,kBAAkB,OAAO;AAEhD,YAAM,SAAwB;AAAA,QAC5B,SAAS;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAEA,mBAAa,QAAQ,KAAK,YAAY,KAAK,UAAU,MAAM,CAAC;AAG5D,WAAK,UAAU,OAAO;AAEtB,WAAK,IAAI,0BAA0B;AAAA,IACrC,SAAS,OAAO;AACd,WAAK,IAAI,2BAA2B,KAAK;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,WAAW,KAAK,UAAU;AACvC,WAAK,YAAY;AACjB,WAAK,IAAI,8BAA8B;AAAA,IACzC,SAAS,OAAO;AACd,WAAK,IAAI,4BAA4B,KAAK;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAkB;AAChB,WAAO,KAAK,IAAI,MAAM;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,UAAU,SAA6B;AAC7C,UAAM,OAAO,KAAK,OAAO,SAAS,gBAAgB;AAClD,UAAM,UAAU,oBAAI,KAAK;AACzB,YAAQ,QAAQ,QAAQ,QAAQ,IAAI,IAAI;AAGxC,UAAM,cAAc,KAAK,UAAU,QAAQ,UAAU;AACrD,UAAM,UAAU,mBAAmB,WAAW;AAE9C,aAAS,SAAS;AAAA,MAChB,GAAG,KAAK,UAAU,IAAI,OAAO;AAAA,MAC7B,WAAW,QAAQ,YAAY,CAAC;AAAA,MAChC;AAAA,MACA;AAAA,MACA,SAAS,aAAa,WAAW,WAAW;AAAA,IAC9C,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC1B,aAAS,SAAS,GAAG,KAAK,UAAU;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,kBAAkB,SAA+B;AACvD,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,UAAM,MAAM,KAAK,OAAO;AAIxB,WAAO,KAAK,WAAW,OAAO,GAAG;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,SAAuB,WAA4B;AACzE,UAAM,WAAW,KAAK,kBAAkB,OAAO;AAC/C,WAAO,aAAa;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,KAAqB;AACtC,QAAI,OAAO;AACX,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,aAAQ,OAAO,KAAM,IAAI,WAAW,CAAC;AAAA,IACvC;AACA,YAAQ,SAAS,GAAG,SAAS,EAAE;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAO,MAAuB;AACpC,QAAI,KAAK,OAAO,OAAO;AACrB,cAAQ,IAAI,oBAAoB,GAAG,IAAI;AAAA,IACzC;AAAA,EACF;AACF;;;ACrKO,IAAM,gBAAN,MAAoB;AAAA,EAMzB,YAAY,QAAuB;AAJnC,SAAQ,WAAoC;AAC5C,SAAQ,oBAA0C,oBAAI,IAAI,CAAC,WAAW,CAAC;AACvE,SAAQ,oBAAsC,oBAAI,QAAQ;AAGxD,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAGA,SAAK,wBAAwB;AAG7B,SAAK,WAAW,IAAI,iBAAiB,CAAC,cAAc;AAClD,iBAAW,YAAY,WAAW;AAChC,mBAAW,QAAQ,SAAS,YAAY;AACtC,cAAI,KAAK,aAAa,KAAK,cAAc;AACvC,iBAAK,eAAe,IAAe;AAAA,UACrC;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,SAAK,SAAS,QAAQ,SAAS,iBAAiB;AAAA,MAC9C,WAAW;AAAA,MACX,SAAS;AAAA,IACX,CAAC;AAED,SAAK,IAAI,2BAA2B;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAAiC;AAC9C,QAAI,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AACxC;AAAA,IACF;AAEA,SAAK,kBAAkB,IAAI,QAAQ;AACnC,SAAK,IAAI,qBAAqB,QAAQ;AAGtC,SAAK,iBAAiB,QAAQ;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,UAAiC;AAC/C,QAAI,aAAa,aAAa;AAE5B;AAAA,IACF;AAEA,SAAK,kBAAkB,OAAO,QAAQ;AACtC,SAAK,IAAI,sBAAsB,QAAQ;AAAA,EAIzC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,SAAK,kBAAkB,MAAM;AAC7B,SAAK,kBAAkB,IAAI,WAAW;AACtC,SAAK,IAAI,wBAAwB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAoC;AACpD,WAAO,KAAK,kBAAkB,IAAI,QAAQ;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,UAAU,WAAW;AAC1B,SAAK,WAAW;AAChB,SAAK,IAAI,yBAAyB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,0BAAgC;AAEtC,UAAM,UAAU,SAAS;AAAA,MACvB;AAAA,IACF;AACA,YAAQ,QAAQ,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC;AAGtD,UAAM,UAAU,SAAS;AAAA,MACvB;AAAA,IACF;AACA,YAAQ,QAAQ,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC;AAEtD,SAAK,IAAI,aAAa,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,SAAwB;AAC7C,QAAI,QAAQ,YAAY,UAAU;AAChC,WAAK,cAAc,OAAwB;AAAA,IAC7C,WAAW,QAAQ,YAAY,UAAU;AACvC,WAAK,cAAc,OAAwB;AAAA,IAC7C;AAGA,YACG,iBAAgC,sBAAsB,EACtD,QAAQ,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC;AACjD,YACG,iBAAgC,sBAAsB,EACtD,QAAQ,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,QAA6B;AACjD,QAAI,KAAK,kBAAkB,IAAI,MAAM,GAAG;AACtC;AAAA,IACF;AAEA,UAAM,WAAW,OAAO,QAAQ;AAChC,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,SAAK,kBAAkB,IAAI,MAAM;AAEjC,QAAI,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AACxC,WAAK,eAAe,MAAM;AAAA,IAC5B,OAAO;AACL,WAAK,IAAI,mBAAmB,QAAQ,MAAM,OAAO,QAAQ,OAAO,QAAQ;AAAA,IAC1E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,QAA6B;AACjD,QAAI,KAAK,kBAAkB,IAAI,MAAM,GAAG;AACtC;AAAA,IACF;AAEA,UAAM,WAAW,OAAO,QAAQ;AAChC,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,SAAK,kBAAkB,IAAI,MAAM;AAEjC,QAAI,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AACxC,WAAK,eAAe,MAAM;AAAA,IAC5B,OAAO;AACL,WAAK,IAAI,mBAAmB,QAAQ,MAAM,OAAO,QAAQ,GAAG;AAE5D,WAAK,gBAAgB,QAAQ,QAAQ;AAAA,IACvC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAA6B;AAClD,UAAM,MAAM,OAAO,QAAQ;AAE3B,QAAI,KAAK;AAEP,YAAM,YAAY,SAAS,cAAc,QAAQ;AAGjD,iBAAW,QAAQ,OAAO,YAAY;AACpC,YAAI,KAAK,SAAS,UAAU,KAAK,SAAS,YAAY;AACpD,oBAAU,aAAa,KAAK,MAAM,KAAK,KAAK;AAAA,QAC9C;AAAA,MACF;AAEA,gBAAU,MAAM;AAChB,gBAAU,gBAAgB,cAAc;AAGxC,aAAO,YAAY,aAAa,WAAW,MAAM;AAEjD,WAAK,IAAI,8BAA8B,GAAG;AAAA,IAC5C,OAAO;AAEL,YAAM,YAAY,SAAS,cAAc,QAAQ;AAEjD,iBAAW,QAAQ,OAAO,YAAY;AACpC,YAAI,KAAK,SAAS,QAAQ;AACxB,oBAAU,aAAa,KAAK,MAAM,KAAK,KAAK;AAAA,QAC9C;AAAA,MACF;AAEA,gBAAU,cAAc,OAAO;AAC/B,gBAAU,gBAAgB,cAAc;AAExC,aAAO,YAAY,aAAa,WAAW,MAAM;AAEjD,WAAK,IAAI,yBAAyB;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAA6B;AAClD,UAAM,MAAM,OAAO,QAAQ;AAC3B,QAAI,CAAC,KAAK;AACR;AAAA,IACF;AAGA,UAAM,cAAc,OAAO,eAAe;AAAA,MACxC;AAAA,IACF;AACA,iBAAa,OAAO;AAGpB,WAAO,MAAM;AACb,WAAO,gBAAgB,UAAU;AACjC,WAAO,gBAAgB,cAAc;AACrC,WAAO,MAAM,UAAU;AAEvB,SAAK,IAAI,qBAAqB,GAAG;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,QAAuB,UAAiC;AAE9E,WAAO,MAAM,UAAU;AAGvB,UAAM,cAAc,SAAS,cAAc,KAAK;AAChD,gBAAY,YAAY;AACxB,gBAAY,aAAa,iBAAiB,QAAQ;AAClD,gBAAY,YAAY;AAAA;AAAA;AAAA;AAAA,YAIhB,KAAK,gBAAgB,QAAQ,CAAC;AAAA;AAAA;AAAA;AAMtC,UAAM,MAAM,YAAY,cAAc,QAAQ;AAC9C,SAAK,iBAAiB,SAAS,MAAM;AAEnC,aAAO;AAAA,QACL,IAAI,YAAY,sBAAsB;AAAA,UACpC,QAAQ,EAAE,SAAS;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,WAAO,YAAY,aAAa,aAAa,OAAO,WAAW;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,UAAiC;AAExD,UAAM,UAAU,SAAS;AAAA,MACvB,wBAAwB,QAAQ;AAAA,IAClC;AACA,YAAQ,QAAQ,CAAC,WAAW,KAAK,eAAe,MAAM,CAAC;AAGvD,UAAM,UAAU,SAAS;AAAA,MACvB,wBAAwB,QAAQ;AAAA,IAClC;AACA,YAAQ,QAAQ,CAAC,WAAW,KAAK,eAAe,MAAM,CAAC;AAEvD,SAAK;AAAA,MACH,aAAa,QAAQ,MAAM,aAAa,QAAQ,MAAM,gBAAgB,QAAQ;AAAA,IAChF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,UAAmC;AACzD,UAAM,QAAyC;AAAA,MAC7C,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AACA,WAAO,MAAM,QAAQ,KAAK;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAO,MAAuB;AACpC,QAAI,KAAK,OAAO,OAAO;AACrB,cAAQ,IAAI,mBAAmB,GAAG,IAAI;AAAA,IACxC;AAAA,EACF;AACF;;;AC1UO,IAAM,aAAN,MAAiB;AAAA,EAItB,YAAY,QAAuB;AACjC,SAAK,SAAS;AACd,SAAK,UAAU,OAAO,YAAY,QAAQ,OAAO,EAAE;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,SAA0D;AAC1E,UAAM,UAAU;AAAA,MACd,GAAG;AAAA,MACH,UAAU;AAAA,QACR,WAAW,OAAO,cAAc,cAAc,UAAU,YAAY;AAAA,QACpE,UAAU,OAAO,cAAc,cAAc,UAAU,WAAW;AAAA,QAClE,kBACE,OAAO,WAAW,cACd,GAAG,OAAO,OAAO,KAAK,IAAI,OAAO,OAAO,MAAM,KAC9C;AAAA,QACN,UAAU;AAAA,QACV,GAAG,QAAQ;AAAA,MACb;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,KAAK,MAAM,YAAY;AAAA,MAC5C,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,2BAA2B,SAAS,MAAM,EAAE;AAAA,IAC9D;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WACJ,QACA,mBAC8B;AAC9B,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,WAAW,MAAM,KAAK,MAAM,YAAY,MAAM,EAAE;AAEtD,QAAI,SAAS,WAAW,KAAK;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,0BAA0B,SAAS,MAAM,EAAE;AAAA,IAC7D;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,WAAkC;AACpD,UAAM,WAAW,MAAM,KAAK,MAAM,YAAY,SAAS,IAAI;AAAA,MACzD,QAAQ;AAAA,IACV,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,6BAA6B,SAAS,MAAM,EAAE;AAAA,IAChE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,QAA6C;AAC/D,UAAM,WAAW,MAAM,KAAK,MAAM,WAAW,MAAM,EAAE;AAErD,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,8BAA8B,SAAS,MAAM,EAAE;AAAA,IACjE;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,QAAkC;AACpD,UAAM,SAAS,IAAI,gBAAgB,EAAE,OAAO,CAAC;AAC7C,UAAM,WAAW,MAAM,KAAK,MAAM,mBAAmB,MAAM,EAAE;AAE7D,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,6BAA6B,SAAS,MAAM,EAAE;AAAA,IAChE;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,MACZ,MACA,UAAuB,CAAC,GACL;AACnB,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAElC,UAAM,UAAuB;AAAA,MAC3B,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,GAAG,KAAK,oBAAoB;AAAA,MAC5B,GAAI,QAAQ,WAAW,CAAC;AAAA,IAC1B;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,GAAG;AAAA,QACH;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AAED,WAAK,IAAI,GAAG,QAAQ,UAAU,KAAK,IAAI,IAAI,KAAK,SAAS,MAAM;AAC/D,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,IAAI,gBAAgB,KAAK;AAC9B,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAA8C;AACpD,UAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,EAAE,SAAS;AAIzD,UAAM,YAAY,KAAK,WAAW,GAAG,KAAK,OAAO,MAAM,IAAI,SAAS,EAAE;AAEtE,WAAO;AAAA,MACL,uBAAuB;AAAA,MACvB,uBAAuB,UAAU,SAAS;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,KAAqB;AACtC,QAAI,OAAO;AACX,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,aAAQ,OAAO,KAAM,IAAI,WAAW,CAAC;AAAA,IACvC;AACA,YAAQ,SAAS,GAAG,SAAS,EAAE;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAO,MAAuB;AACpC,QAAI,KAAK,OAAO,OAAO;AACrB,cAAQ,IAAI,gBAAgB,GAAG,IAAI;AAAA,IACrC;AAAA,EACF;AACF;;;AC1MO,IAAM,eAAN,MAAiF;AAAA,EAAjF;AACL,SAAQ,YAA4D,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM5E,GACE,OACA,UACY;AACZ,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AAEA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAkC;AAGjE,WAAO,MAAM,KAAK,IAAI,OAAO,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,IACE,OACA,UACM;AACN,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAkC;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA,EAKA,KAA6B,OAAU,MAAuB;AAC5D,SAAK,UAAU,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa;AAC/C,UAAI;AACF,iBAAS,IAAI;AAAA,MACf,SAAS,OAAO;AACd,gBAAQ,MAAM,8BAA8B,OAAO,KAAK,CAAC,KAAK,KAAK;AAAA,MACrE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,KACE,OACA,UACY;AACZ,UAAM,UAAU,CAAC,SAAoB;AACnC,WAAK,IAAI,OAAO,OAAO;AACvB,eAAS,IAAI;AAAA,IACf;AAEA,WAAO,KAAK,GAAG,OAAO,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAmC,OAAgB;AACjD,SAAK,UAAU,OAAO,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAsC,OAAkB;AACtD,WAAO,KAAK,UAAU,IAAI,KAAK,GAAG,QAAQ;AAAA,EAC5C;AACF;;;AClEA,SAAS,gBAA0B;AACjC,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,CAAC,QAAQ;AAAA,EAClB;AAEA,QAAM,aAAuB,CAAC;AAG9B,MAAI;AAEF,UAAM,KAAK,UAAU;AACrB,QAAI,GAAG,SAAS,QAAQ,EAAG,YAAW,KAAK,QAAQ;AAAA,aAC1C,GAAG,SAAS,SAAS,EAAG,YAAW,KAAK,SAAS;AAAA,aACjD,GAAG,SAAS,QAAQ,EAAG,YAAW,KAAK,QAAQ;AAAA,aAC/C,GAAG,SAAS,MAAM,EAAG,YAAW,KAAK,MAAM;AAAA,QAC/C,YAAW,KAAK,OAAO;AAAA,EAC9B,QAAQ;AACN,eAAW,KAAK,iBAAiB;AAAA,EACnC;AAGA,MAAI;AACF,eAAW,KAAK,UAAU,YAAY,cAAc;AAAA,EACtD,QAAQ;AACN,eAAW,KAAK,cAAc;AAAA,EAChC;AAGA,MAAI;AACF,UAAM,QAAQ,OAAO,OAAO;AAC5B,QAAI,SAAS,KAAM,YAAW,KAAK,IAAI;AAAA,aAC9B,SAAS,KAAM,YAAW,KAAK,KAAK;AAAA,aACpC,SAAS,KAAM,YAAW,KAAK,IAAI;AAAA,aACnC,SAAS,IAAK,YAAW,KAAK,QAAQ;AAAA,QAC1C,YAAW,KAAK,QAAQ;AAAA,EAC/B,QAAQ;AACN,eAAW,KAAK,gBAAgB;AAAA,EAClC;AAGA,MAAI;AACF,UAAM,QAAQ,OAAO,OAAO;AAC5B,QAAI,SAAS,GAAI,YAAW,KAAK,YAAY;AAAA,QACxC,YAAW,KAAK,gBAAgB;AAAA,EACvC,QAAQ;AACN,eAAW,KAAK,eAAe;AAAA,EACjC;AAGA,MAAI;AACF,UAAM,UAAS,oBAAI,KAAK,GAAE,kBAAkB;AAC5C,UAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,MAAM,IAAI,EAAE;AAC9C,UAAM,OAAO,UAAU,IAAI,MAAM;AACjC,eAAW,KAAK,KAAK,IAAI,GAAG,KAAK,EAAE;AAAA,EACrC,QAAQ;AACN,eAAW,KAAK,YAAY;AAAA,EAC9B;AAGA,MAAI;AACF,UAAM,WAAW,UAAU,UAAU,YAAY,KAAK;AACtD,QAAI,SAAS,SAAS,KAAK,EAAG,YAAW,KAAK,KAAK;AAAA,aAC1C,SAAS,SAAS,KAAK,EAAG,YAAW,KAAK,KAAK;AAAA,aAC/C,SAAS,SAAS,OAAO,EAAG,YAAW,KAAK,OAAO;AAAA,aACnD,SAAS,SAAS,QAAQ,KAAK,SAAS,SAAS,MAAM;AAC9D,iBAAW,KAAK,KAAK;AAAA,aACd,SAAS,SAAS,SAAS,EAAG,YAAW,KAAK,SAAS;AAAA,QAC3D,YAAW,KAAK,gBAAgB;AAAA,EACvC,QAAQ;AACN,eAAW,KAAK,kBAAkB;AAAA,EACpC;AAGA,MAAI;AACF,QAAI,kBAAkB,UAAU,UAAU,iBAAiB,GAAG;AAC5D,iBAAW,KAAK,OAAO;AAAA,IACzB,OAAO;AACL,iBAAW,KAAK,UAAU;AAAA,IAC5B;AAAA,EACF,QAAQ;AACN,eAAW,KAAK,eAAe;AAAA,EACjC;AAGA,MAAI;AACF,QAAI,UAAU,eAAe,KAAK;AAChC,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAKA,eAAe,OAAO,SAAkC;AACtD,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,QAAQ,QAAQ;AAE3D,WAAO,WAAW,OAAO;AAAA,EAC3B;AAEA,MAAI;AACF,UAAM,UAAU,IAAI,YAAY;AAChC,UAAM,OAAO,QAAQ,OAAO,OAAO;AACnC,UAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AAC7D,UAAM,YAAY,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;AACvD,WAAO,UAAU,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,EACtE,QAAQ;AACN,WAAO,WAAW,OAAO;AAAA,EAC3B;AACF;AAKA,SAAS,WAAW,KAAqB;AACvC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,WAAQ,OAAO,KAAM,IAAI,WAAW,CAAC;AAAA,EACvC;AACA,UAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClD;AAWA,eAAsB,sBAAuC;AAC3D,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,WAAW,KAAK,GAAG;AACpC,QAAM,OAAO,MAAM,OAAO,QAAQ;AAGlC,SAAO,MAAM,KAAK,UAAU,GAAG,EAAE,CAAC;AACpC;;;AC/JO,IAAM,cAAc;;;ACuB3B,IAAM,iBAAyC;AAAA,EAC7C,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,IAAI;AAAA,IACF,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,oBAAoB;AAAA,EACtB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,cAAc;AAAA,IACd,kBAAkB;AAAA,EACpB;AAAA,EACA,YAAY,CAAC,aAAa,cAAc,aAAa,aAAa,QAAQ;AAAA,EAC1E,OAAO;AACT;AAKA,IAAM,kBAAqC;AAAA,EACzC,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,QAAQ;AACV;AAKO,IAAM,iBAAN,MAAqB;AAAA,EAW1B,YAAY,QAAuB;AALnC,SAAQ,iBAAsC;AAC9C,SAAQ,cAAc;AACtB,SAAQ,gBAAgB;AACxB,SAAQ,oBAA4B;AAGlC,SAAK,SAAS,KAAK,YAAY,MAAM;AACrC,SAAK,UAAU,IAAI,eAAe,KAAK,MAAM;AAC7C,SAAK,gBAAgB,IAAI,cAAc,KAAK,MAAM;AAClD,SAAK,MAAM,IAAI,WAAW,KAAK,MAAM;AACrC,SAAK,SAAS,IAAI,aAAa;AAE/B,SAAK,IAAI,uCAAuC,KAAK,MAAM;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,aAAa;AACpB,WAAK,IAAI,+BAA+B;AACxC;AAAA,IACF;AAEA,QAAI;AACF,WAAK,IAAI,gCAAgC;AAGzC,WAAK,oBAAoB,MAAM,oBAAoB;AAGnD,WAAK,iBAAiB,KAAK,QAAQ,IAAI;AAEvC,UAAI,KAAK,gBAAgB;AACvB,aAAK,IAAI,gCAAgC,KAAK,cAAc;AAG5D,YAAI,KAAK,iBAAiB,GAAG;AAC3B,eAAK,IAAI,2BAA2B;AACpC,eAAK,QAAQ,MAAM;AACnB,eAAK,iBAAiB;AAAA,QACxB,OAAO;AAEL,eAAK,aAAa;AAAA,QACpB;AAAA,MACF;AAGA,WAAK,cAAc,KAAK;AAExB,WAAK,cAAc;AACnB,WAAK,KAAK,QAAQ,KAAK,cAAc;AAGrC,UAAI,KAAK,aAAa,GAAG;AACvB,aAAK,WAAW;AAAA,MAClB;AAEA,WAAK,IAAI,yCAAyC;AAAA,IACpD,SAAS,OAAO;AACd,WAAK,YAAY,KAAc;AAC/B,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,WAAW,UAAoC;AAC7C,QAAI,CAAC,KAAK,gBAAgB;AACxB,aAAO,aAAa;AAAA,IACtB;AACA,WAAO,KAAK,eAAe,WAAW,QAAQ,KAAK;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAA2B;AAC1C,QAAI,CAAC,KAAK,gBAAgB;AACxB,aAAO;AAAA,IACT;AACA,WAAO,KAAK,eAAe,QAAQ,QAAQ,KAAK;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAkC;AAChC,WAAO,KAAK,iBAAiB,EAAE,GAAG,KAAK,eAAe,IAAI;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,OAAoC;AACnD,UAAM,aAAa,KAAK,sBAAsB,KAAK;AAGnD,eAAW,YAAY;AAEvB,UAAM,aAA2B;AAAA,MAC/B;AAAA,MACA,SAAS,aAAa,SAAS,MAAM,UAAU,MAAM,UAAU,CAAC;AAAA,MAChE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,SAAS;AAAA,IACX;AAEA,QAAI;AAEF,YAAM,WAAW,MAAM,KAAK,IAAI,YAAY;AAAA,QAC1C,QAAQ,KAAK,OAAO;AAAA,QACpB,mBAAmB,KAAK;AAAA,QACxB,SAAS;AAAA,MACX,CAAC;AAED,iBAAW,YAAY,SAAS;AAChC,iBAAW,YAAY,SAAS;AAGhC,WAAK,QAAQ,IAAI,UAAU;AAC3B,WAAK,iBAAiB;AAGtB,WAAK,aAAa;AAGlB,WAAK,KAAK,UAAU,UAAU;AAC9B,WAAK,OAAO,kBAAkB,UAAU;AAExC,WAAK,IAAI,kBAAkB,UAAU;AAAA,IACvC,SAAS,OAAO;AAEd,WAAK,IAAI,8BAA8B,KAAK;AAC5C,WAAK,QAAQ,IAAI,UAAU;AAC3B,WAAK,iBAAiB;AACtB,WAAK,aAAa;AAClB,WAAK,KAAK,UAAU,UAAU;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAC/B,UAAM,gBAAmC;AAAA,MACvC,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAEA,UAAM,KAAK,WAAW,aAAa;AACnC,SAAK,KAAK,cAAc,KAAK,cAAe;AAC5C,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAC/B,UAAM,oBAAuC;AAAA,MAC3C,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAEA,UAAM,KAAK,WAAW,iBAAiB;AACvC,SAAK,KAAK,cAAc,KAAK,cAAe;AAC5C,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAC/B,QAAI,KAAK,gBAAgB,WAAW;AAClC,UAAI;AACF,cAAM,KAAK,IAAI,cAAc,KAAK,eAAe,SAAS;AAAA,MAC5D,SAAS,OAAO;AACd,aAAK,IAAI,+BAA+B,KAAK;AAAA,MAC/C;AAAA,IACF;AAEA,SAAK,QAAQ,MAAM;AACnB,SAAK,iBAAiB;AACtB,SAAK,cAAc,SAAS;AAE5B,SAAK,IAAI,sBAAsB;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAiC;AACrC,UAAM,aAAa;AAAA,MACjB,gBAAgB,KAAK;AAAA,MACrB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,QAAQ,KAAK,OAAO;AAAA,MACpB,mBAAmB,KAAK;AAAA,IAC1B;AAEA,WAAO,KAAK,UAAU,YAAY,MAAM,CAAC;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,eAAwB;AACtB,QAAI,CAAC,KAAK,gBAAgB;AACxB,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,iBAAiB,GAAG;AAC3B,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,OAAO,SAAS,kBAAkB;AACzC,YAAM,cAAc,IAAI,KAAK,KAAK,eAAe,SAAS;AAC1D,YAAM,cAAc,IAAI,KAAK,WAAW;AACxC,kBAAY;AAAA,QACV,YAAY,QAAQ,IAAI,KAAK,OAAO,QAAQ;AAAA,MAC9C;AAEA,UAAI,oBAAI,KAAK,IAAI,aAAa;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,QAAI,KAAK,eAAe;AACtB;AAAA,IACF;AAEA,SAAK,gBAAgB;AACrB,SAAK,KAAK,eAAe,MAAS;AAClC,SAAK,OAAO,eAAe;AAI3B,SAAK,IAAI,cAAc;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,QAAI,CAAC,KAAK,eAAe;AACvB;AAAA,IACF;AAEA,SAAK,gBAAgB;AACrB,SAAK,KAAK,eAAe,MAAS;AAClC,SAAK,OAAO,eAAe;AAE3B,SAAK,IAAI,eAAe;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,KAAK,iBAAiB,MAAS;AACpC,SAAK,IAAI,iBAAiB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA2B;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,GACE,OACA,UACY;AACZ,WAAO,KAAK,OAAO,GAAG,OAAO,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,IACE,OACA,UACM;AACN,SAAK,OAAO,IAAI,OAAO,QAAQ;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,YAAY,QAAsC;AACxD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,MACH,IAAI,EAAE,GAAG,eAAe,IAAI,GAAG,OAAO,GAAG;AAAA,MACzC,SAAS,EAAE,GAAG,eAAe,SAAS,GAAG,OAAO,QAAQ;AAAA,IAC1D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,OAAwC;AACpE,QAAI,gBAAgB,SAAS,MAAM,YAAY;AAC7C,aAAO,EAAE,GAAG,iBAAiB,GAAG,MAAM,WAAW;AAAA,IACnD;AAEA,WAAO,EAAE,GAAG,iBAAiB,GAAI,MAAqC;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAqB;AAC3B,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,eAAW,CAAC,UAAU,OAAO,KAAK,OAAO;AAAA,MACvC,KAAK,eAAe;AAAA,IACtB,GAAG;AACD,UAAI,SAAS;AACX,aAAK,cAAc,eAAe,QAA2B;AAAA,MAC/D,OAAO;AACL,aAAK,cAAc,gBAAgB,QAA2B;AAAA,MAChE;AAAA,IACF;AAGA,SAAK,wBAAwB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKQ,0BAAgC;AACtC,QAAI,OAAO,WAAW,eAAe,CAAC,KAAK,gBAAgB;AACzD;AAAA,IACF;AAEA,UAAM,OAAQ,OAA8D;AAC5E,QAAI,OAAO,SAAS,YAAY;AAC9B;AAAA,IACF;AAEA,UAAM,EAAE,WAAW,IAAI,KAAK;AAE5B,SAAK,WAAW,UAAU;AAAA,MACxB,YAAY,WAAW,YAAY,YAAY;AAAA,MAC/C,cAAc,WAAW,YAAY,YAAY;AAAA,MACjD,oBAAoB,WAAW,YAAY,YAAY;AAAA,MACvD,mBAAmB,WAAW,YAAY,YAAY;AAAA,MACtD,uBAAuB,WAAW,aAAa,YAAY;AAAA,MAC3D,yBAAyB,WAAW,aAAa,YAAY;AAAA,MAC7D,kBAAkB;AAAA,IACpB,CAAC;AAED,SAAK,IAAI,6BAA6B;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAA4B;AAClC,QAAI,CAAC,KAAK,gBAAgB,WAAW;AAEnC,UAAI,KAAK,gBAAgB,aAAa,KAAK,OAAO,SAAS,cAAc;AACvE,cAAM,cAAc,IAAI,KAAK,KAAK,eAAe,SAAS;AAC1D,cAAM,aAAa,IAAI,KAAK,WAAW;AACvC,mBAAW;AAAA,UACT,WAAW,QAAQ,IAAI,KAAK,OAAO,QAAQ;AAAA,QAC7C;AACA,eAAO,oBAAI,KAAK,IAAI;AAAA,MACtB;AACA,aAAO;AAAA,IACT;AAEA,WAAO,oBAAI,KAAK,IAAI,IAAI,KAAK,KAAK,eAAe,SAAS;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKQ,KACN,OACA,MACM;AACN,SAAK,OAAO,KAAK,OAAO,IAAI;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,OAAoB;AACtC,SAAK,IAAI,UAAU,KAAK;AACxB,SAAK,KAAK,SAAS,KAAK;AACxB,SAAK,OAAO,UAAU,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAO,MAAuB;AACpC,QAAI,KAAK,OAAO,OAAO;AACrB,cAAQ,IAAI,gBAAgB,GAAG,IAAI;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,aAAqB;AAC1B,WAAO;AAAA,EACT;AACF;;;AP1SI,SA2FO,UA3FP,KAyIA,YAzIA;AA9IJ,IAAM,iBAAiB,cAA0C,IAAI;AAiB9D,IAAM,kBAA4C,CAAC;AAAA,EACxD;AAAA,EACA;AACF,MAAM;AACJ,QAAM,CAAC,SAAS,UAAU,IAAI,SAAgC,IAAI;AAClE,QAAM,CAAC,SAAS,UAAU,IAAI,SAA8B,IAAI;AAChE,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,KAAK;AACxD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAS,KAAK;AAG5D,YAAU,MAAM;AACd,UAAM,iBAAiB,IAAI,eAAe,MAAM;AAChD,eAAW,cAAc;AAGzB,UAAM,cAAc,eAAe,GAAG,UAAU,CAAC,eAAe;AAC9D,iBAAW,UAAU;AAAA,IACvB,CAAC;AAED,UAAM,kBAAkB,eAAe,GAAG,eAAe,MAAM;AAC7D,yBAAmB,IAAI;AAAA,IACzB,CAAC;AAED,UAAM,kBAAkB,eAAe,GAAG,eAAe,MAAM;AAC7D,yBAAmB,KAAK;AAAA,IAC1B,CAAC;AAGD,mBACG,KAAK,EACL,KAAK,MAAM;AACV,iBAAW,eAAe,WAAW,CAAC;AACtC,uBAAiB,IAAI;AACrB,mBAAa,KAAK;AAClB,yBAAmB,eAAe,gBAAgB,CAAC;AAAA,IACrD,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,cAAQ,MAAM,wCAAwC,KAAK;AAC3D,mBAAa,KAAK;AAAA,IACpB,CAAC;AAGH,WAAO,MAAM;AACX,kBAAY;AACZ,sBAAgB;AAChB,sBAAgB;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,QAAM,aAAa;AAAA,IACjB,CAAC,aAAuC;AACtC,aAAO,SAAS,WAAW,QAAQ,KAAK,aAAa;AAAA,IACvD;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAEA,QAAM,YAAY,YAAY,YAAY;AACxC,UAAM,SAAS,UAAU;AAAA,EAC3B,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,YAAY,YAAY,YAAY;AACxC,UAAM,SAAS,UAAU;AAAA,EAC3B,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,gBAAgB;AAAA,IACpB,OAAO,eAA2C;AAChD,YAAM,SAAS,WAAW,UAAU;AACpC,eAAS,WAAW;AAAA,IACtB;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAEA,QAAM,aAAa,YAAY,MAAM;AACnC,aAAS,WAAW;AAAA,EACtB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,aAAa,YAAY,MAAM;AACnC,aAAS,WAAW;AAAA,EACtB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,eAAe,YAAY,MAAM;AACrC,aAAS,aAAa;AAAA,EACxB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,eAAe,QAAQ,MAAM;AACjC,WAAO,SAAS,aAAa,KAAK;AAAA,EACpC,GAAG,CAAC,SAAS,OAAO,CAAC;AAGrB,QAAM,eAAe;AAAA,IACnB,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SACE,oBAAC,eAAe,UAAf,EAAwB,OAAO,cAC7B,UACH;AAEJ;AAsBO,SAAS,WAAW,UAA4B;AACrD,QAAM,UAAU,WAAW,cAAc;AAEzC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,MAAI,UAAU;AACZ,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,QAAQ,WAAW,QAAQ;AAAA,IACtC;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,oBAA2C;AACzD,QAAM,UAAU,WAAW,cAAc;AACzC,SAAO,SAAS,WAAW;AAC7B;AAiCO,IAAM,cAAoC,CAAC;AAAA,EAChD;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AACb,MAAM;AACJ,QAAM,EAAE,YAAY,UAAU,IAAI,WAAW;AAE7C,MAAI,WAAW;AACb,WAAO,gCAAG,oBAAS;AAAA,EACrB;AAEA,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO,gCAAG,uBAAY;AAAA,EACxB;AAEA,SAAO,gCAAG,UAAS;AACrB;AAmBO,IAAM,qBAAkD,CAAC;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd,MAAM;AACJ,QAAM,EAAE,aAAa,IAAI,WAAW;AAEpC,QAAM,gBAAiD;AAAA,IACrD,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,EACV;AAEA,QAAM,iBAAiB,2BAA2B,cAAc,QAAQ,CAAC;AAEzE,SACE,qBAAC,SAAI,WAAW,0BAA0B,SAAS,IACjD;AAAA,wBAAC,OAAG,qBAAW,gBAAe;AAAA,IAC9B,oBAAC,YAAO,MAAK,UAAS,SAAS,cAC5B,wBAAc,gCACjB;AAAA,KACF;AAEJ;AA+DO,IAAM,gBAAwC,CAAC,EAAE,QAAQ,UAAU,MAAM;AAC9E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,WAAW;AAEf,QAAM,cAAwC;AAAA,IAC5C,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,SAAS;AAAA,EACX;AAGA,MAAI,QAAQ;AACV,WAAO,gCAAG,iBAAO,WAAW,GAAE;AAAA,EAChC;AAGA,MAAI,CAAC,iBAAiB;AACpB,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,qBAAqB,aAAa,EAAE;AAAA,MAC/C,MAAK;AAAA,MACL,cAAW;AAAA,MACX,cAAW;AAAA,MAEX,+BAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,QAAG,sCAAwB;AAAA,QAC5B,oBAAC,OAAE,6GAGH;AAAA,QAEA,qBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cACV;AAAA;AAAA,UAED;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cACV;AAAA;AAAA,UAED;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cACV;AAAA;AAAA,UAED;AAAA,WACF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;","names":[]} \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/vue/index.d.mts b/docs-src/consent-sdk/dist/vue/index.d.mts deleted file mode 100644 index 41b77fa..0000000 --- a/docs-src/consent-sdk/dist/vue/index.d.mts +++ /dev/null @@ -1,483 +0,0 @@ -import * as vue from 'vue'; -import { InjectionKey, Ref, PropType } from 'vue'; - -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * Minimaler Consent-Input fuer setConsent() - */ -type ConsentInput = Partial | { - categories?: Partial; - vendors?: ConsentVendors; -}; -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} -/** - * Event-Typen - */ -type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error'; -/** - * Event-Listener Callback - */ -type ConsentEventCallback = (data: T) => void; -/** - * Event-Daten fuer verschiedene Events - */ -type ConsentEventData = { - init: ConsentState | null; - change: ConsentState; - accept_all: ConsentState; - reject_all: ConsentState; - save_selection: ConsentState; - banner_show: undefined; - banner_hide: undefined; - settings_open: undefined; - settings_close: undefined; - vendor_enable: string; - vendor_disable: string; - error: Error; -}; - -/** - * ConsentManager - Hauptklasse fuer das Consent Management - * - * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. - */ - -/** - * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung - */ -declare class ConsentManager { - private config; - private storage; - private scriptBlocker; - private api; - private events; - private currentConsent; - private initialized; - private bannerVisible; - private deviceFingerprint; - constructor(config: ConsentConfig); - /** - * SDK initialisieren - */ - init(): Promise; - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category: ConsentCategory): boolean; - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId: string): boolean; - /** - * Aktuellen Consent-State abrufen - */ - getConsent(): ConsentState | null; - /** - * Consent setzen - */ - setConsent(input: ConsentInput): Promise; - /** - * Alle Kategorien akzeptieren - */ - acceptAll(): Promise; - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - rejectAll(): Promise; - /** - * Alle Einwilligungen widerrufen - */ - revokeAll(): Promise; - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - exportConsent(): Promise; - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent(): boolean; - /** - * Banner anzeigen - */ - showBanner(): void; - /** - * Banner verstecken - */ - hideBanner(): void; - /** - * Einstellungs-Modal oeffnen - */ - showSettings(): void; - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible(): boolean; - /** - * Event-Listener registrieren - */ - on(event: T, callback: ConsentEventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: T, callback: ConsentEventCallback): void; - /** - * Konfiguration zusammenfuehren - */ - private mergeConfig; - /** - * Consent-Input normalisieren - */ - private normalizeConsentInput; - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - private applyConsent; - /** - * Google Consent Mode v2 aktualisieren - */ - private updateGoogleConsentMode; - /** - * Pruefen ob Consent abgelaufen - */ - private isConsentExpired; - /** - * Event emittieren - */ - private emit; - /** - * Fehler behandeln - */ - private handleError; - /** - * Debug-Logging - */ - private log; - /** - * SDK-Version abrufen - */ - static getVersion(): string; -} - -declare const CONSENT_KEY: InjectionKey; -interface ConsentContext { - manager: Ref; - consent: Ref; - isInitialized: Ref; - isLoading: Ref; - isBannerVisible: Ref; - needsConsent: Ref; - hasConsent: (category: ConsentCategory) => boolean; - acceptAll: () => Promise; - rejectAll: () => Promise; - saveSelection: (categories: Partial) => Promise; - showBanner: () => void; - hideBanner: () => void; - showSettings: () => void; -} -/** - * Haupt-Composable fuer Consent-Zugriff - * - * @example - * ```vue - * - * ``` - */ -declare function useConsent(): ConsentContext; -/** - * Consent-Provider einrichten (in App.vue aufrufen) - * - * @example - * ```vue - * - * ``` - */ -declare function provideConsent(config: ConsentConfig): ConsentContext; -/** - * ConsentProvider - Wrapper-Komponente - * - * @example - * ```vue - * - * - * - * ``` - */ -declare const ConsentProvider: vue.DefineComponent; - required: true; - }; -}>, () => vue.VNode[] | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly; - required: true; - }; -}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * ConsentGate - Zeigt Inhalt nur bei Consent - * - * @example - * ```vue - * - * - * - * - * ``` - */ -declare const ConsentGate: vue.DefineComponent; - required: true; - }; -}>, () => vue.VNode[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly; - required: true; - }; -}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * ConsentPlaceholder - Placeholder fuer blockierten Inhalt - * - * @example - * ```vue - * - * ``` - */ -declare const ConsentPlaceholder: vue.DefineComponent; - required: true; - }; - message: { - type: StringConstructor; - default: string; - }; - buttonText: { - type: StringConstructor; - default: string; - }; -}>, () => vue.VNode, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly; - required: true; - }; - message: { - type: StringConstructor; - default: string; - }; - buttonText: { - type: StringConstructor; - default: string; - }; -}>> & Readonly<{}>, { - message: string; - buttonText: string; -}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * ConsentBanner - Cookie-Banner Komponente - * - * @example - * ```vue - * - * - * - * ``` - */ -declare const ConsentBanner: vue.DefineComponent<{}, () => vue.VNode | vue.VNode[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * Vue Plugin fuer globale Installation - * - * @example - * ```ts - * import { createApp } from 'vue'; - * import { ConsentPlugin } from '@breakpilot/consent-sdk/vue'; - * - * const app = createApp(App); - * app.use(ConsentPlugin, { - * apiEndpoint: 'https://consent.example.com/api/v1', - * siteId: 'site_abc123', - * }); - * ``` - */ -declare const ConsentPlugin: { - install(app: { - provide: (key: symbol | string, value: unknown) => void; - }, config: ConsentConfig): void; -}; - -export { CONSENT_KEY, ConsentBanner, type ConsentContext, ConsentGate, ConsentPlaceholder, ConsentPlugin, ConsentProvider, provideConsent, useConsent }; diff --git a/docs-src/consent-sdk/dist/vue/index.d.ts b/docs-src/consent-sdk/dist/vue/index.d.ts deleted file mode 100644 index 41b77fa..0000000 --- a/docs-src/consent-sdk/dist/vue/index.d.ts +++ /dev/null @@ -1,483 +0,0 @@ -import * as vue from 'vue'; -import { InjectionKey, Ref, PropType } from 'vue'; - -/** - * Consent SDK Types - * - * DSGVO/TTDSG-konforme Typdefinitionen für das Consent Management System. - */ -/** - * Standard-Consent-Kategorien nach IAB TCF 2.2 - */ -type ConsentCategory = 'essential' | 'functional' | 'analytics' | 'marketing' | 'social'; -/** - * Consent-Status pro Kategorie - */ -type ConsentCategories = Record; -/** - * Consent-Status pro Vendor - */ -type ConsentVendors = Record; -/** - * Aktueller Consent-Zustand - */ -interface ConsentState { - /** Consent pro Kategorie */ - categories: ConsentCategories; - /** Consent pro Vendor (optional, für granulare Kontrolle) */ - vendors: ConsentVendors; - /** Zeitstempel der letzten Aenderung */ - timestamp: string; - /** SDK-Version bei Erstellung */ - version: string; - /** Eindeutige Consent-ID vom Backend */ - consentId?: string; - /** Ablaufdatum */ - expiresAt?: string; - /** IAB TCF String (falls aktiviert) */ - tcfString?: string; -} -/** - * Minimaler Consent-Input fuer setConsent() - */ -type ConsentInput = Partial | { - categories?: Partial; - vendors?: ConsentVendors; -}; -/** - * UI-Position des Banners - */ -type BannerPosition = 'bottom' | 'top' | 'center'; -/** - * Banner-Layout - */ -type BannerLayout = 'bar' | 'modal' | 'floating'; -/** - * Farbschema - */ -type BannerTheme = 'light' | 'dark' | 'auto'; -/** - * UI-Konfiguration - */ -interface ConsentUIConfig { - /** Position des Banners */ - position?: BannerPosition; - /** Layout-Typ */ - layout?: BannerLayout; - /** Farbschema */ - theme?: BannerTheme; - /** Pfad zu Custom CSS */ - customCss?: string; - /** z-index fuer Banner */ - zIndex?: number; - /** Scroll blockieren bei Modal */ - blockScrollOnModal?: boolean; - /** Custom Container-ID */ - containerId?: string; -} -/** - * Consent-Verhaltens-Konfiguration - */ -interface ConsentBehaviorConfig { - /** Muss Nutzer interagieren? */ - required?: boolean; - /** "Alle ablehnen" Button sichtbar */ - rejectAllVisible?: boolean; - /** "Alle akzeptieren" Button sichtbar */ - acceptAllVisible?: boolean; - /** Einzelne Kategorien waehlbar */ - granularControl?: boolean; - /** Einzelne Vendors waehlbar */ - vendorControl?: boolean; - /** Auswahl speichern */ - rememberChoice?: boolean; - /** Speicherdauer in Tagen */ - rememberDays?: number; - /** Nur in EU anzeigen (Geo-Targeting) */ - geoTargeting?: boolean; - /** Erneut nachfragen nach X Tagen */ - recheckAfterDays?: number; -} -/** - * TCF 2.2 Konfiguration - */ -interface TCFConfig { - /** TCF aktivieren */ - enabled?: boolean; - /** CMP ID */ - cmpId?: number; - /** CMP Version */ - cmpVersion?: number; -} -/** - * PWA-spezifische Konfiguration - */ -interface PWAConfig { - /** Offline-Unterstuetzung aktivieren */ - offlineSupport?: boolean; - /** Bei Reconnect synchronisieren */ - syncOnReconnect?: boolean; - /** Cache-Strategie */ - cacheStrategy?: 'stale-while-revalidate' | 'network-first' | 'cache-first'; -} -/** - * Haupt-Konfiguration fuer ConsentManager - */ -interface ConsentConfig { - /** API-Endpunkt fuer Consent-Backend */ - apiEndpoint: string; - /** Site-ID */ - siteId: string; - /** Sprache (ISO 639-1) */ - language?: string; - /** Fallback-Sprache */ - fallbackLanguage?: string; - /** UI-Konfiguration */ - ui?: ConsentUIConfig; - /** Consent-Verhaltens-Konfiguration */ - consent?: ConsentBehaviorConfig; - /** Aktive Kategorien */ - categories?: ConsentCategory[]; - /** TCF 2.2 Konfiguration */ - tcf?: TCFConfig; - /** PWA-Konfiguration */ - pwa?: PWAConfig; - /** Callback bei Consent-Aenderung */ - onConsentChange?: (consent: ConsentState) => void; - /** Callback wenn Banner angezeigt wird */ - onBannerShow?: () => void; - /** Callback wenn Banner geschlossen wird */ - onBannerHide?: () => void; - /** Callback bei Fehler */ - onError?: (error: Error) => void; - /** Debug-Modus aktivieren */ - debug?: boolean; -} -/** - * Event-Typen - */ -type ConsentEventType = 'init' | 'change' | 'accept_all' | 'reject_all' | 'save_selection' | 'banner_show' | 'banner_hide' | 'settings_open' | 'settings_close' | 'vendor_enable' | 'vendor_disable' | 'error'; -/** - * Event-Listener Callback - */ -type ConsentEventCallback = (data: T) => void; -/** - * Event-Daten fuer verschiedene Events - */ -type ConsentEventData = { - init: ConsentState | null; - change: ConsentState; - accept_all: ConsentState; - reject_all: ConsentState; - save_selection: ConsentState; - banner_show: undefined; - banner_hide: undefined; - settings_open: undefined; - settings_close: undefined; - vendor_enable: string; - vendor_disable: string; - error: Error; -}; - -/** - * ConsentManager - Hauptklasse fuer das Consent Management - * - * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. - */ - -/** - * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung - */ -declare class ConsentManager { - private config; - private storage; - private scriptBlocker; - private api; - private events; - private currentConsent; - private initialized; - private bannerVisible; - private deviceFingerprint; - constructor(config: ConsentConfig); - /** - * SDK initialisieren - */ - init(): Promise; - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category: ConsentCategory): boolean; - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId: string): boolean; - /** - * Aktuellen Consent-State abrufen - */ - getConsent(): ConsentState | null; - /** - * Consent setzen - */ - setConsent(input: ConsentInput): Promise; - /** - * Alle Kategorien akzeptieren - */ - acceptAll(): Promise; - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - rejectAll(): Promise; - /** - * Alle Einwilligungen widerrufen - */ - revokeAll(): Promise; - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - exportConsent(): Promise; - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent(): boolean; - /** - * Banner anzeigen - */ - showBanner(): void; - /** - * Banner verstecken - */ - hideBanner(): void; - /** - * Einstellungs-Modal oeffnen - */ - showSettings(): void; - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible(): boolean; - /** - * Event-Listener registrieren - */ - on(event: T, callback: ConsentEventCallback): () => void; - /** - * Event-Listener entfernen - */ - off(event: T, callback: ConsentEventCallback): void; - /** - * Konfiguration zusammenfuehren - */ - private mergeConfig; - /** - * Consent-Input normalisieren - */ - private normalizeConsentInput; - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - private applyConsent; - /** - * Google Consent Mode v2 aktualisieren - */ - private updateGoogleConsentMode; - /** - * Pruefen ob Consent abgelaufen - */ - private isConsentExpired; - /** - * Event emittieren - */ - private emit; - /** - * Fehler behandeln - */ - private handleError; - /** - * Debug-Logging - */ - private log; - /** - * SDK-Version abrufen - */ - static getVersion(): string; -} - -declare const CONSENT_KEY: InjectionKey; -interface ConsentContext { - manager: Ref; - consent: Ref; - isInitialized: Ref; - isLoading: Ref; - isBannerVisible: Ref; - needsConsent: Ref; - hasConsent: (category: ConsentCategory) => boolean; - acceptAll: () => Promise; - rejectAll: () => Promise; - saveSelection: (categories: Partial) => Promise; - showBanner: () => void; - hideBanner: () => void; - showSettings: () => void; -} -/** - * Haupt-Composable fuer Consent-Zugriff - * - * @example - * ```vue - * - * ``` - */ -declare function useConsent(): ConsentContext; -/** - * Consent-Provider einrichten (in App.vue aufrufen) - * - * @example - * ```vue - * - * ``` - */ -declare function provideConsent(config: ConsentConfig): ConsentContext; -/** - * ConsentProvider - Wrapper-Komponente - * - * @example - * ```vue - * - * - * - * ``` - */ -declare const ConsentProvider: vue.DefineComponent; - required: true; - }; -}>, () => vue.VNode[] | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly; - required: true; - }; -}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * ConsentGate - Zeigt Inhalt nur bei Consent - * - * @example - * ```vue - * - * - * - * - * ``` - */ -declare const ConsentGate: vue.DefineComponent; - required: true; - }; -}>, () => vue.VNode[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly; - required: true; - }; -}>> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * ConsentPlaceholder - Placeholder fuer blockierten Inhalt - * - * @example - * ```vue - * - * ``` - */ -declare const ConsentPlaceholder: vue.DefineComponent; - required: true; - }; - message: { - type: StringConstructor; - default: string; - }; - buttonText: { - type: StringConstructor; - default: string; - }; -}>, () => vue.VNode, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly; - required: true; - }; - message: { - type: StringConstructor; - default: string; - }; - buttonText: { - type: StringConstructor; - default: string; - }; -}>> & Readonly<{}>, { - message: string; - buttonText: string; -}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * ConsentBanner - Cookie-Banner Komponente - * - * @example - * ```vue - * - * - * - * ``` - */ -declare const ConsentBanner: vue.DefineComponent<{}, () => vue.VNode | vue.VNode[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>; -/** - * Vue Plugin fuer globale Installation - * - * @example - * ```ts - * import { createApp } from 'vue'; - * import { ConsentPlugin } from '@breakpilot/consent-sdk/vue'; - * - * const app = createApp(App); - * app.use(ConsentPlugin, { - * apiEndpoint: 'https://consent.example.com/api/v1', - * siteId: 'site_abc123', - * }); - * ``` - */ -declare const ConsentPlugin: { - install(app: { - provide: (key: symbol | string, value: unknown) => void; - }, config: ConsentConfig): void; -}; - -export { CONSENT_KEY, ConsentBanner, type ConsentContext, ConsentGate, ConsentPlaceholder, ConsentPlugin, ConsentProvider, provideConsent, useConsent }; diff --git a/docs-src/consent-sdk/dist/vue/index.js b/docs-src/consent-sdk/dist/vue/index.js deleted file mode 100644 index 1580040..0000000 --- a/docs-src/consent-sdk/dist/vue/index.js +++ /dev/null @@ -1,1423 +0,0 @@ -"use strict"; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - -// src/vue/index.ts -var index_exports = {}; -__export(index_exports, { - CONSENT_KEY: () => CONSENT_KEY, - ConsentBanner: () => ConsentBanner, - ConsentGate: () => ConsentGate, - ConsentPlaceholder: () => ConsentPlaceholder, - ConsentPlugin: () => ConsentPlugin, - ConsentProvider: () => ConsentProvider, - provideConsent: () => provideConsent, - useConsent: () => useConsent -}); -module.exports = __toCommonJS(index_exports); -var import_vue = require("vue"); - -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; - -// src/vue/index.ts -var CONSENT_KEY = /* @__PURE__ */ Symbol("consent"); -function useConsent() { - const context = (0, import_vue.inject)(CONSENT_KEY); - if (!context) { - throw new Error( - "useConsent() must be used within a component that has called provideConsent() or is wrapped in ConsentProvider" - ); - } - return context; -} -function provideConsent(config) { - const manager = (0, import_vue.ref)(null); - const consent = (0, import_vue.ref)(null); - const isInitialized = (0, import_vue.ref)(false); - const isLoading = (0, import_vue.ref)(true); - const isBannerVisible = (0, import_vue.ref)(false); - const needsConsent = (0, import_vue.computed)(() => { - return manager.value?.needsConsent() ?? true; - }); - (0, import_vue.onMounted)(async () => { - const consentManager = new ConsentManager(config); - manager.value = consentManager; - const unsubChange = consentManager.on("change", (newConsent) => { - consent.value = newConsent; - }); - const unsubBannerShow = consentManager.on("banner_show", () => { - isBannerVisible.value = true; - }); - const unsubBannerHide = consentManager.on("banner_hide", () => { - isBannerVisible.value = false; - }); - try { - await consentManager.init(); - consent.value = consentManager.getConsent(); - isInitialized.value = true; - isBannerVisible.value = consentManager.isBannerVisible(); - } catch (error) { - console.error("Failed to initialize ConsentManager:", error); - } finally { - isLoading.value = false; - } - (0, import_vue.onUnmounted)(() => { - unsubChange(); - unsubBannerShow(); - unsubBannerHide(); - }); - }); - const hasConsent = (category) => { - return manager.value?.hasConsent(category) ?? category === "essential"; - }; - const acceptAll = async () => { - await manager.value?.acceptAll(); - }; - const rejectAll = async () => { - await manager.value?.rejectAll(); - }; - const saveSelection = async (categories) => { - await manager.value?.setConsent(categories); - manager.value?.hideBanner(); - }; - const showBanner = () => { - manager.value?.showBanner(); - }; - const hideBanner = () => { - manager.value?.hideBanner(); - }; - const showSettings = () => { - manager.value?.showSettings(); - }; - const context = { - manager: (0, import_vue.readonly)(manager), - consent: (0, import_vue.readonly)(consent), - isInitialized: (0, import_vue.readonly)(isInitialized), - isLoading: (0, import_vue.readonly)(isLoading), - isBannerVisible: (0, import_vue.readonly)(isBannerVisible), - needsConsent, - hasConsent, - acceptAll, - rejectAll, - saveSelection, - showBanner, - hideBanner, - showSettings - }; - (0, import_vue.provide)(CONSENT_KEY, context); - return context; -} -var ConsentProvider = (0, import_vue.defineComponent)({ - name: "ConsentProvider", - props: { - config: { - type: Object, - required: true - } - }, - setup(props, { slots }) { - provideConsent(props.config); - return () => slots.default?.(); - } -}); -var ConsentGate = (0, import_vue.defineComponent)({ - name: "ConsentGate", - props: { - category: { - type: String, - required: true - } - }, - setup(props, { slots }) { - const { hasConsent, isLoading } = useConsent(); - return () => { - if (isLoading.value) { - return slots.fallback?.() ?? null; - } - if (!hasConsent(props.category)) { - return slots.placeholder?.() ?? null; - } - return slots.default?.(); - }; - } -}); -var ConsentPlaceholder = (0, import_vue.defineComponent)({ - name: "ConsentPlaceholder", - props: { - category: { - type: String, - required: true - }, - message: { - type: String, - default: "" - }, - buttonText: { - type: String, - default: "Cookie-Einstellungen \xF6ffnen" - } - }, - setup(props) { - const { showSettings } = useConsent(); - const categoryNames = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - const displayMessage = (0, import_vue.computed)(() => { - return props.message || `Dieser Inhalt erfordert ${categoryNames[props.category]}.`; - }); - return () => (0, import_vue.h)("div", { class: "bp-consent-placeholder" }, [ - (0, import_vue.h)("p", displayMessage.value), - (0, import_vue.h)( - "button", - { - type: "button", - onClick: showSettings - }, - props.buttonText - ) - ]); - } -}); -var ConsentBanner = (0, import_vue.defineComponent)({ - name: "ConsentBanner", - setup(_, { slots }) { - const { - consent, - isBannerVisible, - needsConsent, - acceptAll, - rejectAll, - saveSelection, - showSettings, - hideBanner - } = useConsent(); - const slotProps = (0, import_vue.computed)(() => ({ - isVisible: isBannerVisible.value, - consent: consent.value, - needsConsent: needsConsent.value, - onAcceptAll: acceptAll, - onRejectAll: rejectAll, - onSaveSelection: saveSelection, - onShowSettings: showSettings, - onClose: hideBanner - })); - return () => { - if (slots.default) { - return slots.default(slotProps.value); - } - if (!isBannerVisible.value) { - return null; - } - return (0, import_vue.h)( - "div", - { - class: "bp-consent-banner", - role: "dialog", - "aria-modal": "true", - "aria-label": "Cookie-Einstellungen" - }, - [ - (0, import_vue.h)("div", { class: "bp-consent-banner-content" }, [ - (0, import_vue.h)("h2", "Datenschutzeinstellungen"), - (0, import_vue.h)( - "p", - "Wir nutzen Cookies und \xE4hnliche Technologien, um Ihnen ein optimales Nutzererlebnis zu bieten." - ), - (0, import_vue.h)("div", { class: "bp-consent-banner-actions" }, [ - (0, import_vue.h)( - "button", - { - type: "button", - class: "bp-consent-btn bp-consent-btn-reject", - onClick: rejectAll - }, - "Alle ablehnen" - ), - (0, import_vue.h)( - "button", - { - type: "button", - class: "bp-consent-btn bp-consent-btn-settings", - onClick: showSettings - }, - "Einstellungen" - ), - (0, import_vue.h)( - "button", - { - type: "button", - class: "bp-consent-btn bp-consent-btn-accept", - onClick: acceptAll - }, - "Alle akzeptieren" - ) - ]) - ]) - ] - ); - }; - } -}); -var ConsentPlugin = { - install(app, config) { - const manager = new ConsentManager(config); - const consent = (0, import_vue.ref)(null); - const isInitialized = (0, import_vue.ref)(false); - const isLoading = (0, import_vue.ref)(true); - const isBannerVisible = (0, import_vue.ref)(false); - manager.init().then(() => { - consent.value = manager.getConsent(); - isInitialized.value = true; - isLoading.value = false; - isBannerVisible.value = manager.isBannerVisible(); - }); - manager.on("change", (newConsent) => { - consent.value = newConsent; - }); - manager.on("banner_show", () => { - isBannerVisible.value = true; - }); - manager.on("banner_hide", () => { - isBannerVisible.value = false; - }); - const context = { - manager: (0, import_vue.ref)(manager), - consent, - isInitialized, - isLoading, - isBannerVisible, - needsConsent: (0, import_vue.computed)(() => manager.needsConsent()), - hasConsent: (category) => manager.hasConsent(category), - acceptAll: () => manager.acceptAll(), - rejectAll: () => manager.rejectAll(), - saveSelection: async (categories) => { - await manager.setConsent(categories); - manager.hideBanner(); - }, - showBanner: () => manager.showBanner(), - hideBanner: () => manager.hideBanner(), - showSettings: () => manager.showSettings() - }; - app.provide(CONSENT_KEY, context); - } -}; -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - CONSENT_KEY, - ConsentBanner, - ConsentGate, - ConsentPlaceholder, - ConsentPlugin, - ConsentProvider, - provideConsent, - useConsent -}); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/vue/index.js.map b/docs-src/consent-sdk/dist/vue/index.js.map deleted file mode 100644 index 1d94593..0000000 --- a/docs-src/consent-sdk/dist/vue/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/vue/index.ts","../../src/core/ConsentStorage.ts","../../src/core/ScriptBlocker.ts","../../src/core/ConsentAPI.ts","../../src/utils/EventEmitter.ts","../../src/utils/fingerprint.ts","../../src/version.ts","../../src/core/ConsentManager.ts"],"sourcesContent":["/**\n * Vue 3 Integration fuer @breakpilot/consent-sdk\n *\n * @example\n * ```vue\n * \n *\n * \n * ```\n */\n\nimport {\n ref,\n computed,\n readonly,\n inject,\n provide,\n onMounted,\n onUnmounted,\n defineComponent,\n h,\n type Ref,\n type InjectionKey,\n type PropType,\n} from 'vue';\nimport { ConsentManager } from '../core/ConsentManager';\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n} from '../types';\n\n// =============================================================================\n// Injection Key\n// =============================================================================\n\nconst CONSENT_KEY: InjectionKey = Symbol('consent');\n\n// =============================================================================\n// Types\n// =============================================================================\n\ninterface ConsentContext {\n manager: Ref;\n consent: Ref;\n isInitialized: Ref;\n isLoading: Ref;\n isBannerVisible: Ref;\n needsConsent: Ref;\n hasConsent: (category: ConsentCategory) => boolean;\n acceptAll: () => Promise;\n rejectAll: () => Promise;\n saveSelection: (categories: Partial) => Promise;\n showBanner: () => void;\n hideBanner: () => void;\n showSettings: () => void;\n}\n\n// =============================================================================\n// Composable: useConsent\n// =============================================================================\n\n/**\n * Haupt-Composable fuer Consent-Zugriff\n *\n * @example\n * ```vue\n * \n * ```\n */\nexport function useConsent(): ConsentContext {\n const context = inject(CONSENT_KEY);\n\n if (!context) {\n throw new Error(\n 'useConsent() must be used within a component that has called provideConsent() or is wrapped in ConsentProvider'\n );\n }\n\n return context;\n}\n\n/**\n * Consent-Provider einrichten (in App.vue aufrufen)\n *\n * @example\n * ```vue\n * \n * ```\n */\nexport function provideConsent(config: ConsentConfig): ConsentContext {\n const manager = ref(null);\n const consent = ref(null);\n const isInitialized = ref(false);\n const isLoading = ref(true);\n const isBannerVisible = ref(false);\n\n const needsConsent = computed(() => {\n return manager.value?.needsConsent() ?? true;\n });\n\n // Initialisierung\n onMounted(async () => {\n const consentManager = new ConsentManager(config);\n manager.value = consentManager;\n\n // Events abonnieren\n const unsubChange = consentManager.on('change', (newConsent) => {\n consent.value = newConsent;\n });\n\n const unsubBannerShow = consentManager.on('banner_show', () => {\n isBannerVisible.value = true;\n });\n\n const unsubBannerHide = consentManager.on('banner_hide', () => {\n isBannerVisible.value = false;\n });\n\n try {\n await consentManager.init();\n consent.value = consentManager.getConsent();\n isInitialized.value = true;\n isBannerVisible.value = consentManager.isBannerVisible();\n } catch (error) {\n console.error('Failed to initialize ConsentManager:', error);\n } finally {\n isLoading.value = false;\n }\n\n // Cleanup bei Unmount\n onUnmounted(() => {\n unsubChange();\n unsubBannerShow();\n unsubBannerHide();\n });\n });\n\n // Methoden\n const hasConsent = (category: ConsentCategory): boolean => {\n return manager.value?.hasConsent(category) ?? category === 'essential';\n };\n\n const acceptAll = async (): Promise => {\n await manager.value?.acceptAll();\n };\n\n const rejectAll = async (): Promise => {\n await manager.value?.rejectAll();\n };\n\n const saveSelection = async (categories: Partial): Promise => {\n await manager.value?.setConsent(categories);\n manager.value?.hideBanner();\n };\n\n const showBanner = (): void => {\n manager.value?.showBanner();\n };\n\n const hideBanner = (): void => {\n manager.value?.hideBanner();\n };\n\n const showSettings = (): void => {\n manager.value?.showSettings();\n };\n\n const context: ConsentContext = {\n manager: readonly(manager) as Ref,\n consent: readonly(consent) as Ref,\n isInitialized: readonly(isInitialized),\n isLoading: readonly(isLoading),\n isBannerVisible: readonly(isBannerVisible),\n needsConsent,\n hasConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showBanner,\n hideBanner,\n showSettings,\n };\n\n provide(CONSENT_KEY, context);\n\n return context;\n}\n\n// =============================================================================\n// Components\n// =============================================================================\n\n/**\n * ConsentProvider - Wrapper-Komponente\n *\n * @example\n * ```vue\n * \n * \n * \n * ```\n */\nexport const ConsentProvider = defineComponent({\n name: 'ConsentProvider',\n props: {\n config: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props, { slots }) {\n provideConsent(props.config);\n return () => slots.default?.();\n },\n});\n\n/**\n * ConsentGate - Zeigt Inhalt nur bei Consent\n *\n * @example\n * ```vue\n * \n * \n * \n * \n * ```\n */\nexport const ConsentGate = defineComponent({\n name: 'ConsentGate',\n props: {\n category: {\n type: String as PropType,\n required: true,\n },\n },\n setup(props, { slots }) {\n const { hasConsent, isLoading } = useConsent();\n\n return () => {\n if (isLoading.value) {\n return slots.fallback?.() ?? null;\n }\n\n if (!hasConsent(props.category)) {\n return slots.placeholder?.() ?? null;\n }\n\n return slots.default?.();\n };\n },\n});\n\n/**\n * ConsentPlaceholder - Placeholder fuer blockierten Inhalt\n *\n * @example\n * ```vue\n * \n * ```\n */\nexport const ConsentPlaceholder = defineComponent({\n name: 'ConsentPlaceholder',\n props: {\n category: {\n type: String as PropType,\n required: true,\n },\n message: {\n type: String,\n default: '',\n },\n buttonText: {\n type: String,\n default: 'Cookie-Einstellungen öffnen',\n },\n },\n setup(props) {\n const { showSettings } = useConsent();\n\n const categoryNames: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n\n const displayMessage = computed(() => {\n return props.message || `Dieser Inhalt erfordert ${categoryNames[props.category]}.`;\n });\n\n return () =>\n h('div', { class: 'bp-consent-placeholder' }, [\n h('p', displayMessage.value),\n h(\n 'button',\n {\n type: 'button',\n onClick: showSettings,\n },\n props.buttonText\n ),\n ]);\n },\n});\n\n/**\n * ConsentBanner - Cookie-Banner Komponente\n *\n * @example\n * ```vue\n * \n * \n * \n * ```\n */\nexport const ConsentBanner = defineComponent({\n name: 'ConsentBanner',\n setup(_, { slots }) {\n const {\n consent,\n isBannerVisible,\n needsConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showSettings,\n hideBanner,\n } = useConsent();\n\n const slotProps = computed(() => ({\n isVisible: isBannerVisible.value,\n consent: consent.value,\n needsConsent: needsConsent.value,\n onAcceptAll: acceptAll,\n onRejectAll: rejectAll,\n onSaveSelection: saveSelection,\n onShowSettings: showSettings,\n onClose: hideBanner,\n }));\n\n return () => {\n // Custom Slot\n if (slots.default) {\n return slots.default(slotProps.value);\n }\n\n // Default UI\n if (!isBannerVisible.value) {\n return null;\n }\n\n return h(\n 'div',\n {\n class: 'bp-consent-banner',\n role: 'dialog',\n 'aria-modal': 'true',\n 'aria-label': 'Cookie-Einstellungen',\n },\n [\n h('div', { class: 'bp-consent-banner-content' }, [\n h('h2', 'Datenschutzeinstellungen'),\n h(\n 'p',\n 'Wir nutzen Cookies und ähnliche Technologien, um Ihnen ein optimales Nutzererlebnis zu bieten.'\n ),\n h('div', { class: 'bp-consent-banner-actions' }, [\n h(\n 'button',\n {\n type: 'button',\n class: 'bp-consent-btn bp-consent-btn-reject',\n onClick: rejectAll,\n },\n 'Alle ablehnen'\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'bp-consent-btn bp-consent-btn-settings',\n onClick: showSettings,\n },\n 'Einstellungen'\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'bp-consent-btn bp-consent-btn-accept',\n onClick: acceptAll,\n },\n 'Alle akzeptieren'\n ),\n ]),\n ]),\n ]\n );\n };\n },\n});\n\n// =============================================================================\n// Plugin\n// =============================================================================\n\n/**\n * Vue Plugin fuer globale Installation\n *\n * @example\n * ```ts\n * import { createApp } from 'vue';\n * import { ConsentPlugin } from '@breakpilot/consent-sdk/vue';\n *\n * const app = createApp(App);\n * app.use(ConsentPlugin, {\n * apiEndpoint: 'https://consent.example.com/api/v1',\n * siteId: 'site_abc123',\n * });\n * ```\n */\nexport const ConsentPlugin = {\n install(app: { provide: (key: symbol | string, value: unknown) => void }, config: ConsentConfig) {\n const manager = new ConsentManager(config);\n const consent = ref(null);\n const isInitialized = ref(false);\n const isLoading = ref(true);\n const isBannerVisible = ref(false);\n\n // Initialisieren\n manager.init().then(() => {\n consent.value = manager.getConsent();\n isInitialized.value = true;\n isLoading.value = false;\n isBannerVisible.value = manager.isBannerVisible();\n });\n\n // Events\n manager.on('change', (newConsent) => {\n consent.value = newConsent;\n });\n manager.on('banner_show', () => {\n isBannerVisible.value = true;\n });\n manager.on('banner_hide', () => {\n isBannerVisible.value = false;\n });\n\n const context: ConsentContext = {\n manager: ref(manager) as Ref,\n consent: consent as Ref,\n isInitialized,\n isLoading,\n isBannerVisible,\n needsConsent: computed(() => manager.needsConsent()),\n hasConsent: (category: ConsentCategory) => manager.hasConsent(category),\n acceptAll: () => manager.acceptAll(),\n rejectAll: () => manager.rejectAll(),\n saveSelection: async (categories: Partial) => {\n await manager.setConsent(categories);\n manager.hideBanner();\n },\n showBanner: () => manager.showBanner(),\n hideBanner: () => manager.hideBanner(),\n showSettings: () => manager.showSettings(),\n };\n\n app.provide(CONSENT_KEY, context);\n },\n};\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport { CONSENT_KEY };\nexport type { ConsentContext };\n","/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/vue/index.mjs b/docs-src/consent-sdk/dist/vue/index.mjs deleted file mode 100644 index 28ef901..0000000 --- a/docs-src/consent-sdk/dist/vue/index.mjs +++ /dev/null @@ -1,1401 +0,0 @@ -// src/vue/index.ts -import { - ref, - computed, - readonly, - inject, - provide, - onMounted, - onUnmounted, - defineComponent, - h -} from "vue"; - -// src/core/ConsentStorage.ts -var STORAGE_KEY = "bp_consent"; -var STORAGE_VERSION = "1"; -var ConsentStorage = class { - constructor(config) { - this.config = config; - this.storageKey = `${STORAGE_KEY}_${config.siteId}`; - } - /** - * Consent laden - */ - get() { - if (typeof window === "undefined") { - return null; - } - try { - const raw = localStorage.getItem(this.storageKey); - if (!raw) { - return null; - } - const stored = JSON.parse(raw); - if (stored.version !== STORAGE_VERSION) { - this.log("Storage version mismatch, clearing"); - this.clear(); - return null; - } - if (!this.verifySignature(stored.consent, stored.signature)) { - this.log("Invalid signature, clearing"); - this.clear(); - return null; - } - return stored.consent; - } catch (error) { - this.log("Failed to load consent:", error); - return null; - } - } - /** - * Consent speichern - */ - set(consent) { - if (typeof window === "undefined") { - return; - } - try { - const signature = this.generateSignature(consent); - const stored = { - version: STORAGE_VERSION, - consent, - signature - }; - localStorage.setItem(this.storageKey, JSON.stringify(stored)); - this.setCookie(consent); - this.log("Consent saved to storage"); - } catch (error) { - this.log("Failed to save consent:", error); - } - } - /** - * Consent loeschen - */ - clear() { - if (typeof window === "undefined") { - return; - } - try { - localStorage.removeItem(this.storageKey); - this.clearCookie(); - this.log("Consent cleared from storage"); - } catch (error) { - this.log("Failed to clear consent:", error); - } - } - /** - * Pruefen ob Consent existiert - */ - exists() { - return this.get() !== null; - } - // =========================================================================== - // Cookie Management - // =========================================================================== - /** - * Consent als Cookie setzen - */ - setCookie(consent) { - const days = this.config.consent?.rememberDays ?? 365; - const expires = /* @__PURE__ */ new Date(); - expires.setDate(expires.getDate() + days); - const cookieValue = JSON.stringify(consent.categories); - const encoded = encodeURIComponent(cookieValue); - document.cookie = [ - `${this.storageKey}=${encoded}`, - `expires=${expires.toUTCString()}`, - "path=/", - "SameSite=Lax", - location.protocol === "https:" ? "Secure" : "" - ].filter(Boolean).join("; "); - } - /** - * Cookie loeschen - */ - clearCookie() { - document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; - } - // =========================================================================== - // Signature (Simple HMAC-like) - // =========================================================================== - /** - * Signatur generieren - */ - generateSignature(consent) { - const data = JSON.stringify(consent); - const key = this.config.siteId; - return this.simpleHash(data + key); - } - /** - * Signatur verifizieren - */ - verifySignature(consent, signature) { - const expected = this.generateSignature(consent); - return expected === signature; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentStorage]", ...args); - } - } -}; - -// src/core/ScriptBlocker.ts -var ScriptBlocker = class { - constructor(config) { - this.observer = null; - this.enabledCategories = /* @__PURE__ */ new Set(["essential"]); - this.processedElements = /* @__PURE__ */ new WeakSet(); - this.config = config; - } - /** - * Initialisieren und Observer starten - */ - init() { - if (typeof window === "undefined") { - return; - } - this.processExistingElements(); - this.observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.processElement(node); - } - } - } - }); - this.observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - this.log("ScriptBlocker initialized"); - } - /** - * Kategorie aktivieren - */ - enableCategory(category) { - if (this.enabledCategories.has(category)) { - return; - } - this.enabledCategories.add(category); - this.log("Category enabled:", category); - this.activateCategory(category); - } - /** - * Kategorie deaktivieren - */ - disableCategory(category) { - if (category === "essential") { - return; - } - this.enabledCategories.delete(category); - this.log("Category disabled:", category); - } - /** - * Alle Kategorien blockieren (ausser Essential) - */ - blockAll() { - this.enabledCategories.clear(); - this.enabledCategories.add("essential"); - this.log("All categories blocked"); - } - /** - * Pruefen ob Kategorie aktiviert - */ - isCategoryEnabled(category) { - return this.enabledCategories.has(category); - } - /** - * Observer stoppen - */ - destroy() { - this.observer?.disconnect(); - this.observer = null; - this.log("ScriptBlocker destroyed"); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Bestehende Elemente verarbeiten - */ - processExistingElements() { - const scripts = document.querySelectorAll( - "script[data-consent]" - ); - scripts.forEach((script) => this.processScript(script)); - const iframes = document.querySelectorAll( - "iframe[data-consent]" - ); - iframes.forEach((iframe) => this.processIframe(iframe)); - this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); - } - /** - * Element verarbeiten - */ - processElement(element) { - if (element.tagName === "SCRIPT") { - this.processScript(element); - } else if (element.tagName === "IFRAME") { - this.processIframe(element); - } - element.querySelectorAll("script[data-consent]").forEach((script) => this.processScript(script)); - element.querySelectorAll("iframe[data-consent]").forEach((iframe) => this.processIframe(iframe)); - } - /** - * Script-Element verarbeiten - */ - processScript(script) { - if (this.processedElements.has(script)) { - return; - } - const category = script.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(script); - if (this.enabledCategories.has(category)) { - this.activateScript(script); - } else { - this.log(`Script blocked (${category}):`, script.dataset.src || "inline"); - } - } - /** - * iFrame-Element verarbeiten - */ - processIframe(iframe) { - if (this.processedElements.has(iframe)) { - return; - } - const category = iframe.dataset.consent; - if (!category) { - return; - } - this.processedElements.add(iframe); - if (this.enabledCategories.has(category)) { - this.activateIframe(iframe); - } else { - this.log(`iFrame blocked (${category}):`, iframe.dataset.src); - this.showPlaceholder(iframe, category); - } - } - /** - * Script aktivieren - */ - activateScript(script) { - const src = script.dataset.src; - if (src) { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type" && attr.name !== "data-src") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.src = src; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("External script activated:", src); - } else { - const newScript = document.createElement("script"); - for (const attr of script.attributes) { - if (attr.name !== "type") { - newScript.setAttribute(attr.name, attr.value); - } - } - newScript.textContent = script.textContent; - newScript.removeAttribute("data-consent"); - script.parentNode?.replaceChild(newScript, script); - this.log("Inline script activated"); - } - } - /** - * iFrame aktivieren - */ - activateIframe(iframe) { - const src = iframe.dataset.src; - if (!src) { - return; - } - const placeholder = iframe.parentElement?.querySelector( - ".bp-consent-placeholder" - ); - placeholder?.remove(); - iframe.src = src; - iframe.removeAttribute("data-src"); - iframe.removeAttribute("data-consent"); - iframe.style.display = ""; - this.log("iFrame activated:", src); - } - /** - * Placeholder fuer blockierten iFrame anzeigen - */ - showPlaceholder(iframe, category) { - iframe.style.display = "none"; - const placeholder = document.createElement("div"); - placeholder.className = "bp-consent-placeholder"; - placeholder.setAttribute("data-category", category); - placeholder.innerHTML = ` - - `; - const btn = placeholder.querySelector("button"); - btn?.addEventListener("click", () => { - window.dispatchEvent( - new CustomEvent("bp-consent-request", { - detail: { category } - }) - ); - }); - iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); - } - /** - * Alle Elemente einer Kategorie aktivieren - */ - activateCategory(category) { - const scripts = document.querySelectorAll( - `script[data-consent="${category}"]` - ); - scripts.forEach((script) => this.activateScript(script)); - const iframes = document.querySelectorAll( - `iframe[data-consent="${category}"]` - ); - iframes.forEach((iframe) => this.activateIframe(iframe)); - this.log( - `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}` - ); - } - /** - * Kategorie-Name fuer UI - */ - getCategoryName(category) { - const names = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - return names[category] ?? category; - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ScriptBlocker]", ...args); - } - } -}; - -// src/core/ConsentAPI.ts -var ConsentAPI = class { - constructor(config) { - this.config = config; - this.baseUrl = config.apiEndpoint.replace(/\/$/, ""); - } - /** - * Consent speichern - */ - async saveConsent(request) { - const payload = { - ...request, - metadata: { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "", - language: typeof navigator !== "undefined" ? navigator.language : "", - screenResolution: typeof window !== "undefined" ? `${window.screen.width}x${window.screen.height}` : "", - platform: "web", - ...request.metadata - } - }; - const response = await this.fetch("/consent", { - method: "POST", - body: JSON.stringify(payload) - }); - if (!response.ok) { - throw new Error(`Failed to save consent: ${response.status}`); - } - return response.json(); - } - /** - * Consent abrufen - */ - async getConsent(siteId, deviceFingerprint) { - const params = new URLSearchParams({ - siteId, - deviceFingerprint - }); - const response = await this.fetch(`/consent?${params}`); - if (response.status === 404) { - return null; - } - if (!response.ok) { - throw new Error(`Failed to get consent: ${response.status}`); - } - const data = await response.json(); - return data.consent; - } - /** - * Consent widerrufen - */ - async revokeConsent(consentId) { - const response = await this.fetch(`/consent/${consentId}`, { - method: "DELETE" - }); - if (!response.ok) { - throw new Error(`Failed to revoke consent: ${response.status}`); - } - } - /** - * Site-Konfiguration abrufen - */ - async getSiteConfig(siteId) { - const response = await this.fetch(`/config/${siteId}`); - if (!response.ok) { - throw new Error(`Failed to get site config: ${response.status}`); - } - return response.json(); - } - /** - * Consent-Historie exportieren (DSGVO Art. 20) - */ - async exportConsent(userId) { - const params = new URLSearchParams({ userId }); - const response = await this.fetch(`/consent/export?${params}`); - if (!response.ok) { - throw new Error(`Failed to export consent: ${response.status}`); - } - return response.json(); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Fetch mit Standard-Headers - */ - async fetch(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const headers = { - "Content-Type": "application/json", - Accept: "application/json", - ...this.getSignatureHeaders(), - ...options.headers || {} - }; - try { - const response = await fetch(url, { - ...options, - headers, - credentials: "include" - }); - this.log(`${options.method || "GET"} ${path}:`, response.status); - return response; - } catch (error) { - this.log("Fetch error:", error); - throw error; - } - } - /** - * Signatur-Headers generieren (HMAC) - */ - getSignatureHeaders() { - const timestamp = Math.floor(Date.now() / 1e3).toString(); - const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); - return { - "X-Consent-Timestamp": timestamp, - "X-Consent-Signature": `sha256=${signature}` - }; - } - /** - * Einfache Hash-Funktion (djb2) - */ - simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentAPI]", ...args); - } - } -}; - -// src/utils/EventEmitter.ts -var EventEmitter = class { - constructor() { - this.listeners = /* @__PURE__ */ new Map(); - } - /** - * Event-Listener registrieren - * @returns Unsubscribe-Funktion - */ - on(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, /* @__PURE__ */ new Set()); - } - this.listeners.get(event).add(callback); - return () => this.off(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.listeners.get(event)?.delete(callback); - } - /** - * Event emittieren - */ - emit(event, data) { - this.listeners.get(event)?.forEach((callback) => { - try { - callback(data); - } catch (error) { - console.error(`Error in event handler for ${String(event)}:`, error); - } - }); - } - /** - * Einmaligen Listener registrieren - */ - once(event, callback) { - const wrapper = (data) => { - this.off(event, wrapper); - callback(data); - }; - return this.on(event, wrapper); - } - /** - * Alle Listener entfernen - */ - clear() { - this.listeners.clear(); - } - /** - * Alle Listener fuer ein Event entfernen - */ - clearEvent(event) { - this.listeners.delete(event); - } - /** - * Anzahl Listener fuer ein Event - */ - listenerCount(event) { - return this.listeners.get(event)?.size ?? 0; - } -}; - -// src/utils/fingerprint.ts -function getComponents() { - if (typeof window === "undefined") { - return ["server"]; - } - const components = []; - try { - const ua = navigator.userAgent; - if (ua.includes("Chrome")) components.push("chrome"); - else if (ua.includes("Firefox")) components.push("firefox"); - else if (ua.includes("Safari")) components.push("safari"); - else if (ua.includes("Edge")) components.push("edge"); - else components.push("other"); - } catch { - components.push("unknown-browser"); - } - try { - components.push(navigator.language || "unknown-lang"); - } catch { - components.push("unknown-lang"); - } - try { - const width = window.screen.width; - if (width >= 2560) components.push("4k"); - else if (width >= 1920) components.push("fhd"); - else if (width >= 1366) components.push("hd"); - else if (width >= 768) components.push("tablet"); - else components.push("mobile"); - } catch { - components.push("unknown-screen"); - } - try { - const depth = window.screen.colorDepth; - if (depth >= 24) components.push("deep-color"); - else components.push("standard-color"); - } catch { - components.push("unknown-color"); - } - try { - const offset = (/* @__PURE__ */ new Date()).getTimezoneOffset(); - const hours = Math.floor(Math.abs(offset) / 60); - const sign = offset <= 0 ? "+" : "-"; - components.push(`tz${sign}${hours}`); - } catch { - components.push("unknown-tz"); - } - try { - const platform = navigator.platform?.toLowerCase() || ""; - if (platform.includes("mac")) components.push("mac"); - else if (platform.includes("win")) components.push("win"); - else if (platform.includes("linux")) components.push("linux"); - else if (platform.includes("iphone") || platform.includes("ipad")) - components.push("ios"); - else if (platform.includes("android")) components.push("android"); - else components.push("other-platform"); - } catch { - components.push("unknown-platform"); - } - try { - if ("ontouchstart" in window || navigator.maxTouchPoints > 0) { - components.push("touch"); - } else { - components.push("no-touch"); - } - } catch { - components.push("unknown-touch"); - } - try { - if (navigator.doNotTrack === "1") { - components.push("dnt"); - } - } catch { - } - return components; -} -async function sha256(message) { - if (typeof window === "undefined" || !window.crypto?.subtle) { - return simpleHash(message); - } - try { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } catch { - return simpleHash(message); - } -} -function simpleHash(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = hash * 33 ^ str.charCodeAt(i); - } - return (hash >>> 0).toString(16).padStart(8, "0"); -} -async function generateFingerprint() { - const components = getComponents(); - const combined = components.join("|"); - const hash = await sha256(combined); - return `fp_${hash.substring(0, 32)}`; -} - -// src/version.ts -var SDK_VERSION = "1.0.0"; - -// src/core/ConsentManager.ts -var DEFAULT_CONFIG = { - language: "de", - fallbackLanguage: "en", - ui: { - position: "bottom", - layout: "modal", - theme: "auto", - zIndex: 999999, - blockScrollOnModal: true - }, - consent: { - required: true, - rejectAllVisible: true, - acceptAllVisible: true, - granularControl: true, - vendorControl: false, - rememberChoice: true, - rememberDays: 365, - geoTargeting: false, - recheckAfterDays: 180 - }, - categories: ["essential", "functional", "analytics", "marketing", "social"], - debug: false -}; -var DEFAULT_CONSENT = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false -}; -var ConsentManager = class { - constructor(config) { - this.currentConsent = null; - this.initialized = false; - this.bannerVisible = false; - this.deviceFingerprint = ""; - this.config = this.mergeConfig(config); - this.storage = new ConsentStorage(this.config); - this.scriptBlocker = new ScriptBlocker(this.config); - this.api = new ConsentAPI(this.config); - this.events = new EventEmitter(); - this.log("ConsentManager created with config:", this.config); - } - /** - * SDK initialisieren - */ - async init() { - if (this.initialized) { - this.log("Already initialized, skipping"); - return; - } - try { - this.log("Initializing ConsentManager..."); - this.deviceFingerprint = await generateFingerprint(); - this.currentConsent = this.storage.get(); - if (this.currentConsent) { - this.log("Loaded consent from storage:", this.currentConsent); - if (this.isConsentExpired()) { - this.log("Consent expired, clearing"); - this.storage.clear(); - this.currentConsent = null; - } else { - this.applyConsent(); - } - } - this.scriptBlocker.init(); - this.initialized = true; - this.emit("init", this.currentConsent); - if (this.needsConsent()) { - this.showBanner(); - } - this.log("ConsentManager initialized successfully"); - } catch (error) { - this.handleError(error); - throw error; - } - } - // =========================================================================== - // Public API - // =========================================================================== - /** - * Pruefen ob Consent fuer Kategorie vorhanden - */ - hasConsent(category) { - if (!this.currentConsent) { - return category === "essential"; - } - return this.currentConsent.categories[category] ?? false; - } - /** - * Pruefen ob Consent fuer Vendor vorhanden - */ - hasVendorConsent(vendorId) { - if (!this.currentConsent) { - return false; - } - return this.currentConsent.vendors[vendorId] ?? false; - } - /** - * Aktuellen Consent-State abrufen - */ - getConsent() { - return this.currentConsent ? { ...this.currentConsent } : null; - } - /** - * Consent setzen - */ - async setConsent(input) { - const categories = this.normalizeConsentInput(input); - categories.essential = true; - const newConsent = { - categories, - vendors: "vendors" in input && input.vendors ? input.vendors : {}, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - version: SDK_VERSION - }; - try { - const response = await this.api.saveConsent({ - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint, - consent: newConsent - }); - newConsent.consentId = response.consentId; - newConsent.expiresAt = response.expiresAt; - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - this.config.onConsentChange?.(newConsent); - this.log("Consent saved:", newConsent); - } catch (error) { - this.log("API error, saving locally:", error); - this.storage.set(newConsent); - this.currentConsent = newConsent; - this.applyConsent(); - this.emit("change", newConsent); - } - } - /** - * Alle Kategorien akzeptieren - */ - async acceptAll() { - const allCategories = { - essential: true, - functional: true, - analytics: true, - marketing: true, - social: true - }; - await this.setConsent(allCategories); - this.emit("accept_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle nicht-essentiellen Kategorien ablehnen - */ - async rejectAll() { - const minimalCategories = { - essential: true, - functional: false, - analytics: false, - marketing: false, - social: false - }; - await this.setConsent(minimalCategories); - this.emit("reject_all", this.currentConsent); - this.hideBanner(); - } - /** - * Alle Einwilligungen widerrufen - */ - async revokeAll() { - if (this.currentConsent?.consentId) { - try { - await this.api.revokeConsent(this.currentConsent.consentId); - } catch (error) { - this.log("Failed to revoke on server:", error); - } - } - this.storage.clear(); - this.currentConsent = null; - this.scriptBlocker.blockAll(); - this.log("All consents revoked"); - } - /** - * Consent-Daten exportieren (DSGVO Art. 20) - */ - async exportConsent() { - const exportData = { - currentConsent: this.currentConsent, - exportedAt: (/* @__PURE__ */ new Date()).toISOString(), - siteId: this.config.siteId, - deviceFingerprint: this.deviceFingerprint - }; - return JSON.stringify(exportData, null, 2); - } - // =========================================================================== - // Banner Control - // =========================================================================== - /** - * Pruefen ob Consent-Abfrage noetig - */ - needsConsent() { - if (!this.currentConsent) { - return true; - } - if (this.isConsentExpired()) { - return true; - } - if (this.config.consent?.recheckAfterDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const recheckDate = new Date(consentDate); - recheckDate.setDate( - recheckDate.getDate() + this.config.consent.recheckAfterDays - ); - if (/* @__PURE__ */ new Date() > recheckDate) { - return true; - } - } - return false; - } - /** - * Banner anzeigen - */ - showBanner() { - if (this.bannerVisible) { - return; - } - this.bannerVisible = true; - this.emit("banner_show", void 0); - this.config.onBannerShow?.(); - this.log("Banner shown"); - } - /** - * Banner verstecken - */ - hideBanner() { - if (!this.bannerVisible) { - return; - } - this.bannerVisible = false; - this.emit("banner_hide", void 0); - this.config.onBannerHide?.(); - this.log("Banner hidden"); - } - /** - * Einstellungs-Modal oeffnen - */ - showSettings() { - this.emit("settings_open", void 0); - this.log("Settings opened"); - } - /** - * Pruefen ob Banner sichtbar - */ - isBannerVisible() { - return this.bannerVisible; - } - // =========================================================================== - // Event Handling - // =========================================================================== - /** - * Event-Listener registrieren - */ - on(event, callback) { - return this.events.on(event, callback); - } - /** - * Event-Listener entfernen - */ - off(event, callback) { - this.events.off(event, callback); - } - // =========================================================================== - // Internal Methods - // =========================================================================== - /** - * Konfiguration zusammenfuehren - */ - mergeConfig(config) { - return { - ...DEFAULT_CONFIG, - ...config, - ui: { ...DEFAULT_CONFIG.ui, ...config.ui }, - consent: { ...DEFAULT_CONFIG.consent, ...config.consent } - }; - } - /** - * Consent-Input normalisieren - */ - normalizeConsentInput(input) { - if ("categories" in input && input.categories) { - return { ...DEFAULT_CONSENT, ...input.categories }; - } - return { ...DEFAULT_CONSENT, ...input }; - } - /** - * Consent anwenden (Skripte aktivieren/blockieren) - */ - applyConsent() { - if (!this.currentConsent) { - return; - } - for (const [category, allowed] of Object.entries( - this.currentConsent.categories - )) { - if (allowed) { - this.scriptBlocker.enableCategory(category); - } else { - this.scriptBlocker.disableCategory(category); - } - } - this.updateGoogleConsentMode(); - } - /** - * Google Consent Mode v2 aktualisieren - */ - updateGoogleConsentMode() { - if (typeof window === "undefined" || !this.currentConsent) { - return; - } - const gtag = window.gtag; - if (typeof gtag !== "function") { - return; - } - const { categories } = this.currentConsent; - gtag("consent", "update", { - ad_storage: categories.marketing ? "granted" : "denied", - ad_user_data: categories.marketing ? "granted" : "denied", - ad_personalization: categories.marketing ? "granted" : "denied", - analytics_storage: categories.analytics ? "granted" : "denied", - functionality_storage: categories.functional ? "granted" : "denied", - personalization_storage: categories.functional ? "granted" : "denied", - security_storage: "granted" - }); - this.log("Google Consent Mode updated"); - } - /** - * Pruefen ob Consent abgelaufen - */ - isConsentExpired() { - if (!this.currentConsent?.expiresAt) { - if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) { - const consentDate = new Date(this.currentConsent.timestamp); - const expiryDate = new Date(consentDate); - expiryDate.setDate( - expiryDate.getDate() + this.config.consent.rememberDays - ); - return /* @__PURE__ */ new Date() > expiryDate; - } - return false; - } - return /* @__PURE__ */ new Date() > new Date(this.currentConsent.expiresAt); - } - /** - * Event emittieren - */ - emit(event, data) { - this.events.emit(event, data); - } - /** - * Fehler behandeln - */ - handleError(error) { - this.log("Error:", error); - this.emit("error", error); - this.config.onError?.(error); - } - /** - * Debug-Logging - */ - log(...args) { - if (this.config.debug) { - console.log("[ConsentSDK]", ...args); - } - } - // =========================================================================== - // Static Methods - // =========================================================================== - /** - * SDK-Version abrufen - */ - static getVersion() { - return SDK_VERSION; - } -}; - -// src/vue/index.ts -var CONSENT_KEY = /* @__PURE__ */ Symbol("consent"); -function useConsent() { - const context = inject(CONSENT_KEY); - if (!context) { - throw new Error( - "useConsent() must be used within a component that has called provideConsent() or is wrapped in ConsentProvider" - ); - } - return context; -} -function provideConsent(config) { - const manager = ref(null); - const consent = ref(null); - const isInitialized = ref(false); - const isLoading = ref(true); - const isBannerVisible = ref(false); - const needsConsent = computed(() => { - return manager.value?.needsConsent() ?? true; - }); - onMounted(async () => { - const consentManager = new ConsentManager(config); - manager.value = consentManager; - const unsubChange = consentManager.on("change", (newConsent) => { - consent.value = newConsent; - }); - const unsubBannerShow = consentManager.on("banner_show", () => { - isBannerVisible.value = true; - }); - const unsubBannerHide = consentManager.on("banner_hide", () => { - isBannerVisible.value = false; - }); - try { - await consentManager.init(); - consent.value = consentManager.getConsent(); - isInitialized.value = true; - isBannerVisible.value = consentManager.isBannerVisible(); - } catch (error) { - console.error("Failed to initialize ConsentManager:", error); - } finally { - isLoading.value = false; - } - onUnmounted(() => { - unsubChange(); - unsubBannerShow(); - unsubBannerHide(); - }); - }); - const hasConsent = (category) => { - return manager.value?.hasConsent(category) ?? category === "essential"; - }; - const acceptAll = async () => { - await manager.value?.acceptAll(); - }; - const rejectAll = async () => { - await manager.value?.rejectAll(); - }; - const saveSelection = async (categories) => { - await manager.value?.setConsent(categories); - manager.value?.hideBanner(); - }; - const showBanner = () => { - manager.value?.showBanner(); - }; - const hideBanner = () => { - manager.value?.hideBanner(); - }; - const showSettings = () => { - manager.value?.showSettings(); - }; - const context = { - manager: readonly(manager), - consent: readonly(consent), - isInitialized: readonly(isInitialized), - isLoading: readonly(isLoading), - isBannerVisible: readonly(isBannerVisible), - needsConsent, - hasConsent, - acceptAll, - rejectAll, - saveSelection, - showBanner, - hideBanner, - showSettings - }; - provide(CONSENT_KEY, context); - return context; -} -var ConsentProvider = defineComponent({ - name: "ConsentProvider", - props: { - config: { - type: Object, - required: true - } - }, - setup(props, { slots }) { - provideConsent(props.config); - return () => slots.default?.(); - } -}); -var ConsentGate = defineComponent({ - name: "ConsentGate", - props: { - category: { - type: String, - required: true - } - }, - setup(props, { slots }) { - const { hasConsent, isLoading } = useConsent(); - return () => { - if (isLoading.value) { - return slots.fallback?.() ?? null; - } - if (!hasConsent(props.category)) { - return slots.placeholder?.() ?? null; - } - return slots.default?.(); - }; - } -}); -var ConsentPlaceholder = defineComponent({ - name: "ConsentPlaceholder", - props: { - category: { - type: String, - required: true - }, - message: { - type: String, - default: "" - }, - buttonText: { - type: String, - default: "Cookie-Einstellungen \xF6ffnen" - } - }, - setup(props) { - const { showSettings } = useConsent(); - const categoryNames = { - essential: "Essentielle Cookies", - functional: "Funktionale Cookies", - analytics: "Statistik-Cookies", - marketing: "Marketing-Cookies", - social: "Social Media-Cookies" - }; - const displayMessage = computed(() => { - return props.message || `Dieser Inhalt erfordert ${categoryNames[props.category]}.`; - }); - return () => h("div", { class: "bp-consent-placeholder" }, [ - h("p", displayMessage.value), - h( - "button", - { - type: "button", - onClick: showSettings - }, - props.buttonText - ) - ]); - } -}); -var ConsentBanner = defineComponent({ - name: "ConsentBanner", - setup(_, { slots }) { - const { - consent, - isBannerVisible, - needsConsent, - acceptAll, - rejectAll, - saveSelection, - showSettings, - hideBanner - } = useConsent(); - const slotProps = computed(() => ({ - isVisible: isBannerVisible.value, - consent: consent.value, - needsConsent: needsConsent.value, - onAcceptAll: acceptAll, - onRejectAll: rejectAll, - onSaveSelection: saveSelection, - onShowSettings: showSettings, - onClose: hideBanner - })); - return () => { - if (slots.default) { - return slots.default(slotProps.value); - } - if (!isBannerVisible.value) { - return null; - } - return h( - "div", - { - class: "bp-consent-banner", - role: "dialog", - "aria-modal": "true", - "aria-label": "Cookie-Einstellungen" - }, - [ - h("div", { class: "bp-consent-banner-content" }, [ - h("h2", "Datenschutzeinstellungen"), - h( - "p", - "Wir nutzen Cookies und \xE4hnliche Technologien, um Ihnen ein optimales Nutzererlebnis zu bieten." - ), - h("div", { class: "bp-consent-banner-actions" }, [ - h( - "button", - { - type: "button", - class: "bp-consent-btn bp-consent-btn-reject", - onClick: rejectAll - }, - "Alle ablehnen" - ), - h( - "button", - { - type: "button", - class: "bp-consent-btn bp-consent-btn-settings", - onClick: showSettings - }, - "Einstellungen" - ), - h( - "button", - { - type: "button", - class: "bp-consent-btn bp-consent-btn-accept", - onClick: acceptAll - }, - "Alle akzeptieren" - ) - ]) - ]) - ] - ); - }; - } -}); -var ConsentPlugin = { - install(app, config) { - const manager = new ConsentManager(config); - const consent = ref(null); - const isInitialized = ref(false); - const isLoading = ref(true); - const isBannerVisible = ref(false); - manager.init().then(() => { - consent.value = manager.getConsent(); - isInitialized.value = true; - isLoading.value = false; - isBannerVisible.value = manager.isBannerVisible(); - }); - manager.on("change", (newConsent) => { - consent.value = newConsent; - }); - manager.on("banner_show", () => { - isBannerVisible.value = true; - }); - manager.on("banner_hide", () => { - isBannerVisible.value = false; - }); - const context = { - manager: ref(manager), - consent, - isInitialized, - isLoading, - isBannerVisible, - needsConsent: computed(() => manager.needsConsent()), - hasConsent: (category) => manager.hasConsent(category), - acceptAll: () => manager.acceptAll(), - rejectAll: () => manager.rejectAll(), - saveSelection: async (categories) => { - await manager.setConsent(categories); - manager.hideBanner(); - }, - showBanner: () => manager.showBanner(), - hideBanner: () => manager.hideBanner(), - showSettings: () => manager.showSettings() - }; - app.provide(CONSENT_KEY, context); - } -}; -export { - CONSENT_KEY, - ConsentBanner, - ConsentGate, - ConsentPlaceholder, - ConsentPlugin, - ConsentProvider, - provideConsent, - useConsent -}; -//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/docs-src/consent-sdk/dist/vue/index.mjs.map b/docs-src/consent-sdk/dist/vue/index.mjs.map deleted file mode 100644 index 89f36ea..0000000 --- a/docs-src/consent-sdk/dist/vue/index.mjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/vue/index.ts","../../src/core/ConsentStorage.ts","../../src/core/ScriptBlocker.ts","../../src/core/ConsentAPI.ts","../../src/utils/EventEmitter.ts","../../src/utils/fingerprint.ts","../../src/version.ts","../../src/core/ConsentManager.ts"],"sourcesContent":["/**\n * Vue 3 Integration fuer @breakpilot/consent-sdk\n *\n * @example\n * ```vue\n * \n *\n * \n * ```\n */\n\nimport {\n ref,\n computed,\n readonly,\n inject,\n provide,\n onMounted,\n onUnmounted,\n defineComponent,\n h,\n type Ref,\n type InjectionKey,\n type PropType,\n} from 'vue';\nimport { ConsentManager } from '../core/ConsentManager';\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n} from '../types';\n\n// =============================================================================\n// Injection Key\n// =============================================================================\n\nconst CONSENT_KEY: InjectionKey = Symbol('consent');\n\n// =============================================================================\n// Types\n// =============================================================================\n\ninterface ConsentContext {\n manager: Ref;\n consent: Ref;\n isInitialized: Ref;\n isLoading: Ref;\n isBannerVisible: Ref;\n needsConsent: Ref;\n hasConsent: (category: ConsentCategory) => boolean;\n acceptAll: () => Promise;\n rejectAll: () => Promise;\n saveSelection: (categories: Partial) => Promise;\n showBanner: () => void;\n hideBanner: () => void;\n showSettings: () => void;\n}\n\n// =============================================================================\n// Composable: useConsent\n// =============================================================================\n\n/**\n * Haupt-Composable fuer Consent-Zugriff\n *\n * @example\n * ```vue\n * \n * ```\n */\nexport function useConsent(): ConsentContext {\n const context = inject(CONSENT_KEY);\n\n if (!context) {\n throw new Error(\n 'useConsent() must be used within a component that has called provideConsent() or is wrapped in ConsentProvider'\n );\n }\n\n return context;\n}\n\n/**\n * Consent-Provider einrichten (in App.vue aufrufen)\n *\n * @example\n * ```vue\n * \n * ```\n */\nexport function provideConsent(config: ConsentConfig): ConsentContext {\n const manager = ref(null);\n const consent = ref(null);\n const isInitialized = ref(false);\n const isLoading = ref(true);\n const isBannerVisible = ref(false);\n\n const needsConsent = computed(() => {\n return manager.value?.needsConsent() ?? true;\n });\n\n // Initialisierung\n onMounted(async () => {\n const consentManager = new ConsentManager(config);\n manager.value = consentManager;\n\n // Events abonnieren\n const unsubChange = consentManager.on('change', (newConsent) => {\n consent.value = newConsent;\n });\n\n const unsubBannerShow = consentManager.on('banner_show', () => {\n isBannerVisible.value = true;\n });\n\n const unsubBannerHide = consentManager.on('banner_hide', () => {\n isBannerVisible.value = false;\n });\n\n try {\n await consentManager.init();\n consent.value = consentManager.getConsent();\n isInitialized.value = true;\n isBannerVisible.value = consentManager.isBannerVisible();\n } catch (error) {\n console.error('Failed to initialize ConsentManager:', error);\n } finally {\n isLoading.value = false;\n }\n\n // Cleanup bei Unmount\n onUnmounted(() => {\n unsubChange();\n unsubBannerShow();\n unsubBannerHide();\n });\n });\n\n // Methoden\n const hasConsent = (category: ConsentCategory): boolean => {\n return manager.value?.hasConsent(category) ?? category === 'essential';\n };\n\n const acceptAll = async (): Promise => {\n await manager.value?.acceptAll();\n };\n\n const rejectAll = async (): Promise => {\n await manager.value?.rejectAll();\n };\n\n const saveSelection = async (categories: Partial): Promise => {\n await manager.value?.setConsent(categories);\n manager.value?.hideBanner();\n };\n\n const showBanner = (): void => {\n manager.value?.showBanner();\n };\n\n const hideBanner = (): void => {\n manager.value?.hideBanner();\n };\n\n const showSettings = (): void => {\n manager.value?.showSettings();\n };\n\n const context: ConsentContext = {\n manager: readonly(manager) as Ref,\n consent: readonly(consent) as Ref,\n isInitialized: readonly(isInitialized),\n isLoading: readonly(isLoading),\n isBannerVisible: readonly(isBannerVisible),\n needsConsent,\n hasConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showBanner,\n hideBanner,\n showSettings,\n };\n\n provide(CONSENT_KEY, context);\n\n return context;\n}\n\n// =============================================================================\n// Components\n// =============================================================================\n\n/**\n * ConsentProvider - Wrapper-Komponente\n *\n * @example\n * ```vue\n * \n * \n * \n * ```\n */\nexport const ConsentProvider = defineComponent({\n name: 'ConsentProvider',\n props: {\n config: {\n type: Object as PropType,\n required: true,\n },\n },\n setup(props, { slots }) {\n provideConsent(props.config);\n return () => slots.default?.();\n },\n});\n\n/**\n * ConsentGate - Zeigt Inhalt nur bei Consent\n *\n * @example\n * ```vue\n * \n * \n * \n * \n * ```\n */\nexport const ConsentGate = defineComponent({\n name: 'ConsentGate',\n props: {\n category: {\n type: String as PropType,\n required: true,\n },\n },\n setup(props, { slots }) {\n const { hasConsent, isLoading } = useConsent();\n\n return () => {\n if (isLoading.value) {\n return slots.fallback?.() ?? null;\n }\n\n if (!hasConsent(props.category)) {\n return slots.placeholder?.() ?? null;\n }\n\n return slots.default?.();\n };\n },\n});\n\n/**\n * ConsentPlaceholder - Placeholder fuer blockierten Inhalt\n *\n * @example\n * ```vue\n * \n * ```\n */\nexport const ConsentPlaceholder = defineComponent({\n name: 'ConsentPlaceholder',\n props: {\n category: {\n type: String as PropType,\n required: true,\n },\n message: {\n type: String,\n default: '',\n },\n buttonText: {\n type: String,\n default: 'Cookie-Einstellungen öffnen',\n },\n },\n setup(props) {\n const { showSettings } = useConsent();\n\n const categoryNames: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n\n const displayMessage = computed(() => {\n return props.message || `Dieser Inhalt erfordert ${categoryNames[props.category]}.`;\n });\n\n return () =>\n h('div', { class: 'bp-consent-placeholder' }, [\n h('p', displayMessage.value),\n h(\n 'button',\n {\n type: 'button',\n onClick: showSettings,\n },\n props.buttonText\n ),\n ]);\n },\n});\n\n/**\n * ConsentBanner - Cookie-Banner Komponente\n *\n * @example\n * ```vue\n * \n * \n * \n * ```\n */\nexport const ConsentBanner = defineComponent({\n name: 'ConsentBanner',\n setup(_, { slots }) {\n const {\n consent,\n isBannerVisible,\n needsConsent,\n acceptAll,\n rejectAll,\n saveSelection,\n showSettings,\n hideBanner,\n } = useConsent();\n\n const slotProps = computed(() => ({\n isVisible: isBannerVisible.value,\n consent: consent.value,\n needsConsent: needsConsent.value,\n onAcceptAll: acceptAll,\n onRejectAll: rejectAll,\n onSaveSelection: saveSelection,\n onShowSettings: showSettings,\n onClose: hideBanner,\n }));\n\n return () => {\n // Custom Slot\n if (slots.default) {\n return slots.default(slotProps.value);\n }\n\n // Default UI\n if (!isBannerVisible.value) {\n return null;\n }\n\n return h(\n 'div',\n {\n class: 'bp-consent-banner',\n role: 'dialog',\n 'aria-modal': 'true',\n 'aria-label': 'Cookie-Einstellungen',\n },\n [\n h('div', { class: 'bp-consent-banner-content' }, [\n h('h2', 'Datenschutzeinstellungen'),\n h(\n 'p',\n 'Wir nutzen Cookies und ähnliche Technologien, um Ihnen ein optimales Nutzererlebnis zu bieten.'\n ),\n h('div', { class: 'bp-consent-banner-actions' }, [\n h(\n 'button',\n {\n type: 'button',\n class: 'bp-consent-btn bp-consent-btn-reject',\n onClick: rejectAll,\n },\n 'Alle ablehnen'\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'bp-consent-btn bp-consent-btn-settings',\n onClick: showSettings,\n },\n 'Einstellungen'\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'bp-consent-btn bp-consent-btn-accept',\n onClick: acceptAll,\n },\n 'Alle akzeptieren'\n ),\n ]),\n ]),\n ]\n );\n };\n },\n});\n\n// =============================================================================\n// Plugin\n// =============================================================================\n\n/**\n * Vue Plugin fuer globale Installation\n *\n * @example\n * ```ts\n * import { createApp } from 'vue';\n * import { ConsentPlugin } from '@breakpilot/consent-sdk/vue';\n *\n * const app = createApp(App);\n * app.use(ConsentPlugin, {\n * apiEndpoint: 'https://consent.example.com/api/v1',\n * siteId: 'site_abc123',\n * });\n * ```\n */\nexport const ConsentPlugin = {\n install(app: { provide: (key: symbol | string, value: unknown) => void }, config: ConsentConfig) {\n const manager = new ConsentManager(config);\n const consent = ref(null);\n const isInitialized = ref(false);\n const isLoading = ref(true);\n const isBannerVisible = ref(false);\n\n // Initialisieren\n manager.init().then(() => {\n consent.value = manager.getConsent();\n isInitialized.value = true;\n isLoading.value = false;\n isBannerVisible.value = manager.isBannerVisible();\n });\n\n // Events\n manager.on('change', (newConsent) => {\n consent.value = newConsent;\n });\n manager.on('banner_show', () => {\n isBannerVisible.value = true;\n });\n manager.on('banner_hide', () => {\n isBannerVisible.value = false;\n });\n\n const context: ConsentContext = {\n manager: ref(manager) as Ref,\n consent: consent as Ref,\n isInitialized,\n isLoading,\n isBannerVisible,\n needsConsent: computed(() => manager.needsConsent()),\n hasConsent: (category: ConsentCategory) => manager.hasConsent(category),\n acceptAll: () => manager.acceptAll(),\n rejectAll: () => manager.rejectAll(),\n saveSelection: async (categories: Partial) => {\n await manager.setConsent(categories);\n manager.hideBanner();\n },\n showBanner: () => manager.showBanner(),\n hideBanner: () => manager.hideBanner(),\n showSettings: () => manager.showSettings(),\n };\n\n app.provide(CONSENT_KEY, context);\n },\n};\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport { CONSENT_KEY };\nexport type { ConsentContext };\n","/**\n * ConsentStorage - Lokale Speicherung des Consent-Status\n *\n * Speichert Consent-Daten im localStorage mit HMAC-Signatur\n * zur Manipulationserkennung.\n */\n\nimport type { ConsentConfig, ConsentState } from '../types';\n\nconst STORAGE_KEY = 'bp_consent';\nconst STORAGE_VERSION = '1';\n\n/**\n * Gespeichertes Format\n */\ninterface StoredConsent {\n version: string;\n consent: ConsentState;\n signature: string;\n}\n\n/**\n * ConsentStorage - Persistente Speicherung\n */\nexport class ConsentStorage {\n private config: ConsentConfig;\n private storageKey: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n // Pro Site ein separater Key\n this.storageKey = `${STORAGE_KEY}_${config.siteId}`;\n }\n\n /**\n * Consent laden\n */\n get(): ConsentState | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) {\n return null;\n }\n\n const stored: StoredConsent = JSON.parse(raw);\n\n // Version pruefen\n if (stored.version !== STORAGE_VERSION) {\n this.log('Storage version mismatch, clearing');\n this.clear();\n return null;\n }\n\n // Signatur pruefen\n if (!this.verifySignature(stored.consent, stored.signature)) {\n this.log('Invalid signature, clearing');\n this.clear();\n return null;\n }\n\n return stored.consent;\n } catch (error) {\n this.log('Failed to load consent:', error);\n return null;\n }\n }\n\n /**\n * Consent speichern\n */\n set(consent: ConsentState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n const signature = this.generateSignature(consent);\n\n const stored: StoredConsent = {\n version: STORAGE_VERSION,\n consent,\n signature,\n };\n\n localStorage.setItem(this.storageKey, JSON.stringify(stored));\n\n // Auch als Cookie setzen (fuer Server-Side Rendering)\n this.setCookie(consent);\n\n this.log('Consent saved to storage');\n } catch (error) {\n this.log('Failed to save consent:', error);\n }\n }\n\n /**\n * Consent loeschen\n */\n clear(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(this.storageKey);\n this.clearCookie();\n this.log('Consent cleared from storage');\n } catch (error) {\n this.log('Failed to clear consent:', error);\n }\n }\n\n /**\n * Pruefen ob Consent existiert\n */\n exists(): boolean {\n return this.get() !== null;\n }\n\n // ===========================================================================\n // Cookie Management\n // ===========================================================================\n\n /**\n * Consent als Cookie setzen\n */\n private setCookie(consent: ConsentState): void {\n const days = this.config.consent?.rememberDays ?? 365;\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n // Nur Kategorien als Cookie (fuer SSR)\n const cookieValue = JSON.stringify(consent.categories);\n const encoded = encodeURIComponent(cookieValue);\n\n document.cookie = [\n `${this.storageKey}=${encoded}`,\n `expires=${expires.toUTCString()}`,\n 'path=/',\n 'SameSite=Lax',\n location.protocol === 'https:' ? 'Secure' : '',\n ]\n .filter(Boolean)\n .join('; ');\n }\n\n /**\n * Cookie loeschen\n */\n private clearCookie(): void {\n document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;\n }\n\n // ===========================================================================\n // Signature (Simple HMAC-like)\n // ===========================================================================\n\n /**\n * Signatur generieren\n */\n private generateSignature(consent: ConsentState): string {\n const data = JSON.stringify(consent);\n const key = this.config.siteId;\n\n // Einfache Hash-Funktion (fuer Client-Side)\n // In Produktion wuerde man SubtleCrypto verwenden\n return this.simpleHash(data + key);\n }\n\n /**\n * Signatur verifizieren\n */\n private verifySignature(consent: ConsentState, signature: string): boolean {\n const expected = this.generateSignature(consent);\n return expected === signature;\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentStorage]', ...args);\n }\n }\n}\n\nexport default ConsentStorage;\n","/**\n * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird\n *\n * Verwendet das data-consent Attribut zur Identifikation von\n * Skripten, die erst nach Consent geladen werden duerfen.\n *\n * Beispiel:\n * \n */\n\nimport type { ConsentConfig, ConsentCategory } from '../types';\n\n/**\n * Script-Element mit Consent-Attributen\n */\ninterface ConsentScript extends HTMLScriptElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * iFrame-Element mit Consent-Attributen\n */\ninterface ConsentIframe extends HTMLIFrameElement {\n dataset: DOMStringMap & {\n consent?: string;\n src?: string;\n };\n}\n\n/**\n * ScriptBlocker - Verwaltet Script-Blocking\n */\nexport class ScriptBlocker {\n private config: ConsentConfig;\n private observer: MutationObserver | null = null;\n private enabledCategories: Set = new Set(['essential']);\n private processedElements: WeakSet = new WeakSet();\n\n constructor(config: ConsentConfig) {\n this.config = config;\n }\n\n /**\n * Initialisieren und Observer starten\n */\n init(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Bestehende Elemente verarbeiten\n this.processExistingElements();\n\n // MutationObserver fuer neue Elemente\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.processElement(node as Element);\n }\n }\n }\n });\n\n this.observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n this.log('ScriptBlocker initialized');\n }\n\n /**\n * Kategorie aktivieren\n */\n enableCategory(category: ConsentCategory): void {\n if (this.enabledCategories.has(category)) {\n return;\n }\n\n this.enabledCategories.add(category);\n this.log('Category enabled:', category);\n\n // Blockierte Elemente dieser Kategorie aktivieren\n this.activateCategory(category);\n }\n\n /**\n * Kategorie deaktivieren\n */\n disableCategory(category: ConsentCategory): void {\n if (category === 'essential') {\n // Essential kann nicht deaktiviert werden\n return;\n }\n\n this.enabledCategories.delete(category);\n this.log('Category disabled:', category);\n\n // Hinweis: Bereits geladene Skripte koennen nicht entladen werden\n // Page-Reload noetig fuer vollstaendige Deaktivierung\n }\n\n /**\n * Alle Kategorien blockieren (ausser Essential)\n */\n blockAll(): void {\n this.enabledCategories.clear();\n this.enabledCategories.add('essential');\n this.log('All categories blocked');\n }\n\n /**\n * Pruefen ob Kategorie aktiviert\n */\n isCategoryEnabled(category: ConsentCategory): boolean {\n return this.enabledCategories.has(category);\n }\n\n /**\n * Observer stoppen\n */\n destroy(): void {\n this.observer?.disconnect();\n this.observer = null;\n this.log('ScriptBlocker destroyed');\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Bestehende Elemente verarbeiten\n */\n private processExistingElements(): void {\n // Scripts mit data-consent\n const scripts = document.querySelectorAll(\n 'script[data-consent]'\n );\n scripts.forEach((script) => this.processScript(script));\n\n // iFrames mit data-consent\n const iframes = document.querySelectorAll(\n 'iframe[data-consent]'\n );\n iframes.forEach((iframe) => this.processIframe(iframe));\n\n this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`);\n }\n\n /**\n * Element verarbeiten\n */\n private processElement(element: Element): void {\n if (element.tagName === 'SCRIPT') {\n this.processScript(element as ConsentScript);\n } else if (element.tagName === 'IFRAME') {\n this.processIframe(element as ConsentIframe);\n }\n\n // Auch Kinder verarbeiten\n element\n .querySelectorAll('script[data-consent]')\n .forEach((script) => this.processScript(script));\n element\n .querySelectorAll('iframe[data-consent]')\n .forEach((iframe) => this.processIframe(iframe));\n }\n\n /**\n * Script-Element verarbeiten\n */\n private processScript(script: ConsentScript): void {\n if (this.processedElements.has(script)) {\n return;\n }\n\n const category = script.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(script);\n\n if (this.enabledCategories.has(category)) {\n this.activateScript(script);\n } else {\n this.log(`Script blocked (${category}):`, script.dataset.src || 'inline');\n }\n }\n\n /**\n * iFrame-Element verarbeiten\n */\n private processIframe(iframe: ConsentIframe): void {\n if (this.processedElements.has(iframe)) {\n return;\n }\n\n const category = iframe.dataset.consent as ConsentCategory | undefined;\n if (!category) {\n return;\n }\n\n this.processedElements.add(iframe);\n\n if (this.enabledCategories.has(category)) {\n this.activateIframe(iframe);\n } else {\n this.log(`iFrame blocked (${category}):`, iframe.dataset.src);\n // Placeholder anzeigen\n this.showPlaceholder(iframe, category);\n }\n }\n\n /**\n * Script aktivieren\n */\n private activateScript(script: ConsentScript): void {\n const src = script.dataset.src;\n\n if (src) {\n // Externes Script: neues Element erstellen\n const newScript = document.createElement('script');\n\n // Attribute kopieren\n for (const attr of script.attributes) {\n if (attr.name !== 'type' && attr.name !== 'data-src') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.src = src;\n newScript.removeAttribute('data-consent');\n\n // Altes Element ersetzen\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('External script activated:', src);\n } else {\n // Inline-Script: type aendern\n const newScript = document.createElement('script');\n\n for (const attr of script.attributes) {\n if (attr.name !== 'type') {\n newScript.setAttribute(attr.name, attr.value);\n }\n }\n\n newScript.textContent = script.textContent;\n newScript.removeAttribute('data-consent');\n\n script.parentNode?.replaceChild(newScript, script);\n\n this.log('Inline script activated');\n }\n }\n\n /**\n * iFrame aktivieren\n */\n private activateIframe(iframe: ConsentIframe): void {\n const src = iframe.dataset.src;\n if (!src) {\n return;\n }\n\n // Placeholder entfernen falls vorhanden\n const placeholder = iframe.parentElement?.querySelector(\n '.bp-consent-placeholder'\n );\n placeholder?.remove();\n\n // src setzen\n iframe.src = src;\n iframe.removeAttribute('data-src');\n iframe.removeAttribute('data-consent');\n iframe.style.display = '';\n\n this.log('iFrame activated:', src);\n }\n\n /**\n * Placeholder fuer blockierten iFrame anzeigen\n */\n private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void {\n // iFrame verstecken\n iframe.style.display = 'none';\n\n // Placeholder erstellen\n const placeholder = document.createElement('div');\n placeholder.className = 'bp-consent-placeholder';\n placeholder.setAttribute('data-category', category);\n placeholder.innerHTML = `\n \n `;\n\n // Click-Handler\n const btn = placeholder.querySelector('button');\n btn?.addEventListener('click', () => {\n // Event dispatchen damit ConsentManager reagieren kann\n window.dispatchEvent(\n new CustomEvent('bp-consent-request', {\n detail: { category },\n })\n );\n });\n\n // Nach iFrame einfuegen\n iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling);\n }\n\n /**\n * Alle Elemente einer Kategorie aktivieren\n */\n private activateCategory(category: ConsentCategory): void {\n // Scripts\n const scripts = document.querySelectorAll(\n `script[data-consent=\"${category}\"]`\n );\n scripts.forEach((script) => this.activateScript(script));\n\n // iFrames\n const iframes = document.querySelectorAll(\n `iframe[data-consent=\"${category}\"]`\n );\n iframes.forEach((iframe) => this.activateIframe(iframe));\n\n this.log(\n `Activated ${scripts.length} scripts, ${iframes.length} iframes for ${category}`\n );\n }\n\n /**\n * Kategorie-Name fuer UI\n */\n private getCategoryName(category: ConsentCategory): string {\n const names: Record = {\n essential: 'Essentielle Cookies',\n functional: 'Funktionale Cookies',\n analytics: 'Statistik-Cookies',\n marketing: 'Marketing-Cookies',\n social: 'Social Media-Cookies',\n };\n return names[category] ?? category;\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ScriptBlocker]', ...args);\n }\n }\n}\n\nexport default ScriptBlocker;\n","/**\n * ConsentAPI - Kommunikation mit dem Consent-Backend\n *\n * Sendet Consent-Entscheidungen an das Backend zur\n * revisionssicheren Speicherung.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentAPIResponse,\n SiteConfigResponse,\n} from '../types';\n\n/**\n * Request-Payload fuer Consent-Speicherung\n */\ninterface SaveConsentRequest {\n siteId: string;\n userId?: string;\n deviceFingerprint: string;\n consent: ConsentState;\n metadata?: {\n userAgent?: string;\n language?: string;\n screenResolution?: string;\n platform?: string;\n appVersion?: string;\n };\n}\n\n/**\n * ConsentAPI - Backend-Kommunikation\n */\nexport class ConsentAPI {\n private config: ConsentConfig;\n private baseUrl: string;\n\n constructor(config: ConsentConfig) {\n this.config = config;\n this.baseUrl = config.apiEndpoint.replace(/\\/$/, '');\n }\n\n /**\n * Consent speichern\n */\n async saveConsent(request: SaveConsentRequest): Promise {\n const payload = {\n ...request,\n metadata: {\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n language: typeof navigator !== 'undefined' ? navigator.language : '',\n screenResolution:\n typeof window !== 'undefined'\n ? `${window.screen.width}x${window.screen.height}`\n : '',\n platform: 'web',\n ...request.metadata,\n },\n };\n\n const response = await this.fetch('/consent', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to save consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent abrufen\n */\n async getConsent(\n siteId: string,\n deviceFingerprint: string\n ): Promise {\n const params = new URLSearchParams({\n siteId,\n deviceFingerprint,\n });\n\n const response = await this.fetch(`/consent?${params}`);\n\n if (response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to get consent: ${response.status}`);\n }\n\n const data = await response.json();\n return data.consent;\n }\n\n /**\n * Consent widerrufen\n */\n async revokeConsent(consentId: string): Promise {\n const response = await this.fetch(`/consent/${consentId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to revoke consent: ${response.status}`);\n }\n }\n\n /**\n * Site-Konfiguration abrufen\n */\n async getSiteConfig(siteId: string): Promise {\n const response = await this.fetch(`/config/${siteId}`);\n\n if (!response.ok) {\n throw new Error(`Failed to get site config: ${response.status}`);\n }\n\n return response.json();\n }\n\n /**\n * Consent-Historie exportieren (DSGVO Art. 20)\n */\n async exportConsent(userId: string): Promise {\n const params = new URLSearchParams({ userId });\n const response = await this.fetch(`/consent/export?${params}`);\n\n if (!response.ok) {\n throw new Error(`Failed to export consent: ${response.status}`);\n }\n\n return response.json();\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Fetch mit Standard-Headers\n */\n private async fetch(\n path: string,\n options: RequestInit = {}\n ): Promise {\n const url = `${this.baseUrl}${path}`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...this.getSignatureHeaders(),\n ...(options.headers || {}),\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n credentials: 'include',\n });\n\n this.log(`${options.method || 'GET'} ${path}:`, response.status);\n return response;\n } catch (error) {\n this.log('Fetch error:', error);\n throw error;\n }\n }\n\n /**\n * Signatur-Headers generieren (HMAC)\n */\n private getSignatureHeaders(): Record {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n\n // Einfache Signatur fuer Client-Side\n // In Produktion: Server-seitige Validierung mit echtem HMAC\n const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);\n\n return {\n 'X-Consent-Timestamp': timestamp,\n 'X-Consent-Signature': `sha256=${signature}`,\n };\n }\n\n /**\n * Einfache Hash-Funktion (djb2)\n */\n private simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentAPI]', ...args);\n }\n }\n}\n\nexport default ConsentAPI;\n","/**\n * EventEmitter - Typsicherer Event-Handler\n */\n\ntype EventCallback = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class EventEmitter = Record> {\n private listeners: Map>> = new Map();\n\n /**\n * Event-Listener registrieren\n * @returns Unsubscribe-Funktion\n */\n on(\n event: K,\n callback: EventCallback\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback as EventCallback);\n\n // Unsubscribe-Funktion zurueckgeben\n return () => this.off(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: K,\n callback: EventCallback\n ): void {\n this.listeners.get(event)?.delete(callback as EventCallback);\n }\n\n /**\n * Event emittieren\n */\n emit(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in event handler for ${String(event)}:`, error);\n }\n });\n }\n\n /**\n * Einmaligen Listener registrieren\n */\n once(\n event: K,\n callback: EventCallback\n ): () => void {\n const wrapper = (data: Events[K]) => {\n this.off(event, wrapper);\n callback(data);\n };\n\n return this.on(event, wrapper);\n }\n\n /**\n * Alle Listener entfernen\n */\n clear(): void {\n this.listeners.clear();\n }\n\n /**\n * Alle Listener fuer ein Event entfernen\n */\n clearEvent(event: K): void {\n this.listeners.delete(event);\n }\n\n /**\n * Anzahl Listener fuer ein Event\n */\n listenerCount(event: K): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\nexport default EventEmitter;\n","/**\n * Device Fingerprinting - Datenschutzkonform\n *\n * Generiert einen anonymen Fingerprint OHNE:\n * - Canvas Fingerprinting\n * - WebGL Fingerprinting\n * - Audio Fingerprinting\n * - Hardware-spezifische IDs\n *\n * Verwendet nur:\n * - User Agent\n * - Sprache\n * - Bildschirmaufloesung\n * - Zeitzone\n * - Platform\n */\n\n/**\n * Fingerprint-Komponenten sammeln\n */\nfunction getComponents(): string[] {\n if (typeof window === 'undefined') {\n return ['server'];\n }\n\n const components: string[] = [];\n\n // User Agent (anonymisiert)\n try {\n // Nur Browser-Familie, nicht vollstaendiger UA\n const ua = navigator.userAgent;\n if (ua.includes('Chrome')) components.push('chrome');\n else if (ua.includes('Firefox')) components.push('firefox');\n else if (ua.includes('Safari')) components.push('safari');\n else if (ua.includes('Edge')) components.push('edge');\n else components.push('other');\n } catch {\n components.push('unknown-browser');\n }\n\n // Sprache\n try {\n components.push(navigator.language || 'unknown-lang');\n } catch {\n components.push('unknown-lang');\n }\n\n // Bildschirm-Kategorie (nicht exakte Aufloesung)\n try {\n const width = window.screen.width;\n if (width >= 2560) components.push('4k');\n else if (width >= 1920) components.push('fhd');\n else if (width >= 1366) components.push('hd');\n else if (width >= 768) components.push('tablet');\n else components.push('mobile');\n } catch {\n components.push('unknown-screen');\n }\n\n // Farbtiefe (grob)\n try {\n const depth = window.screen.colorDepth;\n if (depth >= 24) components.push('deep-color');\n else components.push('standard-color');\n } catch {\n components.push('unknown-color');\n }\n\n // Zeitzone (nur Offset, nicht Name)\n try {\n const offset = new Date().getTimezoneOffset();\n const hours = Math.floor(Math.abs(offset) / 60);\n const sign = offset <= 0 ? '+' : '-';\n components.push(`tz${sign}${hours}`);\n } catch {\n components.push('unknown-tz');\n }\n\n // Platform-Kategorie\n try {\n const platform = navigator.platform?.toLowerCase() || '';\n if (platform.includes('mac')) components.push('mac');\n else if (platform.includes('win')) components.push('win');\n else if (platform.includes('linux')) components.push('linux');\n else if (platform.includes('iphone') || platform.includes('ipad'))\n components.push('ios');\n else if (platform.includes('android')) components.push('android');\n else components.push('other-platform');\n } catch {\n components.push('unknown-platform');\n }\n\n // Touch-Faehigkeit\n try {\n if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {\n components.push('touch');\n } else {\n components.push('no-touch');\n }\n } catch {\n components.push('unknown-touch');\n }\n\n // Do Not Track (als Datenschutz-Signal)\n try {\n if (navigator.doNotTrack === '1') {\n components.push('dnt');\n }\n } catch {\n // Ignorieren\n }\n\n return components;\n}\n\n/**\n * SHA-256 Hash (async, nutzt SubtleCrypto)\n */\nasync function sha256(message: string): Promise {\n if (typeof window === 'undefined' || !window.crypto?.subtle) {\n // Fallback fuer Server/alte Browser\n return simpleHash(message);\n }\n\n try {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n } catch {\n return simpleHash(message);\n }\n}\n\n/**\n * Fallback Hash-Funktion (djb2)\n */\nfunction simpleHash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Datenschutzkonformen Fingerprint generieren\n *\n * Der Fingerprint ist:\n * - Nicht eindeutig (viele Nutzer teilen sich denselben)\n * - Nicht persistent (aendert sich bei Browser-Updates)\n * - Nicht invasiv (keine Canvas/WebGL/Audio)\n * - Anonymisiert (SHA-256 Hash)\n */\nexport async function generateFingerprint(): Promise {\n const components = getComponents();\n const combined = components.join('|');\n const hash = await sha256(combined);\n\n // Prefix fuer Identifikation\n return `fp_${hash.substring(0, 32)}`;\n}\n\n/**\n * Synchrone Version (mit einfachem Hash)\n */\nexport function generateFingerprintSync(): string {\n const components = getComponents();\n const combined = components.join('|');\n const hash = simpleHash(combined);\n\n return `fp_${hash}`;\n}\n\nexport default generateFingerprint;\n","/**\n * SDK Version\n */\nexport const SDK_VERSION = '1.0.0';\n\nexport default SDK_VERSION;\n","/**\n * ConsentManager - Hauptklasse fuer das Consent Management\n *\n * DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.\n */\n\nimport type {\n ConsentConfig,\n ConsentState,\n ConsentCategory,\n ConsentCategories,\n ConsentInput,\n ConsentEventType,\n ConsentEventCallback,\n ConsentEventData,\n} from '../types';\nimport { ConsentStorage } from './ConsentStorage';\nimport { ScriptBlocker } from './ScriptBlocker';\nimport { ConsentAPI } from './ConsentAPI';\nimport { EventEmitter } from '../utils/EventEmitter';\nimport { generateFingerprint } from '../utils/fingerprint';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Default-Konfiguration\n */\nconst DEFAULT_CONFIG: Partial = {\n language: 'de',\n fallbackLanguage: 'en',\n ui: {\n position: 'bottom',\n layout: 'modal',\n theme: 'auto',\n zIndex: 999999,\n blockScrollOnModal: true,\n },\n consent: {\n required: true,\n rejectAllVisible: true,\n acceptAllVisible: true,\n granularControl: true,\n vendorControl: false,\n rememberChoice: true,\n rememberDays: 365,\n geoTargeting: false,\n recheckAfterDays: 180,\n },\n categories: ['essential', 'functional', 'analytics', 'marketing', 'social'],\n debug: false,\n};\n\n/**\n * Default Consent-State (nur Essential aktiv)\n */\nconst DEFAULT_CONSENT: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n};\n\n/**\n * ConsentManager - Zentrale Klasse fuer Consent-Verwaltung\n */\nexport class ConsentManager {\n private config: ConsentConfig;\n private storage: ConsentStorage;\n private scriptBlocker: ScriptBlocker;\n private api: ConsentAPI;\n private events: EventEmitter;\n private currentConsent: ConsentState | null = null;\n private initialized = false;\n private bannerVisible = false;\n private deviceFingerprint: string = '';\n\n constructor(config: ConsentConfig) {\n this.config = this.mergeConfig(config);\n this.storage = new ConsentStorage(this.config);\n this.scriptBlocker = new ScriptBlocker(this.config);\n this.api = new ConsentAPI(this.config);\n this.events = new EventEmitter();\n\n this.log('ConsentManager created with config:', this.config);\n }\n\n /**\n * SDK initialisieren\n */\n async init(): Promise {\n if (this.initialized) {\n this.log('Already initialized, skipping');\n return;\n }\n\n try {\n this.log('Initializing ConsentManager...');\n\n // Device Fingerprint generieren\n this.deviceFingerprint = await generateFingerprint();\n\n // Consent aus Storage laden\n this.currentConsent = this.storage.get();\n\n if (this.currentConsent) {\n this.log('Loaded consent from storage:', this.currentConsent);\n\n // Pruefen ob Consent abgelaufen\n if (this.isConsentExpired()) {\n this.log('Consent expired, clearing');\n this.storage.clear();\n this.currentConsent = null;\n } else {\n // Consent anwenden\n this.applyConsent();\n }\n }\n\n // Script-Blocker initialisieren\n this.scriptBlocker.init();\n\n this.initialized = true;\n this.emit('init', this.currentConsent);\n\n // Banner anzeigen falls noetig\n if (this.needsConsent()) {\n this.showBanner();\n }\n\n this.log('ConsentManager initialized successfully');\n } catch (error) {\n this.handleError(error as Error);\n throw error;\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Pruefen ob Consent fuer Kategorie vorhanden\n */\n hasConsent(category: ConsentCategory): boolean {\n if (!this.currentConsent) {\n return category === 'essential';\n }\n return this.currentConsent.categories[category] ?? false;\n }\n\n /**\n * Pruefen ob Consent fuer Vendor vorhanden\n */\n hasVendorConsent(vendorId: string): boolean {\n if (!this.currentConsent) {\n return false;\n }\n return this.currentConsent.vendors[vendorId] ?? false;\n }\n\n /**\n * Aktuellen Consent-State abrufen\n */\n getConsent(): ConsentState | null {\n return this.currentConsent ? { ...this.currentConsent } : null;\n }\n\n /**\n * Consent setzen\n */\n async setConsent(input: ConsentInput): Promise {\n const categories = this.normalizeConsentInput(input);\n\n // Essential ist immer aktiv\n categories.essential = true;\n\n const newConsent: ConsentState = {\n categories,\n vendors: 'vendors' in input && input.vendors ? input.vendors : {},\n timestamp: new Date().toISOString(),\n version: SDK_VERSION,\n };\n\n try {\n // An Backend senden\n const response = await this.api.saveConsent({\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n consent: newConsent,\n });\n\n newConsent.consentId = response.consentId;\n newConsent.expiresAt = response.expiresAt;\n\n // Lokal speichern\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n\n // Consent anwenden\n this.applyConsent();\n\n // Event emittieren\n this.emit('change', newConsent);\n this.config.onConsentChange?.(newConsent);\n\n this.log('Consent saved:', newConsent);\n } catch (error) {\n // Bei Netzwerkfehler trotzdem lokal speichern\n this.log('API error, saving locally:', error);\n this.storage.set(newConsent);\n this.currentConsent = newConsent;\n this.applyConsent();\n this.emit('change', newConsent);\n }\n }\n\n /**\n * Alle Kategorien akzeptieren\n */\n async acceptAll(): Promise {\n const allCategories: ConsentCategories = {\n essential: true,\n functional: true,\n analytics: true,\n marketing: true,\n social: true,\n };\n\n await this.setConsent(allCategories);\n this.emit('accept_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle nicht-essentiellen Kategorien ablehnen\n */\n async rejectAll(): Promise {\n const minimalCategories: ConsentCategories = {\n essential: true,\n functional: false,\n analytics: false,\n marketing: false,\n social: false,\n };\n\n await this.setConsent(minimalCategories);\n this.emit('reject_all', this.currentConsent!);\n this.hideBanner();\n }\n\n /**\n * Alle Einwilligungen widerrufen\n */\n async revokeAll(): Promise {\n if (this.currentConsent?.consentId) {\n try {\n await this.api.revokeConsent(this.currentConsent.consentId);\n } catch (error) {\n this.log('Failed to revoke on server:', error);\n }\n }\n\n this.storage.clear();\n this.currentConsent = null;\n this.scriptBlocker.blockAll();\n\n this.log('All consents revoked');\n }\n\n /**\n * Consent-Daten exportieren (DSGVO Art. 20)\n */\n async exportConsent(): Promise {\n const exportData = {\n currentConsent: this.currentConsent,\n exportedAt: new Date().toISOString(),\n siteId: this.config.siteId,\n deviceFingerprint: this.deviceFingerprint,\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n // ===========================================================================\n // Banner Control\n // ===========================================================================\n\n /**\n * Pruefen ob Consent-Abfrage noetig\n */\n needsConsent(): boolean {\n if (!this.currentConsent) {\n return true;\n }\n\n if (this.isConsentExpired()) {\n return true;\n }\n\n // Recheck nach X Tagen\n if (this.config.consent?.recheckAfterDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const recheckDate = new Date(consentDate);\n recheckDate.setDate(\n recheckDate.getDate() + this.config.consent.recheckAfterDays\n );\n\n if (new Date() > recheckDate) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Banner anzeigen\n */\n showBanner(): void {\n if (this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = true;\n this.emit('banner_show', undefined);\n this.config.onBannerShow?.();\n\n // Banner wird von UI-Komponente gerendert\n // Hier nur Status setzen\n this.log('Banner shown');\n }\n\n /**\n * Banner verstecken\n */\n hideBanner(): void {\n if (!this.bannerVisible) {\n return;\n }\n\n this.bannerVisible = false;\n this.emit('banner_hide', undefined);\n this.config.onBannerHide?.();\n\n this.log('Banner hidden');\n }\n\n /**\n * Einstellungs-Modal oeffnen\n */\n showSettings(): void {\n this.emit('settings_open', undefined);\n this.log('Settings opened');\n }\n\n /**\n * Pruefen ob Banner sichtbar\n */\n isBannerVisible(): boolean {\n return this.bannerVisible;\n }\n\n // ===========================================================================\n // Event Handling\n // ===========================================================================\n\n /**\n * Event-Listener registrieren\n */\n on(\n event: T,\n callback: ConsentEventCallback\n ): () => void {\n return this.events.on(event, callback);\n }\n\n /**\n * Event-Listener entfernen\n */\n off(\n event: T,\n callback: ConsentEventCallback\n ): void {\n this.events.off(event, callback);\n }\n\n // ===========================================================================\n // Internal Methods\n // ===========================================================================\n\n /**\n * Konfiguration zusammenfuehren\n */\n private mergeConfig(config: ConsentConfig): ConsentConfig {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n ui: { ...DEFAULT_CONFIG.ui, ...config.ui },\n consent: { ...DEFAULT_CONFIG.consent, ...config.consent },\n } as ConsentConfig;\n }\n\n /**\n * Consent-Input normalisieren\n */\n private normalizeConsentInput(input: ConsentInput): ConsentCategories {\n if ('categories' in input && input.categories) {\n return { ...DEFAULT_CONSENT, ...input.categories };\n }\n\n return { ...DEFAULT_CONSENT, ...(input as Partial) };\n }\n\n /**\n * Consent anwenden (Skripte aktivieren/blockieren)\n */\n private applyConsent(): void {\n if (!this.currentConsent) {\n return;\n }\n\n for (const [category, allowed] of Object.entries(\n this.currentConsent.categories\n )) {\n if (allowed) {\n this.scriptBlocker.enableCategory(category as ConsentCategory);\n } else {\n this.scriptBlocker.disableCategory(category as ConsentCategory);\n }\n }\n\n // Google Consent Mode aktualisieren\n this.updateGoogleConsentMode();\n }\n\n /**\n * Google Consent Mode v2 aktualisieren\n */\n private updateGoogleConsentMode(): void {\n if (typeof window === 'undefined' || !this.currentConsent) {\n return;\n }\n\n const gtag = (window as unknown as { gtag?: (...args: unknown[]) => void }).gtag;\n if (typeof gtag !== 'function') {\n return;\n }\n\n const { categories } = this.currentConsent;\n\n gtag('consent', 'update', {\n ad_storage: categories.marketing ? 'granted' : 'denied',\n ad_user_data: categories.marketing ? 'granted' : 'denied',\n ad_personalization: categories.marketing ? 'granted' : 'denied',\n analytics_storage: categories.analytics ? 'granted' : 'denied',\n functionality_storage: categories.functional ? 'granted' : 'denied',\n personalization_storage: categories.functional ? 'granted' : 'denied',\n security_storage: 'granted',\n });\n\n this.log('Google Consent Mode updated');\n }\n\n /**\n * Pruefen ob Consent abgelaufen\n */\n private isConsentExpired(): boolean {\n if (!this.currentConsent?.expiresAt) {\n // Fallback: Nach rememberDays ablaufen\n if (this.currentConsent?.timestamp && this.config.consent?.rememberDays) {\n const consentDate = new Date(this.currentConsent.timestamp);\n const expiryDate = new Date(consentDate);\n expiryDate.setDate(\n expiryDate.getDate() + this.config.consent.rememberDays\n );\n return new Date() > expiryDate;\n }\n return false;\n }\n\n return new Date() > new Date(this.currentConsent.expiresAt);\n }\n\n /**\n * Event emittieren\n */\n private emit(\n event: T,\n data: ConsentEventData[T]\n ): void {\n this.events.emit(event, data);\n }\n\n /**\n * Fehler behandeln\n */\n private handleError(error: Error): void {\n this.log('Error:', error);\n this.emit('error', error);\n this.config.onError?.(error);\n }\n\n /**\n * Debug-Logging\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[ConsentSDK]', ...args);\n }\n }\n\n // ===========================================================================\n // Static Methods\n // ===========================================================================\n\n /**\n * SDK-Version abrufen\n */\n static getVersion(): string {\n return SDK_VERSION;\n }\n}\n\n// Default-Export\nexport default ConsentManager;\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 2e27a21..d7c8ced 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ -site_name: Breakpilot Dokumentation -site_url: https://macmini:8008 +site_name: BreakPilot Core - Dokumentation +site_url: https://macmini:8009 docs_dir: docs-src site_dir: docs-site @@ -9,14 +9,16 @@ theme: palette: - scheme: default primary: teal + accent: teal toggle: icon: material/brightness-7 - name: Dark Mode aktivieren + name: Dark Mode - scheme: slate primary: teal + accent: teal toggle: icon: material/brightness-4 - name: Light Mode aktivieren + name: Light Mode features: - search.highlight - search.suggest @@ -25,7 +27,6 @@ theme: - navigation.expand - navigation.top - content.code.copy - - content.tabs.link - toc.follow plugins: @@ -52,46 +53,18 @@ markdown_extensions: - toc: permalink: true -extra: - social: - - icon: fontawesome/brands/github - link: http://macmini:3003/breakpilot/breakpilot-pwa - nav: - Start: index.md - Erste Schritte: - - Umgebung einrichten: getting-started/environment-setup.md + - Einrichtung: getting-started/environment-setup.md - Mac Mini Setup: getting-started/mac-mini-setup.md - Architektur: - - Systemuebersicht: architecture/system-architecture.md + - System-Architektur: architecture/system-architecture.md - Auth-System: architecture/auth-system.md - - Mail-RBAC: architecture/mail-rbac-architecture.md - - Multi-Agent: architecture/multi-agent.md + - Mail & RBAC: architecture/mail-rbac-architecture.md - Secrets Management: architecture/secrets-management.md - DevSecOps: architecture/devsecops.md - Environments: architecture/environments.md - - Zeugnis-System: architecture/zeugnis-system.md - - Services: - - KI-Daten-Pipeline: - - Uebersicht: services/ki-daten-pipeline/index.md - - Architektur: services/ki-daten-pipeline/architecture.md - - Klausur-Service: - - Uebersicht: services/klausur-service/index.md - - BYOEH Architektur: services/klausur-service/BYOEH-Architecture.md - - BYOEH Developer Guide: services/klausur-service/BYOEH-Developer-Guide.md - - NiBiS Pipeline: services/klausur-service/NiBiS-Ingestion-Pipeline.md - - OCR Labeling: services/klausur-service/OCR-Labeling-Spec.md - - OCR Compare: services/klausur-service/OCR-Compare.md - - RAG Admin: services/klausur-service/RAG-Admin-Spec.md - - Worksheet Editor: services/klausur-service/Worksheet-Editor-Architecture.md - - Voice-Service: services/voice-service/index.md - - Agent-Core: services/agent-core/index.md - - AI-Compliance-SDK: - - Uebersicht: services/ai-compliance-sdk/index.md - - Architektur: services/ai-compliance-sdk/ARCHITECTURE.md - - Developer Guide: services/ai-compliance-sdk/DEVELOPER.md - - Auditor Dokumentation: services/ai-compliance-sdk/AUDITOR_DOCUMENTATION.md - - SBOM: services/ai-compliance-sdk/SBOM.md - API: - Backend API: api/backend-api.md - Entwicklung: diff --git a/nginx/conf.d/default.conf b/nginx/conf.d/default.conf index bd45634..0172379 100644 --- a/nginx/conf.d/default.conf +++ b/nginx/conf.d/default.conf @@ -188,7 +188,7 @@ server { # Docs proxy location /docs/ { - set $upstream_docs bp-core-docs:8009; + set $upstream_docs bp-lehrer-docs:80; rewrite ^/docs(/.*)$ $1 break; proxy_pass http://$upstream_docs; proxy_http_version 1.1; @@ -242,7 +242,7 @@ server { # Docs proxy location /docs/ { - set $upstream_docs bp-core-docs:8009; + set $upstream_docs bp-compliance-docs:80; rewrite ^/docs(/.*)$ $1 break; proxy_pass http://$upstream_docs; proxy_http_version 1.1;