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 { usePathname } from 'next/navigation';
|
||||||
import { AuthProvider } from '@/lib/auth-context';
|
import { AuthProvider } from '@/lib/auth-context';
|
||||||
import { initTelemetry, trackPageView } from '@/lib/telemetry';
|
import { initTelemetry, trackPageView } from '@/lib/telemetry';
|
||||||
|
import { initFeatureFlags } from '@/lib/feature-flags';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
export function Providers({ children }: { children: ReactNode }) {
|
export function Providers({ children }: { children: ReactNode }) {
|
||||||
@ -11,6 +12,7 @@ export function Providers({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initTelemetry();
|
initTelemetry();
|
||||||
|
initFeatureFlags({ platform: 'web' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
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