'use client' import { useEffect, useRef } from 'react' /** * Eine Verbindung zum Ereignisstrom, so lange die Ansicht offen ist. * * `EventSource` baut nach einem Abriss von selbst wieder auf und schickt dabei * die zuletzt gesehene Kennung mit, sodass der Server nachliefern kann. Das * Aufräumen bei Tabwechsel übernimmt der Browser: eine Verbindung ohne * Verkehr kostet nichts. * * Die Rückrufe stehen in einem Ref, damit ein neu erzeugter Handler nicht die * ganze Verbindung neu aufbaut - sonst reisst der Strom bei jedem Tastendruck * im Eingabefeld ab. */ export type StreamHandlers = Record void> export function useStream(topics: string[], handlers: StreamHandlers): void { const aktuell = useRef(handlers) aktuell.current = handlers // Die Themen als Zeichenkette, damit ein neues Array mit gleichem Inhalt // keine neue Verbindung auslöst. const schluessel = topics.filter(Boolean).sort().join(',') useEffect(() => { if (!schluessel) return const quelle = new EventSource(`/api/stream?topics=${encodeURIComponent(schluessel)}`) const angemeldet: [string, EventListener][] = [] for (const name of Object.keys(aktuell.current)) { const listener: EventListener = (event) => { const nachricht = event as MessageEvent try { aktuell.current[name]?.(JSON.parse(nachricht.data)) } catch { // Kaputte Nutzlast ignorieren; der nächste Seitenaufbau hat den Stand. } } quelle.addEventListener(name, listener) angemeldet.push([name, listener]) } // Der Server meldet einen Entzug ausdrücklich, statt die Verbindung nur // fallen zu lassen: `EventSource` baut sonst sofort wieder auf, und man // arbeitet auf einer Seite weiter, für die man keine Rechte mehr hat. // Neu laden führt in die Anmeldung - dorthin, wo man jetzt hingehört. const entzogen: EventListener = () => { quelle.close() window.location.reload() } quelle.addEventListener('revoked', entzogen) angemeldet.push(['revoked', entzogen]) return () => { for (const [name, listener] of angemeldet) quelle.removeEventListener(name, listener) quelle.close() } // Absichtlich nur an den Themen: die Rückrufe hängen im Ref. }, [schluessel]) } export type ScanEvent = { id: string scanStatus: 'PENDING' | 'CLEAN' | 'BLOCKED' | 'FAILED' scanMessage: string | null mimeType: string sizeBytes: number blurhash: string | null width: number | null height: number | null hasThumb: boolean }