// ClawdDotNet — Ereignisstrom (Server-Sent Events) // // Hört auf /events und verteilt anwendungsintern Ereignisse (chat, step, run, instance, status). import { holeToken } from "./api.js"; let strom = null; const abonnenten = new Map(); const verbindungsStatusCallbacks = new Set(); let verbunden = false; /** * Registriert einen Rückruf für Änderungen des Verbindungsstatus (offen / getrennt). * @param {(status: boolean) => void} callback */ export function beiVerbindungsStatus(callback) { verbindungsStatusCallbacks.add(callback); callback(verbunden); return () => verbindungsStatusCallbacks.delete(callback); } function meldeStatus(istOffen) { if (verbunden === istOffen) return; verbunden = istOffen; for (const cb of verbindungsStatusCallbacks) { try { cb(verbunden); } catch (err) { console.error("Fehler im Verbindungsstatus-Callback:", err); } } } /** * Abonniert ein bestimmtes Server-Ereignis. * @param {string} ereignis Name des Ereignisses ("chat", "step", "run", "instance", "status") * @param {(daten: any) => void} callback * @returns {() => void} Abmeldefunktion */ export function abonnieren(ereignis, callback) { if (!abonnenten.has(ereignis)) { abonnenten.set(ereignis, new Set()); } abonnenten.get(ereignis).add(callback); return () => { const s = abonnenten.get(ereignis); if (s) { s.delete(callback); if (s.size === 0) abonnenten.delete(ereignis); } }; } function verteileEreignis(ereignis, rohdaten) { const gruppe = abonnenten.get(ereignis); if (!gruppe || gruppe.size === 0) return; let daten = rohdaten; try { daten = JSON.parse(rohdaten); } catch { // Falls Rohdaten kein JSON waren, unverändert übergeben } for (const cb of gruppe) { try { cb(daten); } catch (err) { console.error(`Fehler bei Verarbeitung von ${ereignis}:`, err); } } } /** * Startet den Ereignisstrom, falls ein Token vorliegt. */ export function verbindeStrom() { trenneStrom(); const token = holeToken(); if (!token) return; // EventSource kann keine Header setzen: Token wird über die Query übergeben (siehe TokenGate.cs) strom = new EventSource(`/events?token=${encodeURIComponent(token)}`); strom.onopen = () => { meldeStatus(true); }; // EventSource verbindet automatisch wieder bei Abbruch. strom.onerror = () => { meldeStatus(false); }; const bekannteEreignisse = ["chat", "step", "run", "instance", "status", "activity"]; for (const art of bekannteEreignisse) { strom.addEventListener(art, (e) => verteileEreignis(art, e.data)); } } /** * Schließt die bestehende Verbindung zum Ereignisstrom. */ export function trenneStrom() { if (strom) { strom.close(); strom = null; } meldeStatus(false); } /** * Gibt an, ob der Ereignisstrom aktuell aktiv verbunden ist. * @returns {boolean} */ export function istVerbunden() { return verbunden; }