refactor(consent-sdk,dsms-gateway): split ConsentManager, types, and main.py
- consent-sdk/src/types/index.ts: extracted 438 LOC into core.ts, config.ts, vendor.ts, api.ts, events.ts, storage.ts, translations.ts; index.ts is now a 21-LOC barrel re-exporter - consent-sdk/src/core/ConsentManager.ts: extracted normalizeConsentInput, isConsentExpired, needsConsent, ALL_CATEGORIES, MINIMAL_CATEGORIES into consent-manager-helpers.ts; reduced from 467 to 345 LOC - dsms-gateway/main.py: extracted models → models.py, config → config.py, IPFS helpers + verify_token → dependencies.py, route handlers → routers/documents.py and routers/node.py; main.py is now a 41-LOC app factory; test mock paths updated accordingly (27/27 tests pass) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
/**
|
||||
* ConsentManager - Hauptklasse fuer das Consent Management
|
||||
*
|
||||
* DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile.
|
||||
*/
|
||||
/** ConsentManager - DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. */
|
||||
|
||||
import type {
|
||||
ConsentConfig,
|
||||
ConsentState,
|
||||
ConsentCategory,
|
||||
ConsentCategories,
|
||||
ConsentInput,
|
||||
ConsentEventType,
|
||||
ConsentEventCallback,
|
||||
@@ -20,15 +15,16 @@ import { ConsentAPI } from './ConsentAPI';
|
||||
import { EventEmitter } from '../utils/EventEmitter';
|
||||
import { generateFingerprint } from '../utils/fingerprint';
|
||||
import { SDK_VERSION } from '../version';
|
||||
import {
|
||||
DEFAULT_CONSENT,
|
||||
mergeConsentConfig,
|
||||
} from './consent-manager-config';
|
||||
import { mergeConsentConfig } from './consent-manager-config';
|
||||
import { updateGoogleConsentMode as applyGoogleConsent } from './consent-manager-google';
|
||||
import {
|
||||
normalizeConsentInput,
|
||||
isConsentExpired,
|
||||
needsConsent,
|
||||
ALL_CATEGORIES,
|
||||
MINIMAL_CATEGORIES,
|
||||
} from './consent-manager-helpers';
|
||||
|
||||
/**
|
||||
* ConsentManager - Zentrale Klasse fuer Consent-Verwaltung
|
||||
*/
|
||||
export class ConsentManager {
|
||||
private config: ConsentConfig;
|
||||
private storage: ConsentStorage;
|
||||
@@ -41,7 +37,7 @@ export class ConsentManager {
|
||||
private deviceFingerprint: string = '';
|
||||
|
||||
constructor(config: ConsentConfig) {
|
||||
this.config = this.mergeConfig(config);
|
||||
this.config = mergeConsentConfig(config);
|
||||
this.storage = new ConsentStorage(this.config);
|
||||
this.scriptBlocker = new ScriptBlocker(this.config);
|
||||
this.api = new ConsentAPI(this.config);
|
||||
@@ -72,7 +68,7 @@ export class ConsentManager {
|
||||
this.log('Loaded consent from storage:', this.currentConsent);
|
||||
|
||||
// Pruefen ob Consent abgelaufen
|
||||
if (this.isConsentExpired()) {
|
||||
if (isConsentExpired(this.currentConsent, this.config)) {
|
||||
this.log('Consent expired, clearing');
|
||||
this.storage.clear();
|
||||
this.currentConsent = null;
|
||||
@@ -89,7 +85,7 @@ export class ConsentManager {
|
||||
this.emit('init', this.currentConsent);
|
||||
|
||||
// Banner anzeigen falls noetig
|
||||
if (this.needsConsent()) {
|
||||
if (needsConsent(this.currentConsent, this.config)) {
|
||||
this.showBanner();
|
||||
}
|
||||
|
||||
@@ -100,9 +96,7 @@ export class ConsentManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Public API
|
||||
// ===========================================================================
|
||||
// --- Public API ---
|
||||
|
||||
/**
|
||||
* Pruefen ob Consent fuer Kategorie vorhanden
|
||||
@@ -135,7 +129,7 @@ export class ConsentManager {
|
||||
* Consent setzen
|
||||
*/
|
||||
async setConsent(input: ConsentInput): Promise<void> {
|
||||
const categories = this.normalizeConsentInput(input);
|
||||
const categories = normalizeConsentInput(input);
|
||||
|
||||
// Essential ist immer aktiv
|
||||
categories.essential = true;
|
||||
@@ -184,15 +178,7 @@ export class ConsentManager {
|
||||
* Alle Kategorien akzeptieren
|
||||
*/
|
||||
async acceptAll(): Promise<void> {
|
||||
const allCategories: ConsentCategories = {
|
||||
essential: true,
|
||||
functional: true,
|
||||
analytics: true,
|
||||
marketing: true,
|
||||
social: true,
|
||||
};
|
||||
|
||||
await this.setConsent(allCategories);
|
||||
await this.setConsent(ALL_CATEGORIES);
|
||||
this.emit('accept_all', this.currentConsent!);
|
||||
this.hideBanner();
|
||||
}
|
||||
@@ -201,15 +187,7 @@ export class ConsentManager {
|
||||
* Alle nicht-essentiellen Kategorien ablehnen
|
||||
*/
|
||||
async rejectAll(): Promise<void> {
|
||||
const minimalCategories: ConsentCategories = {
|
||||
essential: true,
|
||||
functional: false,
|
||||
analytics: false,
|
||||
marketing: false,
|
||||
social: false,
|
||||
};
|
||||
|
||||
await this.setConsent(minimalCategories);
|
||||
await this.setConsent(MINIMAL_CATEGORIES);
|
||||
this.emit('reject_all', this.currentConsent!);
|
||||
this.hideBanner();
|
||||
}
|
||||
@@ -247,52 +225,23 @@ export class ConsentManager {
|
||||
return JSON.stringify(exportData, null, 2);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Banner Control
|
||||
// ===========================================================================
|
||||
// --- Banner Control ---
|
||||
|
||||
/**
|
||||
* Pruefen ob Consent-Abfrage noetig
|
||||
*/
|
||||
needsConsent(): boolean {
|
||||
if (!this.currentConsent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isConsentExpired()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recheck nach X Tagen
|
||||
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 (new Date() > recheckDate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return needsConsent(this.currentConsent, this.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Banner anzeigen
|
||||
*/
|
||||
showBanner(): void {
|
||||
if (this.bannerVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.bannerVisible) return;
|
||||
this.bannerVisible = true;
|
||||
this.emit('banner_show', undefined);
|
||||
this.config.onBannerShow?.();
|
||||
|
||||
// Banner wird von UI-Komponente gerendert
|
||||
// Hier nur Status setzen
|
||||
this.log('Banner shown');
|
||||
}
|
||||
|
||||
@@ -300,14 +249,10 @@ export class ConsentManager {
|
||||
* Banner verstecken
|
||||
*/
|
||||
hideBanner(): void {
|
||||
if (!this.bannerVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.bannerVisible) return;
|
||||
this.bannerVisible = false;
|
||||
this.emit('banner_hide', undefined);
|
||||
this.config.onBannerHide?.();
|
||||
|
||||
this.log('Banner hidden');
|
||||
}
|
||||
|
||||
@@ -326,9 +271,7 @@ export class ConsentManager {
|
||||
return this.bannerVisible;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Event Handling
|
||||
// ===========================================================================
|
||||
// --- Event Handling ---
|
||||
|
||||
/**
|
||||
* Event-Listener registrieren
|
||||
@@ -350,35 +293,13 @@ export class ConsentManager {
|
||||
this.events.off(event, callback);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Internal Methods
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Konfiguration zusammenfuehren — delegates to the extracted helper.
|
||||
*/
|
||||
private mergeConfig(config: ConsentConfig): ConsentConfig {
|
||||
return mergeConsentConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consent-Input normalisieren
|
||||
*/
|
||||
private normalizeConsentInput(input: ConsentInput): ConsentCategories {
|
||||
if ('categories' in input && input.categories) {
|
||||
return { ...DEFAULT_CONSENT, ...input.categories };
|
||||
}
|
||||
|
||||
return { ...DEFAULT_CONSENT, ...(input as Partial<ConsentCategories>) };
|
||||
}
|
||||
// --- Internal Methods ---
|
||||
|
||||
/**
|
||||
* Consent anwenden (Skripte aktivieren/blockieren)
|
||||
*/
|
||||
private applyConsent(): void {
|
||||
if (!this.currentConsent) {
|
||||
return;
|
||||
}
|
||||
if (!this.currentConsent) return;
|
||||
|
||||
for (const [category, allowed] of Object.entries(
|
||||
this.currentConsent.categories
|
||||
@@ -391,69 +312,26 @@ export class ConsentManager {
|
||||
}
|
||||
|
||||
// Google Consent Mode aktualisieren
|
||||
this.updateGoogleConsentMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Consent Mode v2 aktualisieren — delegates to the extracted helper.
|
||||
*/
|
||||
private updateGoogleConsentMode(): void {
|
||||
if (applyGoogleConsent(this.currentConsent)) {
|
||||
this.log('Google Consent Mode updated');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pruefen ob Consent abgelaufen
|
||||
*/
|
||||
private isConsentExpired(): boolean {
|
||||
if (!this.currentConsent?.expiresAt) {
|
||||
// Fallback: Nach rememberDays ablaufen
|
||||
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 new Date() > expiryDate;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Date() > new Date(this.currentConsent.expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event emittieren
|
||||
*/
|
||||
private emit<T extends ConsentEventType>(
|
||||
event: T,
|
||||
data: ConsentEventData[T]
|
||||
): void {
|
||||
private emit<T extends ConsentEventType>(event: T, data: ConsentEventData[T]): void {
|
||||
this.events.emit(event, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fehler behandeln
|
||||
*/
|
||||
private handleError(error: Error): void {
|
||||
this.log('Error:', error);
|
||||
this.emit('error', error);
|
||||
this.config.onError?.(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug-Logging
|
||||
*/
|
||||
private log(...args: unknown[]): void {
|
||||
if (this.config.debug) {
|
||||
console.log('[ConsentSDK]', ...args);
|
||||
}
|
||||
if (this.config.debug) console.log('[ConsentSDK]', ...args);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Static Methods
|
||||
// ===========================================================================
|
||||
// --- Static Methods ---
|
||||
|
||||
/**
|
||||
* SDK-Version abrufen
|
||||
|
||||
88
consent-sdk/src/core/consent-manager-helpers.ts
Normal file
88
consent-sdk/src/core/consent-manager-helpers.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* consent-manager-helpers.ts
|
||||
*
|
||||
* Pure helper functions used by ConsentManager that have no dependency on
|
||||
* class instance state. Extracted to keep ConsentManager.ts under the
|
||||
* 350 LOC soft target.
|
||||
*/
|
||||
|
||||
import type {
|
||||
ConsentState,
|
||||
ConsentCategories,
|
||||
ConsentInput,
|
||||
ConsentConfig,
|
||||
} from '../types';
|
||||
import { DEFAULT_CONSENT } from './consent-manager-config';
|
||||
|
||||
/** All categories accepted (used by acceptAll). */
|
||||
export const ALL_CATEGORIES: ConsentCategories = {
|
||||
essential: true,
|
||||
functional: true,
|
||||
analytics: true,
|
||||
marketing: true,
|
||||
social: true,
|
||||
};
|
||||
|
||||
/** Only essential consent (used by rejectAll). */
|
||||
export const MINIMAL_CATEGORIES: ConsentCategories = {
|
||||
essential: true,
|
||||
functional: false,
|
||||
analytics: false,
|
||||
marketing: false,
|
||||
social: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalise a ConsentInput into a full ConsentCategories map.
|
||||
* Always returns a shallow copy — never mutates the input.
|
||||
*/
|
||||
export function normalizeConsentInput(input: ConsentInput): ConsentCategories {
|
||||
if ('categories' in input && input.categories) {
|
||||
return { ...DEFAULT_CONSENT, ...input.categories };
|
||||
}
|
||||
return { ...DEFAULT_CONSENT, ...(input as Partial<ConsentCategories>) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the stored consent record has passed its expiry date.
|
||||
* Falls back to `rememberDays` from config when `expiresAt` is absent.
|
||||
*/
|
||||
export function isConsentExpired(
|
||||
consent: ConsentState | null,
|
||||
config: ConsentConfig
|
||||
): boolean {
|
||||
if (!consent) return false;
|
||||
|
||||
if (!consent.expiresAt) {
|
||||
if (consent.timestamp && config.consent?.rememberDays) {
|
||||
const consentDate = new Date(consent.timestamp);
|
||||
const expiryDate = new Date(consentDate);
|
||||
expiryDate.setDate(expiryDate.getDate() + config.consent.rememberDays);
|
||||
return new Date() > expiryDate;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Date() > new Date(consent.expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the user needs to be shown the consent banner.
|
||||
*/
|
||||
export function needsConsent(
|
||||
consent: ConsentState | null,
|
||||
config: ConsentConfig
|
||||
): boolean {
|
||||
if (!consent) return true;
|
||||
|
||||
if (isConsentExpired(consent, config)) return true;
|
||||
|
||||
if (config.consent?.recheckAfterDays) {
|
||||
const consentDate = new Date(consent.timestamp);
|
||||
const recheckDate = new Date(consentDate);
|
||||
recheckDate.setDate(recheckDate.getDate() + config.consent.recheckAfterDays);
|
||||
if (new Date() > recheckDate) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user