/** * ConsentAPI - Kommunikation mit dem Consent-Backend * * Sendet Consent-Entscheidungen an das Backend zur * revisionssicheren Speicherung. */ import type { ConsentConfig, ConsentState, ConsentAPIResponse, SiteConfigResponse, } from '../types'; /** * 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 */ export class ConsentAPI { private config: ConsentConfig; private baseUrl: string; constructor(config: ConsentConfig) { this.config = config; this.baseUrl = config.apiEndpoint.replace(/\/$/, ''); } /** * Consent speichern */ async saveConsent(request: SaveConsentRequest): Promise { 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: string, deviceFingerprint: string ): Promise { 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: string): Promise { 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: string): Promise { 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: string): Promise { 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 */ private async fetch( path: string, options: RequestInit = {} ): Promise { const url = `${this.baseUrl}${path}`; const headers: HeadersInit = { '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) */ private getSignatureHeaders(): Record { const timestamp = Math.floor(Date.now() / 1000).toString(); // Einfache Signatur fuer Client-Side // In Produktion: Server-seitige Validierung mit echtem HMAC const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`); return { 'X-Consent-Timestamp': timestamp, 'X-Consent-Signature': `sha256=${signature}`, }; } /** * Einfache Hash-Funktion (djb2) */ private simpleHash(str: string): string { let hash = 5381; for (let i = 0; i < str.length; i++) { hash = (hash * 33) ^ str.charCodeAt(i); } return (hash >>> 0).toString(16); } /** * Debug-Logging */ private log(...args: unknown[]): void { if (this.config.debug) { console.log('[ConsentAPI]', ...args); } } } export default ConsentAPI;