learning_ai_common_plat/packages/react-native-platform-sdk/src/broadcasts/index.ts
Saravana Achu Mac e174335a9e fix(rn-platform-sdk): align providers with platform-service APIs
- Feature flags: GET /flags/poll legacy { flags } + optional userId
- Kill switch: GET /settings/kill-switch, map message to reason
- Broadcasts: GET /broadcasts, POST dismiss; map server message shape
- Surveys: GET /surveys/active; submit via start/response/complete
- Auth: register(); login/register bodies include productId
- Telemetry: map queued events to TelemetryEventSchema; RN Platform import
- prepare script runs tsc on install

Made-with: Cursor
2026-03-30 01:12:18 -07:00

126 lines
3.4 KiB
TypeScript

/**
* 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<void>;
}
const BroadcastContext = createContext<BroadcastContextType | null>(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<InAppMessage[]>([]);
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;
}