// Hält den EINEN MessagePort zum Kern — bewusst außerhalb von React: // Die Port-Übergabe vom Haupt passiert genau einmal je Fenster-Ladung; // React-Re-Mounts (StrictMode im Dev, HMR) dürfen sie nicht verlieren. // // R2 gilt auch hier: Der letzte bekannte Kern-Stand bleibt stehen und wird // jedem neuen Abonnenten sofort wiedergegeben — er wird nie auf »leer« // zurückgesetzt, nur weil gerade keine Nachricht kam. import { istKernNachricht, type KernNachricht } from '../gemeinsam/nachrichten' export interface Verbindung { verbunden: boolean kern: Extract | null pongLatenzMs: number | null } type Horcher = (stand: Verbindung) => void const stand: Verbindung = { verbunden: false, kern: null, pongLatenzMs: null } const horcher = new Set() let gestartet = false function melden(): void { for (const h of horcher) h({ ...stand }) } function portAnnehmen(port: MessagePort): void { stand.verbunden = true port.onmessage = (ereignis: MessageEvent) => { const nachricht: unknown = ereignis.data if (!istKernNachricht(nachricht)) return if (nachricht.art === 'kern-bereit') { stand.kern = nachricht } else if (nachricht.art === 'pong') { stand.pongLatenzMs = Math.max(0, Date.now() - nachricht.zeit) // Der Beweis für den Smoke-Lauf: Ping ging hin, Pong kam zurück. window.rippy.melden('kern-pong') } melden() } port.start() port.postMessage({ art: 'ping', zeit: Date.now() }) melden() } /** Idempotent — der erste Aufruf registriert den Horcher und meldet der * Brücke, dass gepufferte Ports jetzt zugestellt werden können. */ export function verbindungStarten(): void { if (gestartet) return gestartet = true window.addEventListener('message', (ereignis: MessageEvent) => { const daten: unknown = ereignis.data if ( typeof daten === 'object' && daten !== null && (daten as Record).art === 'kern-port' && ereignis.ports.length > 0 ) { portAnnehmen(ereignis.ports[0]) } }) window.rippy.bereit() } export function aufVerbindung(h: Horcher): () => void { horcher.add(h) h({ ...stand }) return () => horcher.delete(h) }