Files
breakpilot-compliance/consent-sdk/src/core/ConsentAPI.ts
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

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

213 lines
4.8 KiB
TypeScript

/**
* 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<ConsentAPIResponse> {
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<ConsentState | null> {
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<void> {
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<SiteConfigResponse> {
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<unknown> {
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<Response> {
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<string, string> {
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;