/** * Feature Flags Manager - Frontend * Handles feature flag state and synchronization with backend */ class FeatureFlagsManager { constructor() { this.flags = {}; this.localStorageKey = 'crypto_monitor_feature_flags'; this.apiEndpoint = '/api/feature-flags'; this.listeners = []; } /** * Initialize feature flags from backend and localStorage */ async init() { // Load from localStorage first (for offline/fast access) this.loadFromLocalStorage(); // Sync with backend await this.syncWithBackend(); // Set up periodic sync (every 30 seconds) setInterval(() => this.syncWithBackend(), 30000); return this.flags; } /** * Load flags from localStorage */ loadFromLocalStorage() { try { const stored = localStorage.getItem(this.localStorageKey); if (stored) { const data = JSON.parse(stored); this.flags = data.flags || {}; console.log('[FeatureFlags] Loaded from localStorage:', this.flags); } } catch (error) { console.error('[FeatureFlags] Error loading from localStorage:', error); } } /** * Save flags to localStorage */ saveToLocalStorage() { try { const data = { flags: this.flags, updated_at: new Date().toISOString() }; localStorage.setItem(this.localStorageKey, JSON.stringify(data)); console.log('[FeatureFlags] Saved to localStorage'); } catch (error) { console.error('[FeatureFlags] Error saving to localStorage:', error); } } /** * Sync with backend */ async syncWithBackend() { try { const response = await fetch(this.apiEndpoint); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); this.flags = data.flags || {}; this.saveToLocalStorage(); this.notifyListeners(); console.log('[FeatureFlags] Synced with backend:', this.flags); return this.flags; } catch (error) { console.error('[FeatureFlags] Error syncing with backend:', error); // Fall back to localStorage return this.flags; } } /** * Check if a feature is enabled */ isEnabled(flagName) { return this.flags[flagName] === true; } /** * Get all flags */ getAll() { return { ...this.flags }; } /** * Set a single flag */ async setFlag(flagName, value) { try { const response = await fetch(`${this.apiEndpoint}/${flagName}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ flag_name: flagName, value: value }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.success) { this.flags[flagName] = value; this.saveToLocalStorage(); this.notifyListeners(); console.log(`[FeatureFlags] Set ${flagName} = ${value}`); return true; } return false; } catch (error) { console.error(`[FeatureFlags] Error setting flag ${flagName}:`, error); return false; } } /** * Update multiple flags */ async updateFlags(updates) { try { const response = await fetch(this.apiEndpoint, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ flags: updates }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.success) { this.flags = data.flags; this.saveToLocalStorage(); this.notifyListeners(); console.log('[FeatureFlags] Updated flags:', updates); return true; } return false; } catch (error) { console.error('[FeatureFlags] Error updating flags:', error); return false; } } /** * Reset to defaults */ async resetToDefaults() { try { const response = await fetch(`${this.apiEndpoint}/reset`, { method: 'POST' }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.success) { this.flags = data.flags; this.saveToLocalStorage(); this.notifyListeners(); console.log('[FeatureFlags] Reset to defaults'); return true; } return false; } catch (error) { console.error('[FeatureFlags] Error resetting flags:', error); return false; } } /** * Add change listener */ onChange(callback) { this.listeners.push(callback); return () => { const index = this.listeners.indexOf(callback); if (index > -1) { this.listeners.splice(index, 1); } }; } /** * Notify all listeners of changes */ notifyListeners() { this.listeners.forEach(callback => { try { callback(this.flags); } catch (error) { console.error('[FeatureFlags] Error in listener:', error); } }); } /** * Render feature flags UI */ renderUI(containerId) { const container = document.getElementById(containerId); if (!container) { console.error(`[FeatureFlags] Container #${containerId} not found`); return; } const flagDescriptions = { enableWhaleTracking: 'Show whale transaction tracking', enableMarketOverview: 'Display market overview dashboard', enableFearGreedIndex: 'Show Fear & Greed sentiment index', enableNewsFeed: 'Display cryptocurrency news feed', enableSentimentAnalysis: 'Enable sentiment analysis features', enableMlPredictions: 'Show ML-powered price predictions', enableProxyAutoMode: 'Automatic proxy for failing APIs', enableDefiProtocols: 'Display DeFi protocol data', enableTrendingCoins: 'Show trending cryptocurrencies', enableGlobalStats: 'Display global market statistics', enableProviderRotation: 'Enable provider rotation system', enableWebSocketStreaming: 'Real-time WebSocket updates', enableDatabaseLogging: 'Log provider health to database', enableRealTimeAlerts: 'Show real-time alert notifications', enableAdvancedCharts: 'Display advanced charting', enableExportFeatures: 'Enable data export functions', enableCustomProviders: 'Allow custom API providers', enablePoolManagement: 'Enable provider pool management', enableHFIntegration: 'HuggingFace model integration' }; let html = '
'; html += '

Feature Flags

'; html += '
'; Object.keys(this.flags).forEach(flagName => { const enabled = this.flags[flagName]; const description = flagDescriptions[flagName] || flagName; html += `
${enabled ? '✓ Enabled' : '✗ Disabled'}
`; }); html += '
'; html += '
'; html += ''; html += '
'; html += '
'; container.innerHTML = html; // Add event listeners container.querySelectorAll('.feature-flag-toggle').forEach(toggle => { toggle.addEventListener('change', async (e) => { const flagName = e.target.dataset.flag; const value = e.target.checked; await this.setFlag(flagName, value); }); }); const resetBtn = container.querySelector('#ff-reset-btn'); if (resetBtn) { resetBtn.addEventListener('click', async () => { if (confirm('Reset all feature flags to defaults?')) { await this.resetToDefaults(); this.renderUI(containerId); } }); } // Listen for changes and re-render this.onChange(() => { this.renderUI(containerId); }); } } // Global instance window.featureFlagsManager = new FeatureFlagsManager(); // Auto-initialize on DOMContentLoaded document.addEventListener('DOMContentLoaded', () => { window.featureFlagsManager.init().then(() => { console.log('[FeatureFlags] Initialized'); }); });