'use strict'; import LoggerManager from '../logger'; /** * Custom event emitter implementation for use */ class EventEmitter = DefaultListener> { private _listeners: {[E in keyof L]?: Set}; private _eventEmitterLogger = LoggerManager.getLogger('EventEmitter'); /** * Constructs instance */ constructor() { this._listeners = {}; } /** * Subscribes a listener to event * @param event Event to subscribe to * @param callback Listener function to subscribe */ on>(event: U, callback: L[U]) { if (!event) { throw new Error('Event name is empty or undefined'); } this._listeners[event] = this._listeners[event] || new Set(); this._listeners[event].add(callback); } /** * Unsubscribes a listener from event * @param event Event to unsubscribe from * @param callback Callback to unsubscribe */ off>(event: U, callback: L[U]) { if (this._listeners[event]) { this._listeners[event].delete(callback); if (!this._listeners[event].size) { delete this._listeners[event]; } } } /** * Subscribes a listener to event once * @param event Event to subscribe to * @param callback Listener function to subscribe * @param options Additional options */ once>(event: U, callback: L[U], options?: EventEmitter.OnceOptions) { let listener: any = ((...args: Parameters) => { if (!options?.ifArgs || options?.ifArgs(...args)) { this.off(event, listener); return callback(...args); } }); this.on(event, listener); } /** * Emits an event * @param event Event to emit * @param data Event payload * @returns Promise resolving when all listeners calls completed. Usually async version is useful if listeners may * return promises and the call should wait for them. If one of listener rejects or throws an error, it will be * logged. Async listeners called all at once */ async emit>(event: U, ...args: Parameters): Promise { if (!this._listeners[event]?.size) { return; } let results = []; for (let listener of this._listeners[event]) { results.push(this._callListener(event as string, listener, args)); } return Promise.all(results) as any; } /** * Returns subscribed events * @returns subscribed events */ getSubscriptions(): EventEmitter.Event[] { return Object.keys(this._listeners) as EventEmitter.Event[]; } /** * Returns subscribed event listeners * @param event Event * @returns Listeners */ getListeners>(event: U): L[U][] { if (!this._listeners[event]?.size) { return []; } return [...this._listeners[event].values()]; } /** * Returns whether has listeners on specific event * @param event Event * @returns Whether has listeners */ hasListeners>(event: U): boolean { return !!this._listeners[event]; } private async _callListener(event: string, listener: (...args: any) => any, args: any[]) { try { await listener(...args); } catch (err) { this._eventEmitterLogger.error(`${event}: listener failed with error`, err); } } } namespace EventEmitter { /** Method options */ export type OnceOptions any> = { /** * If specified, the listener will be called only if the callback returns true. Otherwise, the listener will wait * for the next matching call */ ifArgs?: (...args: Parameters) => boolean; }; /** Infers event type of a listener */ export type Event> = Extract; } export default EventEmitter; /** * Event emitter listener type */ export type ListenerSignature = { [EventName in keyof Listeners]: (...args: any[]) => any; }; /** * Default event emitter listener type */ export type DefaultListener = { [k: string]: (...args: any[]) => any; };