/** * EventEmitter.ts * * A lightweight, typed event emitter for framework-agnostic components. * Used by GatewayClient, StreamStateClient, and PlayerController. */ type Listener = (data: T) => void; /** * Typed event emitter that provides type-safe event handling. * * @example * ```typescript * interface MyEvents { * stateChange: { state: string }; * error: { message: string }; * } * * class MyClass extends TypedEventEmitter { * doSomething() { * this.emit('stateChange', { state: 'ready' }); * } * } * * const instance = new MyClass(); * const unsub = instance.on('stateChange', ({ state }) => console.log(state)); * unsub(); // unsubscribe * ``` */ export class TypedEventEmitter> { private listeners = new Map>(); /** * Subscribe to an event. * @param event - The event name to listen for * @param listener - Callback function invoked when the event is emitted * @returns Unsubscribe function */ on(event: K, listener: Listener): () => void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event)!.add(listener); // Return unsubscribe function return () => this.off(event, listener); } /** * Subscribe to an event only once. * The listener is automatically removed after the first invocation. * @param event - The event name to listen for * @param listener - Callback function invoked when the event is emitted * @returns Unsubscribe function */ once(event: K, listener: Listener): () => void { const onceListener = (data: Events[K]) => { this.off(event, onceListener); listener(data); }; return this.on(event, onceListener); } /** * Unsubscribe from an event. * @param event - The event name * @param listener - The callback to remove */ off(event: K, listener: Listener): void { this.listeners.get(event)?.delete(listener); } /** * Emit an event to all subscribers. * @param event - The event name * @param data - The event payload */ protected emit(event: K, data: Events[K]): void { this.listeners.get(event)?.forEach((listener) => { try { listener(data); } catch (e) { console.error(`[EventEmitter] Error in ${String(event)} listener:`, e); } }); } /** * Remove all listeners for all events. */ removeAllListeners(): void { this.listeners.clear(); } /** * Remove all listeners for a specific event. * @param event - The event name */ removeListeners(event: K): void { this.listeners.delete(event); } /** * Check if there are any listeners for an event. * @param event - The event name */ hasListeners(event: K): boolean { return (this.listeners.get(event)?.size ?? 0) > 0; } } export default TypedEventEmitter;