UNPKG

@iqai/mcp-near-agent

Version:
362 lines โ€ข 14.2 kB
import { EventEmitter } from "node:events"; import { AuthManager } from "./auth-manager.js"; import { EventListener } from "./event-listener.js"; import { EventProcessor } from "./event-processor.js"; import { subscriptionManager, } from "./subscription-manager.js"; export class EventWatcher extends EventEmitter { static instance; authManager; eventListener; eventProcessor; server = null; isInitialized = false; startTime = Date.now(); // Statistics tracking stats = { totalEventsDetected: 0, totalEventsProcessed: 0, totalEventsSuccessful: 0, totalEventsFailed: 0, totalProcessingTime: 0, }; constructor() { super(); this.authManager = AuthManager.getInstance(); this.eventListener = new EventListener(); this.eventProcessor = new EventProcessor(); this.setupEventHandlers(); } static getInstance() { if (!EventWatcher.instance) { EventWatcher.instance = new EventWatcher(); } return EventWatcher.instance; } /** * Set the MCP server instance for message creation */ setServer(server) { this.server = server; this.eventProcessor.setServer(this.server); } /** * Initialize the EventWatcher - handles all authentication internally */ async initialize() { if (this.isInitialized) { return; } console.log("๐Ÿš€ Initializing EventWatcher..."); try { // Initialize AuthManager if not already done if (!this.authManager.isReady()) { console.log("๐Ÿ”„ Initializing NEAR account via AuthManager..."); await this.authManager.initialize(); } // Validate connection const isValid = await this.authManager.validateConnection(); if (!isValid) { throw new Error("NEAR account connection is not valid"); } const account = this.authManager.getAccount(); if (!account) { throw new Error("Failed to get NEAR account from AuthManager"); } // Initialize components await this.eventListener.initialize(); this.eventProcessor.setAccount(account); this.isInitialized = true; console.log("โœ… EventWatcher initialized successfully"); } catch (error) { console.error("โŒ Failed to initialize EventWatcher:", error); throw new Error(`EventWatcher initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`); } } /** * Start watching for a specific event */ async watchEvent(request) { // Initialize if not already done await this.initialize(); const { contractId, eventName, responseMethodName, responseParameterName, cronExpression, server, } = request; // Check if already watching this event if (subscriptionManager.hasSubscription(contractId, eventName)) { throw new Error(`Already watching event '${eventName}' on contract '${contractId}'`); } try { console.log(`๐ŸŽฏ Starting to watch event '${eventName}' on contract '${contractId}'`); // Create subscription const subscriptionConfig = { contractId, eventName, responseMethodName, responseParameterName, cronExpression: cronExpression || "*/10 * * * * *", server, }; const subscription = subscriptionManager.subscribe(subscriptionConfig); // Start listening with EventListener const cronJob = this.eventListener.startListening(subscription); // Update subscription with cron job reference subscriptionManager.updateCronJob(contractId, eventName, cronJob); this.emit("watcher:started", subscription.id); this.emitStatsUpdate(); console.log(`โœ… Successfully started watching event '${eventName}' on contract '${contractId}'`); return subscription.id; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; console.error("โŒ Failed to start watching event:", error); // Clean up partial subscription if it was created subscriptionManager.unsubscribe(contractId, eventName); throw new Error(`Failed to watch event: ${errorMessage}`); } } /** * Stop watching a specific event */ async stopWatching(contractId, eventName) { const subscription = subscriptionManager.getSubscription(contractId, eventName); if (!subscription) { console.warn(`โš ๏ธ No active subscription found for event '${eventName}' on contract '${contractId}'`); return false; } try { console.log(`๐Ÿ›‘ Stopping watch for event '${eventName}' on contract '${contractId}'`); // Stop listening this.eventListener.stopListening(subscription.id); // Remove subscription const success = subscriptionManager.unsubscribe(contractId, eventName); if (success) { this.emit("watcher:stopped", subscription.id); this.emitStatsUpdate(); console.log(`โœ… Successfully stopped watching event '${eventName}' on contract '${contractId}'`); } return success; } catch (error) { console.error("โŒ Error stopping watch for event:", error); this.emit("watcher:error", { subscriptionId: subscription.id, error }); return false; } } /** * Stop watching all events */ async stopAllWatching() { console.log("๐Ÿ›‘ Stopping all event watching..."); const activeSubscriptions = subscriptionManager.getActiveSubscriptions(); for (const subscription of activeSubscriptions) { try { await this.stopWatching(subscription.contractId, subscription.eventName); } catch (error) { console.error(`โŒ Error stopping subscription ${subscription.id}:`, error); } } console.log("โœ… All event watching stopped"); } /** * Get current watching status */ getWatchingStatus() { const subscriptions = subscriptionManager.getActiveSubscriptions(); const listenerStats = this.eventListener.getStats(); const processorStats = this.eventProcessor.getStats(); return { isInitialized: this.isInitialized, totalSubscriptions: subscriptions.length, subscriptions: subscriptions.map((sub) => ({ id: sub.id, contractId: sub.contractId, eventName: sub.eventName, responseMethodName: sub.responseMethodName, responseParameterName: sub.responseParameterName, cronExpression: sub.cronExpression, isActive: sub.isActive, createdAt: sub.createdAt, lastEventAt: sub.lastEventAt, })), listener: listenerStats, processor: processorStats, }; } /** * Get comprehensive statistics */ getStats() { const subscriptionStats = subscriptionManager.getStats(); return { totalSubscriptions: subscriptionStats.total, activeSubscriptions: subscriptionStats.active, totalEventsDetected: this.stats.totalEventsDetected, totalEventsProcessed: this.stats.totalEventsProcessed, totalEventsSuccessful: this.stats.totalEventsSuccessful, totalEventsFailed: this.stats.totalEventsFailed, successRate: this.stats.totalEventsProcessed > 0 ? (this.stats.totalEventsSuccessful / this.stats.totalEventsProcessed) * 100 : 0, averageProcessingTime: this.stats.totalEventsProcessed > 0 ? this.stats.totalProcessingTime / this.stats.totalEventsProcessed : 0, uptime: Date.now() - this.startTime, }; } /** * Pause watching for a specific event */ pauseWatching(contractId, eventName) { return subscriptionManager.pauseSubscription(contractId, eventName); } /** * Resume watching for a specific event */ resumeWatching(contractId, eventName) { const subscription = subscriptionManager.getSubscription(contractId, eventName); if (!subscription) { return false; } const success = subscriptionManager.resumeSubscription(contractId, eventName); if (success && subscription.cronJob) { // Restart the cron job subscription.cronJob.start(); } return success; } /** * Check if watching a specific event */ isWatching(contractId, eventName) { return subscriptionManager.hasSubscription(contractId, eventName); } /** * Get list of all watched events */ getWatchedEvents() { return subscriptionManager.getActiveSubscriptions().map((sub) => ({ contractId: sub.contractId, eventName: sub.eventName, subscriptionId: sub.id, })); } /** * Setup event handlers to connect EventListener and EventProcessor */ setupEventHandlers() { // Handle events found by EventListener this.eventListener.on("event:found", async ({ event, subscription }) => { console.log(`๐Ÿ”” Event detected: ${event.eventType} from ${subscription.contractId}`); this.stats.totalEventsDetected++; subscriptionManager.markEventReceived(subscription.contractId, subscription.eventName); this.emit("event:detected", { event, subscription }); // Process the event const account = this.authManager.getAccount(); if (account) { try { const result = await this.eventProcessor.processEvent({ subscription, event, account, }); this.updateProcessingStats(result.success, result.processingTime); if (result.success) { this.emit("event:processed", { requestId: result.requestId, response: result.response || "No Response", processingTime: result.processingTime, }); } else { this.emit("event:failed", { requestId: result.requestId, error: result.error || "No error", processingTime: result.processingTime, }); } } catch (error) { console.error("โŒ Unexpected error processing event:", error); this.stats.totalEventsFailed++; this.emit("event:failed", { requestId: event.requestId, error: error instanceof Error ? error.message : "Unknown error", processingTime: 0, }); } } this.emitStatsUpdate(); }); // Handle EventListener errors this.eventListener.on("event:error", ({ subscription, error }) => { console.error(`โŒ EventListener error for subscription ${subscription.id}:`, error); this.emit("watcher:error", { subscriptionId: subscription.id, error }); }); // Handle EventProcessor events (optional additional logging) this.eventProcessor.on("event:processed", (result) => { console.log(`โœ… Event ${result.requestId} processed successfully in ${result.processingTime}ms`); }); this.eventProcessor.on("event:processing-error", (result) => { console.error(`โŒ Event ${result.requestId} processing failed: ${result.error}`); }); } /** * Update processing statistics */ updateProcessingStats(success, processingTime) { this.stats.totalEventsProcessed++; this.stats.totalProcessingTime += processingTime; if (success) { this.stats.totalEventsSuccessful++; } else { this.stats.totalEventsFailed++; } } /** * Emit stats update event */ emitStatsUpdate() { this.emit("stats:updated", this.getStats()); } /** * Cleanup all resources */ async cleanup() { console.log("๐Ÿงน Cleaning up EventWatcher..."); try { // Stop all watching await this.stopAllWatching(); // Cleanup components this.eventListener.cleanup(); this.eventProcessor.cleanup(); subscriptionManager.cleanup(); // Reset state this.isInitialized = false; this.removeAllListeners(); console.log("โœ… EventWatcher cleaned up successfully"); } catch (error) { console.error("โŒ Error during EventWatcher cleanup:", error); throw error; } } // Type-safe event listener methods on(event, listener) { return super.on(event, listener); } emit(event, ...args) { return super.emit(event, ...args); } off(event, listener) { return super.off(event, listener); } once(event, listener) { return super.once(event, listener); } } // Export singleton instance export const eventWatcher = EventWatcher.getInstance(); //# sourceMappingURL=event-watcher.js.map