feat(web): add feature flag client with platform-service polling
This commit is contained in:
parent
09ded150f4
commit
95f71f4625
@ -4,6 +4,7 @@ import { useEffect } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { AuthProvider } from '@/lib/auth-context';
|
||||
import { initTelemetry, trackPageView } from '@/lib/telemetry';
|
||||
import { initFeatureFlags } from '@/lib/feature-flags';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
@ -11,6 +12,7 @@ export function Providers({ children }: { children: ReactNode }) {
|
||||
|
||||
useEffect(() => {
|
||||
initTelemetry();
|
||||
initFeatureFlags({ platform: 'web' });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
70
web/src/lib/feature-flags.ts
Normal file
70
web/src/lib/feature-flags.ts
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Feature flag client — polls platform-service /flags/poll endpoint.
|
||||
*
|
||||
* Flags are fetched on init and cached in memory. Re-polls on a configurable
|
||||
* interval (default 5 min). Consumers call `isEnabled('flag_key')`.
|
||||
*
|
||||
* Privacy: sends userId + platform only. No PII.
|
||||
*/
|
||||
|
||||
import { PRODUCT_ID } from './auth-api';
|
||||
|
||||
const PLATFORM_URL = process.env.NEXT_PUBLIC_PLATFORM_SERVICE_URL ?? 'https://api.chronomind.app';
|
||||
const POLL_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
let _flags: Record<string, boolean> = {};
|
||||
let _initialized = false;
|
||||
let _intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
interface PollParams {
|
||||
userId?: string;
|
||||
platform?: string;
|
||||
}
|
||||
|
||||
async function fetchFlags(params: PollParams): Promise<Record<string, boolean>> {
|
||||
try {
|
||||
const qs = new URLSearchParams();
|
||||
if (params.userId) qs.set('userId', params.userId);
|
||||
if (params.platform) qs.set('platform', params.platform);
|
||||
const res = await fetch(`${PLATFORM_URL}/api/flags/poll?${qs.toString()}`, {
|
||||
headers: { 'x-product-id': PRODUCT_ID },
|
||||
});
|
||||
if (!res.ok) return _flags;
|
||||
const data = await res.json();
|
||||
return (data as { flags: Record<string, boolean> }).flags ?? {};
|
||||
} catch {
|
||||
return _flags;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the flag client. Call once on app startup.
|
||||
* Fetches flags immediately and starts polling.
|
||||
*/
|
||||
export async function initFeatureFlags(params: PollParams = {}): Promise<void> {
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
const pollParams: PollParams = { platform: 'web', ...params };
|
||||
_flags = await fetchFlags(pollParams);
|
||||
_intervalId = setInterval(async () => {
|
||||
_flags = await fetchFlags(pollParams);
|
||||
}, POLL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/** Check if a feature flag is enabled. Returns false if not found or not initialized. */
|
||||
export function isEnabled(key: string): boolean {
|
||||
return _flags[key] === true;
|
||||
}
|
||||
|
||||
/** Get all currently cached flags. */
|
||||
export function getAllFlags(): Readonly<Record<string, boolean>> {
|
||||
return _flags;
|
||||
}
|
||||
|
||||
/** Stop polling and reset state. Useful for tests. */
|
||||
export function resetFeatureFlags(): void {
|
||||
if (_intervalId) clearInterval(_intervalId);
|
||||
_intervalId = null;
|
||||
_flags = {};
|
||||
_initialized = false;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user