/* SoftSins Secure API Client */ class SecureAPIClient { constructor() { this.supabase = window.supabase; this.edgeFunctionUrl = null; this.rateLimiter = new Map(); // Simple rate limiting this.init(); } async init() { // Set Supabase Edge Function URL (always available) this.edgeFunctionUrl = `https://xboptgbvoqfwpszftoqc.supabase.co/functions/v1/secure-user-operations`; console.log('๐Ÿ”’ Secure API Client initialized with Edge Function URL:', this.edgeFunctionUrl); } // ๐Ÿ”’ RATE LIMITING checkRateLimit(action, userId) { const key = `${userId}_${action}`; const now = Date.now(); const lastCall = this.rateLimiter.get(key); // Allow max 1 call per 5 seconds for create actions if (action === 'create_story' && lastCall && (now - lastCall) < 5000) { throw new Error('Rate limit exceeded. Please wait before creating another story.'); } // Allow max 1 call per 10 seconds for account deletion if (action === 'delete_account' && lastCall && (now - lastCall) < 10000) { throw new Error('Rate limit exceeded. Please wait before trying again.'); } this.rateLimiter.set(key, now); } // ๐Ÿ”’ SECURE API CALLS async makeSecureCall(action, data = {}) { try { const { data: { session } } = await this.supabase.auth.getSession(); if (!session) { throw new Error('Authentication required'); } // Rate limiting this.checkRateLimit(action, session.user.id); const response = await fetch(this.edgeFunctionUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${session.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ action: action, data: data }) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'API call failed'); } return result; } catch (error) { console.error(`๐Ÿ”’ Secure API call failed (${action}):`, error); throw error; } } // ๐Ÿ”’ SECURE STORY CREATION async createUserStory(selections, audioFileId, quizType = 'appearance', isEditMode = false) { console.log('๐Ÿ”’ Creating user story securely...'); console.log('Quiz type:', quizType); console.log('Edit mode:', isEditMode); console.log('Selections:', selections); console.log('Audio file ID:', audioFileId); // Validate input this.validateSelections(selections); const storyData = { quiz_type: quizType, is_edit_mode: isEditMode, title: this.generateTitle(selections, quizType), description: 'Personalisierte Geschichte basierend auf deinen Wรผnschen', story_name: 'WEIHNACHTEN', // Hardcoded for security first_name: selections.firstName, eye_color: selections.eyeColor || 'braun', // Default values for story quiz hair_color: selections.hairColor || 'braun', hair_length: selections.hairLength || 'mittel', audio_file_id: audioFileId, // For story quiz, also include fields at top level for Edge Function setting: selections.setting, dynamic: selections.dynamic, relationship: selections.relationship, intensity: selections.intensity, quiz_preferences: { ...selections, quiz_type: quizType, generated_at: new Date().toISOString() } }; console.log('Story data to send:', storyData); return await this.makeSecureCall('create_story', storyData); } // ๐Ÿ”’ SECURE STATUS UPDATES async updateStoryStatus(audioFileId, field, value) { console.log(`๐Ÿ”’ Updating story status securely: ${field} = ${value}`); return await this.makeSecureCall('update_story_status', { audioFileId, field, value }); } // ๐Ÿ”’ SECURE ACCOUNT DELETION async deleteUserAccount() { console.log('๐Ÿ”’ Deleting user account securely...'); const confirmed = confirm( 'ACHTUNG: Dies wird dein Konto und ALLE deine Daten permanent lรถschen. ' + 'Diese Aktion kann NICHT rรผckgรคngig gemacht werden. Bist du sicher?' ); if (!confirmed) { throw new Error('Account deletion cancelled by user'); } return await this.makeSecureCall('delete_account'); } // Helper methods generateTitle(selections, quizType) { if (quizType === 'appearance') { return `${selections.firstName}s SoftSins sinnliche Weihnachtsgeschichte`; } else { return 'Deine personalisierte sinnliche Geschichte'; } } // Validate data before sending validateSelections(selections) { // Always require firstName if (!selections.firstName) { throw new Error('firstName is required'); } // Sanitize first name if (selections.firstName.length > 50) { throw new Error('First name too long'); } // For appearance quiz, validate appearance fields if (selections.eyeColor || selections.hairColor || selections.hairLength) { const validEyeColors = ['braun', 'blau', 'gruen', 'grau', 'bunt', 'andere']; const validHairColors = ['blond', 'braun', 'schwarz', 'rot', 'grau', 'bunt', 'andere']; const validHairLengths = ['kurz', 'mittel', 'lang']; if (selections.eyeColor && !validEyeColors.includes(selections.eyeColor)) { throw new Error('Invalid eye color'); } if (selections.hairColor && !validHairColors.includes(selections.hairColor)) { throw new Error('Invalid hair color'); } if (selections.hairLength && !validHairLengths.includes(selections.hairLength)) { throw new Error('Invalid hair length'); } } return true; } } // ๐Ÿ”’ GLOBAL SECURE API CLIENT window.secureAPI = new SecureAPIClient(); console.log('๐Ÿ”’ Secure API Client loaded');