toes/src/tools/events.ts

60 lines
1.3 KiB
TypeScript

import type { ToesEvent, ToesEventType } from '../shared/events'
export type { ToesEvent, ToesEventType }
type EventCallback = (event: ToesEvent) => void
interface Listener {
types: ToesEventType[]
callback: EventCallback
}
const _listeners = new Set<Listener>()
let _source: EventSource | undefined
function ensureConnection() {
if (_source) return
const url = `${process.env.TOES_URL}/api/events/stream`
_source = new EventSource(url)
_source.onerror = () => {
if (_source?.readyState === EventSource.CLOSED) {
console.warn('[toes] Event stream closed unexpectedly')
_source = undefined
}
}
_source.onmessage = (e) => {
try {
const event: ToesEvent = JSON.parse(e.data)
_listeners.forEach(l => {
if (l.types.includes(event.type)) l.callback(event)
})
} catch {
// Ignore malformed events
}
}
}
function closeConnection() {
if (_source) {
_source.close()
_source = undefined
}
}
export function on(type: ToesEventType | ToesEventType[], callback: EventCallback): () => void {
const listener: Listener = {
types: Array.isArray(type) ? type : [type],
callback,
}
_listeners.add(listener)
ensureConnection()
return () => {
_listeners.delete(listener)
if (_listeners.size === 0) closeConnection()
}
}