'use client'; import { useEffect, useState, useRef } from 'react'; import type { ChatMessage } from '@shared/chat'; import { proxyImageUrl } from '../../lib/imageProxy'; const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? 'http://localhost:4100'; type SelectionPayload = { message: ChatMessage | null; }; export default function OverlayPage() { const [message, setMessage] = useState(null); const [displayMessage, setDisplayMessage] = useState(null); const [connected, setConnected] = useState(false); const [fadingOut, setFadingOut] = useState(false); const [switching, setSwitching] = useState(false); const connectionRef = useRef(null); const switchTimeoutRef = useRef(null); useEffect(() => { // Prevent multiple connections if (connectionRef.current) { console.log('[Overlay] SSE already connected'); return; } console.log('[Overlay] Creating SSE connection'); const source = new EventSource(`${BACKEND_URL}/overlay/stream`); connectionRef.current = source; const onSelection = (event: MessageEvent) => { try { const payload: SelectionPayload = JSON.parse(event.data); setMessage(payload.message); setConnected(true); } catch (error) { console.error('overlay: failed to parse payload', error); } }; source.addEventListener('selection', onSelection as EventListener); source.addEventListener('heartbeat', () => setConnected(true)); source.onerror = () => setConnected(false); return () => { console.log('[Overlay] Closing SSE connection'); source.removeEventListener('selection', onSelection as EventListener); source.close(); connectionRef.current = null; if (switchTimeoutRef.current) { clearTimeout(switchTimeoutRef.current); } }; }, []); // Empty dependency array - only connect once // Handle message transitions useEffect(() => { // Clear any pending timeout if (switchTimeoutRef.current) { clearTimeout(switchTimeoutRef.current); switchTimeoutRef.current = null; } if (message === null && displayMessage !== null) { // Deselecting - fade out setFadingOut(true); setSwitching(false); switchTimeoutRef.current = setTimeout(() => { setDisplayMessage(null); setFadingOut(false); }, 300); } else if (message !== null && displayMessage !== null && message.id !== displayMessage.id) { // Switching between messages - fade out then fade in setSwitching(true); setFadingOut(true); switchTimeoutRef.current = setTimeout(() => { setDisplayMessage(message); setFadingOut(false); setSwitching(false); }, 300); } else if (message !== null && displayMessage === null) { // First message - just show it setDisplayMessage(message); setFadingOut(false); setSwitching(false); } }, [message, displayMessage]); return (
{displayMessage ? (
{displayMessage.superChat ? ( <>
{displayMessage.authorPhoto && ( {displayMessage.author} )} {displayMessage.author} - {displayMessage.superChat.currency}{displayMessage.superChat.currency ? ' ' : ''}{displayMessage.superChat.amount}
{displayMessage.runs?.length ? (

{displayMessage.runs.map((r, i) => r.emojiUrl ? ( {r.emojiAlt ) : ( {r.text} ) )}

) : displayMessage.text && displayMessage.text !== 'N/A' && (

{displayMessage.text}

)} {displayMessage.superChat.stickerUrl && (
{displayMessage.superChat.stickerAlt { // Hide image on error e.currentTarget.style.display = 'none'; }} />
)} ) : (displayMessage.membershipGift || displayMessage.membershipGiftPurchase) ? ( <>
{displayMessage.authorPhoto && ( {displayMessage.author} )}
{displayMessage.author} - {displayMessage.membershipGiftPurchase && displayMessage.giftCount ? `Sent ${displayMessage.giftCount} Gift Membership${displayMessage.giftCount > 1 ? 's' : ''}` : displayMessage.membershipLevel || 'New Member'}
{displayMessage.runs?.length ? (

{displayMessage.runs.map((r, i) => r.emojiUrl ? ( {r.emojiAlt ) : ( {r.text} ) )}

) : displayMessage.text && displayMessage.text !== 'N/A' && (

{displayMessage.text}

)} ) : ( <>
{displayMessage.authorPhoto && ( {displayMessage.author} )}
{displayMessage.author} {displayMessage.leaderboardRank && ( 👑 #{displayMessage.leaderboardRank} )} {displayMessage.badges && displayMessage.badges.map((badge, i) => ( badge.imageUrl ? ( {badge.label} ) : ( {badge.type === 'moderator' && '🛡️'} {badge.type === 'member' && '⭐'} {badge.type === 'verified' && '✓'} ) ))}
{displayMessage.runs?.length ? (

{displayMessage.runs.map((r, i) => r.emojiUrl ? ( {r.emojiAlt ) : ( {r.text} ) )}

) : displayMessage.text && displayMessage.text !== 'N/A' && (

{displayMessage.text}

)} )}
) : null}
); }