import EventEmitter from 'eventemitter3';
import {
VerificationEventType,
EventData,
BaseEventData,
} from '../types/event-types';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export interface EventListener<T extends EventData = EventData> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(...args: any[]): void | Promise<void>;
}
export interface EventSubscription {
unsubscribe(): void;
}
/**
* Centralized Event Emitter for porkate-valid8
* Provides comprehensive event logging for monitoring and analytics
*/
export class VerificationEventEmitter {
private readonly emitter: EventEmitter;
private readonly eventHistory: Map<string, EventData[]> = new Map();
private readonly maxHistorySize: number;
private isRecording: boolean = false;
constructor(maxHistorySize: number = 1000) {
this.emitter = new EventEmitter();
this.maxHistorySize = maxHistorySize;
}
/**
* Emit an event with enriched data
* @template T - Event data type extending EventData
* @param {string} eventType - The type of verification event
* @param {Object} data - Event data (timestamp and correlationId added automatically)
* @returns {void}
*/
emit<T extends EventData = EventData>(
eventType: VerificationEventType,
data: Omit<T, keyof BaseEventData> & Partial<BaseEventData>,
): void {
const enrichedData = this.enrichEventData(data) as T;
// Store in history if recording is enabled
if (this.isRecording) {
this.addToHistory(eventType, enrichedData);
}
// Emit the event
this.emitter.emit(eventType, enrichedData);
this.emitter.emit('*', eventType, enrichedData); // Wildcard listener
}
/**
* Subscribe to a specific event
*/
on<T extends EventData = EventData>(
eventType: VerificationEventType | '*',
listener: EventListener<T>,
): EventSubscription {
this.emitter.on(eventType, listener);
return {
unsubscribe: () => {
this.emitter.off(eventType, listener);
},
};
}
/**
* Subscribe to an event once
*/
once<T extends EventData = EventData>(
eventType: VerificationEventType,
listener: EventListener<T>,
): EventSubscription {
this.emitter.once(eventType, listener);
return {
unsubscribe: () => {
this.emitter.off(eventType, listener);
},
};
}
/**
* Unsubscribe from an event
*/
off<T extends EventData = EventData>(
eventType: VerificationEventType | '*',
listener: EventListener<T>,
): void {
this.emitter.off(eventType, listener);
}
/**
* Remove all listeners for an event
*/
removeAllListeners(eventType?: VerificationEventType): void {
this.emitter.removeAllListeners(eventType);
}
/**
* Get the number of listeners for an event
*/
listenerCount(eventType: VerificationEventType): number {
return this.emitter.listenerCount(eventType);
}
/**
* Enable event history recording
*/
startRecording(): void {
this.isRecording = true;
}
/**
* Disable event history recording
*/
stopRecording(): void {
this.isRecording = false;
}
/**
* Get event history for a specific event type
*/
getHistory(eventType?: VerificationEventType): EventData[] {
if (eventType) {
return this.eventHistory.get(eventType) || [];
}
// Return all history
const allHistory: EventData[] = [];
this.eventHistory.forEach((events) => {
allHistory.push(...events);
});
return allHistory.sort(
(a, b) => b.timestamp.getTime() - a.timestamp.getTime(),
);
}
/**
* Clear event history
*/
clearHistory(eventType?: VerificationEventType): void {
if (eventType) {
this.eventHistory.delete(eventType);
} else {
this.eventHistory.clear();
}
}
/**
* Get statistics for events
*/
getStatistics(): {
totalEvents: number;
eventsByType: Record<string, number>;
oldestEvent?: Date;
newestEvent?: Date;
} {
let totalEvents = 0;
const eventsByType: Record<string, number> = {};
let oldestEvent: Date | undefined;
let newestEvent: Date | undefined;
this.eventHistory.forEach((events, type) => {
totalEvents += events.length;
eventsByType[type] = events.length;
events.forEach((event) => {
if (!oldestEvent || event.timestamp < oldestEvent) {
oldestEvent = event.timestamp;
}
if (!newestEvent || event.timestamp > newestEvent) {
newestEvent = event.timestamp;
}
});
});
return {
totalEvents,
eventsByType,
oldestEvent,
newestEvent,
};
}
/**
* Enrich event data with base properties
*/
private enrichEventData(data: Partial<BaseEventData>): BaseEventData {
return {
eventId: this.generateEventId(),
timestamp: new Date(),
...data,
};
}
/**
* Add event to history
*/
private addToHistory(eventType: VerificationEventType, data: EventData): void {
if (!this.eventHistory.has(eventType)) {
this.eventHistory.set(eventType, []);
}
const history = this.eventHistory.get(eventType)!;
history.push(data);
// Maintain max history size
if (history.length > this.maxHistorySize) {
history.shift();
}
}
/**
* Generate unique event ID
*/
private generateEventId(): string {
return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
// Export singleton instance
export const globalEventEmitter = new VerificationEventEmitter();
Source