116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
/**
|
|
* 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
|
|
*/
|
|
|
|
// Server-side implementation
|
|
export class FeatureFlagService {
|
|
private flags: Set<string>;
|
|
|
|
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
|
|
* Fetches from ${NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'}/features
|
|
* On error, returns empty flags (secure by default)
|
|
*/
|
|
static async fromAPI(): Promise<FeatureFlagService> {
|
|
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
|
|
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<string, string> }
|
|
// 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<string>;
|
|
|
|
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'
|
|
]); |