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>
This commit is contained in:
212
consent-sdk/src/core/ConsentAPI.ts
Normal file
212
consent-sdk/src/core/ConsentAPI.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user