/** * FeatureFlagService - Manages feature flags for both server and client * * API-Driven Integration: * Fetches feature flags from the API endpoint GET /features * Returns empty flags on error (secure by default) * * Server: Fetches from API at ${NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'}/features * Client: Reads from session context or provides mock implementation */ import { getWebsiteApiBaseUrl } from '../config/apiBaseUrl'; // Server-side implementation export class FeatureFlagService { private flags: Set; constructor(flags?: string[]) { if (flags) { this.flags = new Set(flags); } else { // Parse from environment variable (fallback for backward compatibility) const flagsEnv = process.env.FEATURE_FLAGS; this.flags = flagsEnv ? new Set(flagsEnv.split(',').map(f => f.trim())) : new Set(); } } /** * Check if a feature flag is enabled */ isEnabled(flag: string): boolean { return this.flags.has(flag); } /** * Get all enabled flags */ getEnabledFlags(): string[] { return Array.from(this.flags); } /** * Factory method to create service by fetching from API * On error, returns empty flags (secure by default) */ static async fromAPI(): Promise { const baseUrl = getWebsiteApiBaseUrl(); const url = `${baseUrl}/features`; try { // Use next: { revalidate: 0 } for Next.js server runtime // This is equivalent to cache: 'no-store' but is the preferred Next.js convention const response = await fetch(url, { next: { revalidate: 0 }, }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); // Parse JSON { features: Record } // Enable flags whose value is 'enabled' const enabledFlags: string[] = []; if (data.features && typeof data.features === 'object') { Object.entries(data.features).forEach(([flag, value]) => { if (value === 'enabled') { enabledFlags.push(flag); } }); } return new FeatureFlagService(enabledFlags); } catch (error) { // Log error but return empty flags (secure by default) console.error('Failed to fetch feature flags from API:', error); return new FeatureFlagService([]); } } } // Client-side context interface export interface FeatureFlagContextType { isEnabled: (flag: string) => boolean; getEnabledFlags: () => string[]; } // Mock implementation for client-side when no context is available export class MockFeatureFlagService implements FeatureFlagContextType { private flags: Set; constructor(flags: string[] = []) { this.flags = new Set(flags); } isEnabled(flag: string): boolean { return this.flags.has(flag); } getEnabledFlags(): string[] { return Array.from(this.flags); } } // Default mock instance for client-side usage // Enables all features for development/demo mode export const mockFeatureFlags = new MockFeatureFlagService([ 'driver_profiles', 'team_profiles', 'wallets', 'sponsors', 'team_feature', 'alpha_features' ]);