Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
'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<string, (data: unknown) => 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<string>
|
|
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
|
|
}
|