/** * Broadcasts module — React context + hook for in-app messages in React Native apps. */ import React, { createContext, useContext, useState, useCallback, useEffect } from 'react'; import type { PlatformSDK } from '../core.js'; export interface InAppMessage { id: string; title: string; body: string; type: 'info' | 'warning' | 'critical'; action?: { label: string; url: string }; dismissible: boolean; expiresAt?: string; } interface BroadcastContextType { messages: InAppMessage[]; dismiss: (id: string) => void; refresh: () => Promise; } const BroadcastContext = createContext(null); export function useBroadcasts(): BroadcastContextType { const ctx = useContext(BroadcastContext); if (!ctx) throw new Error('useBroadcasts must be used within a BroadcastProvider'); return ctx; } interface BroadcastProviderProps { sdk: PlatformSDK; /** Poll interval in ms (default: 300000 = 5 min) */ pollInterval?: number; children: React.ReactNode; } export function BroadcastProvider({ sdk, pollInterval = 300_000, children, }: BroadcastProviderProps): React.JSX.Element { const [messages, setMessages] = useState([]); const refresh = useCallback(async () => { try { const res = await sdk.fetch('/broadcasts'); if (!res.ok) return; const data = (await res.json()) as { messages?: Array<{ id: string; title: string; body: string; ctaText?: string; ctaUrl?: string; priority?: 'low' | 'normal' | 'high' | 'urgent'; dismissible?: boolean; expiresAt?: string; }>; }; const raw = data.messages ?? []; const mapped: InAppMessage[] = raw.map(m => ({ id: m.id, title: m.title, body: m.body, type: m.priority === 'urgent' ? 'critical' : m.priority === 'high' ? 'warning' : 'info', action: m.ctaText && m.ctaUrl ? { label: m.ctaText, url: m.ctaUrl } : undefined, dismissible: m.dismissible !== false, expiresAt: m.expiresAt, })); setMessages(mapped); } catch { /* silent */ } }, [sdk]); const dismiss = useCallback( (id: string) => { setMessages(prev => prev.filter(m => m.id !== id)); sdk.fetch(`/broadcasts/${id}/dismiss`, { method: 'POST' }).catch(() => {}); }, [sdk] ); useEffect(() => { refresh(); const id = setInterval(refresh, pollInterval); return () => clearInterval(id); }, [refresh, pollInterval]); const value: BroadcastContextType = { messages, dismiss, refresh }; return React.createElement(BroadcastContext.Provider, { value }, children); } // MARK: - UI Components interface InAppMessageBannerProps { message: InAppMessage; onDismiss: () => void; } /** * Placeholder banner component — product apps should implement their own * styled version using this as a reference. Returns null (render-only hook). */ export function InAppMessageBanner(_props: InAppMessageBannerProps): React.JSX.Element | null { // Product apps implement their own styled component return null; } interface BroadcastModalProps { message: InAppMessage | null; onDismiss: () => void; } /** * Placeholder modal component — product apps should implement their own * styled version. Returns null. */ export function BroadcastModal(_props: BroadcastModalProps): React.JSX.Element | null { return null; }