import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; const BackendContext = createContext(null); export function useBackend() { return useContext(BackendContext); } export function BackendProvider({ children }) { const API_BASE = process.env.REACT_APP_API_BASE || ''; const [backendOnline, setBackendOnline] = useState(false); const [checking, setChecking] = useState(true); const esRef = useRef(null); const eventTargetRef = useRef(new EventTarget()); useEffect(() => { let mounted = true; const check = async () => { if (!mounted) return; setChecking(true); try { const resp = await fetch(`${API_BASE}/api/servers/health`); if (!mounted) return; setBackendOnline(!!(resp && resp.ok)); } catch (e) { if (!mounted) return; setBackendOnline(false); } finally { if (mounted) setChecking(false); } }; check(); const iv = setInterval(check, 5000); return () => { mounted = false; clearInterval(iv); }; }, [API_BASE]); // Single shared EventSource forwarded into a DOM EventTarget useEffect(() => { if (typeof window === 'undefined' || typeof EventSource === 'undefined') return; const url = `${API_BASE}/api/events`; let es = null; try { es = new EventSource(url); esRef.current = es; } catch (err) { // silently ignore return; } const forward = (type) => (e) => { try { const evt = new CustomEvent(type, { detail: e.data ? JSON.parse(e.data) : null }); eventTargetRef.current.dispatchEvent(evt); } catch (err) { // ignore parse errors } }; es.addEventListener('connected', forward('connected')); es.addEventListener('commandToggle', forward('commandToggle')); es.addEventListener('twitchUsersUpdate', forward('twitchUsersUpdate')); es.addEventListener('liveNotificationsUpdate', forward('liveNotificationsUpdate')); es.addEventListener('adminLogAdded', forward('adminLogAdded')); es.addEventListener('adminLogDeleted', forward('adminLogDeleted')); es.addEventListener('adminLogsCleared', forward('adminLogsCleared')); es.onerror = () => { // Let consumers react to backendOnline state changes instead of surfacing connection errors }; return () => { try { es && es.close(); } catch (e) {} }; }, []); // eslint-disable-line react-hooks/exhaustive-deps const forceCheck = async () => { const API_BASE2 = process.env.REACT_APP_API_BASE || ''; try { setChecking(true); const resp = await fetch(`${API_BASE2}/api/servers/health`); setBackendOnline(!!(resp && resp.ok)); } catch (e) { setBackendOnline(false); } finally { setChecking(false); } }; const value = { backendOnline, checking, eventTarget: eventTargetRef.current, forceCheck, }; return ( {children} ); } export default BackendContext;