/** * Typed event emitter for the SDK's public event surface. * * Intentionally tiny — one map, one loop, no dependency on any EventTarget * polyfill. We keep fire-and-forget semantics: handler exceptions are caught * and logged so one misbehaving subscriber can't break the session. */ import type { SableEventHandler, SableEvents } from "../types"; type HandlerSet = Set>; export class SableEventEmitter { // Map of event name → set of handlers. Any-typed because TS can't express // "Map>>" cleanly; we gate access through the typed // `on`/`emit` methods below. private readonly listeners = new Map>(); on( event: E, handler: SableEventHandler, ): () => void { let set = this.listeners.get(event) as HandlerSet | undefined; if (!set) { set = new Set>(); this.listeners.set(event, set as unknown as Set); } set.add(handler); return () => { set?.delete(handler); }; } emit(event: E, payload: SableEvents[E]): void { const set = this.listeners.get(event) as HandlerSet | undefined; if (!set || set.size === 0) return; for (const handler of set) { try { handler(payload); } catch (err) { console.warn(`[Sable] event handler for "${String(event)}" threw`, err); } } } /** Drop every handler. Called on session teardown to avoid leaks. */ clear(): void { this.listeners.clear(); } }