Files
breakpilot-core/docs-src/consent-sdk/dist/react/index.mjs
Benjamin Boenisch ad111d5e69 Initial commit: breakpilot-core - Shared Infrastructure
Docker Compose with 24+ services:
- PostgreSQL (PostGIS), Valkey, MinIO, Qdrant
- Vault (PKI/TLS), Nginx (Reverse Proxy)
- Backend Core API, Consent Service, Billing Service
- RAG Service, Embedding Service
- Gitea, Woodpecker CI/CD
- Night Scheduler, Health Aggregator
- Jitsi (Web/XMPP/JVB/Jicofo), Mailpit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:13 +01:00

1337 lines
36 KiB
JavaScript

// 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 = `
<div class="bp-consent-placeholder-content">
<p>Dieser Inhalt erfordert Ihre Zustimmung.</p>
<button type="button" class="bp-consent-placeholder-btn">
${this.getCategoryName(category)} aktivieren
</button>
</div>
`;
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