import { createContext, createSignal, onCleanup, useContext, type Component, type JSX } from 'solid-js'; import { SPACEDUST_SERVICE } from './api'; export interface LiveEvent { receivedAt: string; source: string; sourceRecord: string; subject: string; } interface LiveEventsContextValue { events: () => LiveEvent[]; unread: () => number; subscribe: (config: { subjects: string[]; sources: string[] } | null) => void; clear: () => void; markRead: () => void; } const LiveEventsContext = createContext(); export const LiveEventsProvider: Component<{ children: JSX.Element }> = (props) => { const [events, setEvents] = createSignal([]); const [unread, setUnread] = createSignal(0); let socket: WebSocket | null = null; let currentKey = ''; const clear = () => { currentKey = ''; socket?.close(); socket = null; setEvents([]); setUnread(0); }; const subscribe = (config: { subjects: string[]; sources: string[] } | null) => { if (!config || config.subjects.length === 0 || config.sources.length === 0) { clear(); return; } const key = JSON.stringify(config); if (key === currentKey) { return; } currentKey = key; socket?.close(); setEvents([]); setUnread(0); const url = new URL(SPACEDUST_SERVICE); for (const subject of config.subjects) { url.searchParams.append('wantedSubjects', subject); } for (const source of config.sources) { url.searchParams.append('wantedSources', source); } url.searchParams.set('instant', 'true'); socket = new WebSocket(url); socket.onmessage = (event) => { try { const data = JSON.parse(event.data) as { kind?: string; link?: { source: string; source_record: string; subject: string; }; }; if (data.kind !== 'link' || !data.link) { return; } const link = data.link; setEvents((prev) => [ { receivedAt: new Date().toISOString(), source: link.source, sourceRecord: link.source_record, subject: link.subject, }, ...prev, ].slice(0, 20), ); setUnread((count) => count + 1); } catch { // ignore malformed messages } }; }; const markRead = () => { setUnread(0); }; onCleanup(() => { socket?.close(); }); return ( {props.children} ); }; export const useLiveEvents = (): LiveEventsContextValue => { const context = useContext(LiveEventsContext); if (!context) { throw new Error('useLiveEvents must be used within its provider'); } return context; };