From 3455e20a9603ebda1076a70611cbc6ece5857144 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 7 Mar 2026 15:05:41 +0100 Subject: [PATCH] Feature: Live Stream-Liste + Toast Notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - stream_available/stream_ended WS-Events verarbeiten - WS sofort beim Mount verbinden (nicht nur beim Broadcasting) - WS reconnect immer aktiv (nicht nur bei aktivem Stream) - Toast Notifications: neuer Stream, Update verfügbar/bereit - Notification Permission beim App-Start anfragen Co-Authored-By: Claude Opus 4.6 --- web/src/App.tsx | 21 ++++++++++-- web/src/plugins/streaming/StreamingTab.tsx | 38 ++++++++++++++++++---- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 6489a8b..0ccfabc 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -37,6 +37,13 @@ export default function App() { const [pluginData, setPluginData] = useState>({}); const eventSourceRef = useRef(null); + // Request notification permission + useEffect(() => { + if ('Notification' in window && Notification.permission === 'default') { + Notification.requestPermission(); + } + }, []); + // Fetch plugin list useEffect(() => { fetch('/api/plugins') @@ -98,8 +105,18 @@ export default function App() { useEffect(() => { const api = (window as any).electronAPI; if (!api?.onUpdateAvailable) return; - api.onUpdateAvailable(() => setUpdateState('downloading')); - api.onUpdateReady(() => setUpdateState('ready')); + api.onUpdateAvailable(() => { + setUpdateState('downloading'); + if (Notification.permission === 'granted') { + new Notification('Gaming Hub Update', { body: 'Ein Update wird heruntergeladen...' }); + } + }); + api.onUpdateReady(() => { + setUpdateState('ready'); + if (Notification.permission === 'granted') { + new Notification('Gaming Hub Update bereit', { body: 'Klicke OK um das Update zu installieren.' }); + } + }); api.onUpdateNotAvailable?.(() => setUpdateState('uptodate')); api.onUpdateError?.(() => setUpdateState('error')); }, []); diff --git a/web/src/plugins/streaming/StreamingTab.tsx b/web/src/plugins/streaming/StreamingTab.tsx index 66aa386..56493ff 100644 --- a/web/src/plugins/streaming/StreamingTab.tsx +++ b/web/src/plugins/streaming/StreamingTab.tsx @@ -165,9 +165,28 @@ export default function StreamingTab({ data }: { data: any }) { break; case 'stream_available': + setStreams(prev => { + if (prev.some(s => s.id === msg.streamId)) return prev; + return [...prev, { + id: msg.streamId, + broadcasterName: msg.broadcasterName, + title: msg.title, + startedAt: new Date().toISOString(), + viewerCount: 0, + hasPassword: true, + }]; + }); + // Toast notification for new stream + if (Notification.permission === 'granted') { + new Notification('Neuer Stream', { + body: `${msg.broadcasterName} streamt: ${msg.title}`, + icon: '/assets/icon.png', + }); + } break; case 'stream_ended': + setStreams(prev => prev.filter(s => s.id !== msg.streamId)); if (viewingRef.current?.streamId === msg.streamId) { cleanupViewer(); setViewing(null); @@ -303,17 +322,24 @@ export default function StreamingTab({ data }: { data: any }) { ws.onclose = () => { wsRef.current = null; - if (isBroadcastingRef.current || viewingRef.current) { - reconnectTimerRef.current = setTimeout(() => { - reconnectDelayRef.current = Math.min(reconnectDelayRef.current * 2, 10000); - connectWs(); - }, reconnectDelayRef.current); - } + // Always reconnect to keep stream list in sync + reconnectTimerRef.current = setTimeout(() => { + reconnectDelayRef.current = Math.min(reconnectDelayRef.current * 2, 10000); + connectWs(); + }, reconnectDelayRef.current); }; ws.onerror = () => { ws.close(); }; }, []); + // ── Connect WS on mount for live stream updates ── + useEffect(() => { + connectWs(); + return () => { + if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current); + }; + }, [connectWs]); + // ── Start broadcasting ── const startBroadcast = useCallback(async () => { if (!userName.trim()) { setError('Bitte gib einen Namen ein.'); return; }