/** * ScriptBlocker - Blockiert Skripte bis Consent erteilt wird * * Verwendet das data-consent Attribut zur Identifikation von * Skripten, die erst nach Consent geladen werden duerfen. * * Beispiel: * */ import type { ConsentConfig, ConsentCategory } from '../types'; /** * Script-Element mit Consent-Attributen */ interface ConsentScript extends HTMLScriptElement { dataset: DOMStringMap & { consent?: string; src?: string; }; } /** * iFrame-Element mit Consent-Attributen */ interface ConsentIframe extends HTMLIFrameElement { dataset: DOMStringMap & { consent?: string; src?: string; }; } /** * ScriptBlocker - Verwaltet Script-Blocking */ export class ScriptBlocker { private config: ConsentConfig; private observer: MutationObserver | null = null; private enabledCategories: Set = new Set(['essential']); private processedElements: WeakSet = new WeakSet(); constructor(config: ConsentConfig) { this.config = config; } /** * Initialisieren und Observer starten */ init(): void { if (typeof window === 'undefined') { return; } // Bestehende Elemente verarbeiten this.processExistingElements(); // MutationObserver fuer neue Elemente 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 as Element); } } } }); this.observer.observe(document.documentElement, { childList: true, subtree: true, }); this.log('ScriptBlocker initialized'); } /** * Kategorie aktivieren */ enableCategory(category: ConsentCategory): void { if (this.enabledCategories.has(category)) { return; } this.enabledCategories.add(category); this.log('Category enabled:', category); // Blockierte Elemente dieser Kategorie aktivieren this.activateCategory(category); } /** * Kategorie deaktivieren */ disableCategory(category: ConsentCategory): void { if (category === 'essential') { // Essential kann nicht deaktiviert werden return; } this.enabledCategories.delete(category); this.log('Category disabled:', category); // Hinweis: Bereits geladene Skripte koennen nicht entladen werden // Page-Reload noetig fuer vollstaendige Deaktivierung } /** * Alle Kategorien blockieren (ausser Essential) */ blockAll(): void { this.enabledCategories.clear(); this.enabledCategories.add('essential'); this.log('All categories blocked'); } /** * Pruefen ob Kategorie aktiviert */ isCategoryEnabled(category: ConsentCategory): boolean { return this.enabledCategories.has(category); } /** * Observer stoppen */ destroy(): void { this.observer?.disconnect(); this.observer = null; this.log('ScriptBlocker destroyed'); } // =========================================================================== // Internal Methods // =========================================================================== /** * Bestehende Elemente verarbeiten */ private processExistingElements(): void { // Scripts mit data-consent const scripts = document.querySelectorAll( 'script[data-consent]' ); scripts.forEach((script) => this.processScript(script)); // iFrames mit data-consent const iframes = document.querySelectorAll( 'iframe[data-consent]' ); iframes.forEach((iframe) => this.processIframe(iframe)); this.log(`Processed ${scripts.length} scripts, ${iframes.length} iframes`); } /** * Element verarbeiten */ private processElement(element: Element): void { if (element.tagName === 'SCRIPT') { this.processScript(element as ConsentScript); } else if (element.tagName === 'IFRAME') { this.processIframe(element as ConsentIframe); } // Auch Kinder verarbeiten element .querySelectorAll('script[data-consent]') .forEach((script) => this.processScript(script)); element .querySelectorAll('iframe[data-consent]') .forEach((iframe) => this.processIframe(iframe)); } /** * Script-Element verarbeiten */ private processScript(script: ConsentScript): void { if (this.processedElements.has(script)) { return; } const category = script.dataset.consent as ConsentCategory | undefined; 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 */ private processIframe(iframe: ConsentIframe): void { if (this.processedElements.has(iframe)) { return; } const category = iframe.dataset.consent as ConsentCategory | undefined; if (!category) { return; } this.processedElements.add(iframe); if (this.enabledCategories.has(category)) { this.activateIframe(iframe); } else { this.log(`iFrame blocked (${category}):`, iframe.dataset.src); // Placeholder anzeigen this.showPlaceholder(iframe, category); } } /** * Script aktivieren */ private activateScript(script: ConsentScript): void { const src = script.dataset.src; if (src) { // Externes Script: neues Element erstellen const newScript = document.createElement('script'); // Attribute kopieren 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'); // Altes Element ersetzen script.parentNode?.replaceChild(newScript, script); this.log('External script activated:', src); } else { // Inline-Script: type aendern 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 */ private activateIframe(iframe: ConsentIframe): void { const src = iframe.dataset.src; if (!src) { return; } // Placeholder entfernen falls vorhanden const placeholder = iframe.parentElement?.querySelector( '.bp-consent-placeholder' ); placeholder?.remove(); // src setzen iframe.src = src; iframe.removeAttribute('data-src'); iframe.removeAttribute('data-consent'); iframe.style.display = ''; this.log('iFrame activated:', src); } /** * Placeholder fuer blockierten iFrame anzeigen */ private showPlaceholder(iframe: ConsentIframe, category: ConsentCategory): void { // iFrame verstecken iframe.style.display = 'none'; // Placeholder erstellen const placeholder = document.createElement('div'); placeholder.className = 'bp-consent-placeholder'; placeholder.setAttribute('data-category', category); placeholder.innerHTML = ` `; // Click-Handler const btn = placeholder.querySelector('button'); btn?.addEventListener('click', () => { // Event dispatchen damit ConsentManager reagieren kann window.dispatchEvent( new CustomEvent('bp-consent-request', { detail: { category }, }) ); }); // Nach iFrame einfuegen iframe.parentNode?.insertBefore(placeholder, iframe.nextSibling); } /** * Alle Elemente einer Kategorie aktivieren */ private activateCategory(category: ConsentCategory): void { // Scripts const scripts = document.querySelectorAll( `script[data-consent="${category}"]` ); scripts.forEach((script) => this.activateScript(script)); // iFrames 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 */ private getCategoryName(category: ConsentCategory): string { const names: Record = { essential: 'Essentielle Cookies', functional: 'Funktionale Cookies', analytics: 'Statistik-Cookies', marketing: 'Marketing-Cookies', social: 'Social Media-Cookies', }; return names[category] ?? category; } /** * Debug-Logging */ private log(...args: unknown[]): void { if (this.config.debug) { console.log('[ScriptBlocker]', ...args); } } } export default ScriptBlocker;