/** * Feature Flags module — React context + hook for feature flags in React Native apps. */ import React, { createContext, useContext, useState, useCallback, useEffect } from 'react'; import type { PlatformSDK } from '../core.js'; export interface FeatureFlag { key: string; enabled: boolean; value?: unknown; } interface FeatureFlagContextType { flags: Map; isEnabled: (key: string) => boolean; getValue: (key: string, fallback: T) => T; refresh: () => Promise; } const FeatureFlagContext = createContext(null); export function useFeatureFlags(): FeatureFlagContextType { const ctx = useContext(FeatureFlagContext); if (!ctx) throw new Error('useFeatureFlags must be used within a FeatureFlagProvider'); return ctx; } interface FeatureFlagProviderProps { sdk: PlatformSDK; /** Poll interval in ms (default: 60000) */ pollInterval?: number; children: React.ReactNode; } export function FeatureFlagProvider({ sdk, pollInterval = 60_000, children, }: FeatureFlagProviderProps): React.JSX.Element { const [flags, setFlags] = useState>(new Map()); const refresh = useCallback(async () => { try { const res = await sdk.fetch('/api/flags/poll'); if (res.ok) { const data = (await res.json()) as FeatureFlag[]; const map = new Map(); for (const flag of data) { map.set(flag.key, flag); } setFlags(map); } } catch { /* fail-open: keep existing flags */ } }, [sdk]); const isEnabled = useCallback( (key: string): boolean => { return flags.get(key)?.enabled ?? false; }, [flags] ); const getValue = useCallback( (key: string, fallback: T): T => { const flag = flags.get(key); if (!flag?.enabled) return fallback; return (flag.value as T) ?? fallback; }, [flags] ); useEffect(() => { refresh(); const id = setInterval(refresh, pollInterval); return () => clearInterval(id); }, [refresh, pollInterval]); const value: FeatureFlagContextType = { flags, isEnabled, getValue, refresh }; return React.createElement(FeatureFlagContext.Provider, { value }, children); }