Skip to main content
Glama
webhook-manager.ts8.48 kB
/** * Webhook Manager * Central coordinator for webhook delivery system * Part of Jaxon Digital Optimizely DXP MCP Server - DXP-136 Phase 2 */ import { getGlobalEmitter } from '../events/event-emitter'; import { getGlobalWebhookQueue } from './webhook-queue'; import WebhookValidator from './webhook-validator'; import WebhookLogger from './webhook-logger'; import { DXPEvent, isTerminalEvent } from '../events/event-types'; import { DXPEventEmitter } from '../events/event-emitter'; import { WebhookQueue } from './webhook-queue'; /** * Webhook configuration */ export interface WebhookConfig { url: string; headers: Record<string, string>; registeredAt: number; project: string; environment: string; } /** * Webhook statistics */ export interface WebhookStats { registrations: number; deliveries: number; errors: number; activeWebhooks: number; queueStats: any; deliveryStats: any; } /** * Register result */ export interface RegisterResult { success: boolean; error?: string; } /** * Register options */ export interface RegisterOptions { headers?: Record<string, string>; project?: string; environment?: string; } /** * Active webhook info */ export interface ActiveWebhook { operationId: string; url: string; project: string; environment: string; registeredAt: number; } /** * Webhook Manager Class * Manages webhook registrations and event-to-webhook routing */ class WebhookManager { private webhooks: Map<string, WebhookConfig>; private emitter: DXPEventEmitter; private queue: WebhookQueue; private stats: { registrations: number; deliveries: number; errors: number; }; private initialized: boolean; constructor() { // Map of operationId → webhook config this.webhooks = new Map(); // Event emitter and queue this.emitter = getGlobalEmitter(); this.queue = getGlobalWebhookQueue(); // Stats this.stats = { registrations: 0, deliveries: 0, errors: 0 }; // Initialize event listener this.initialized = false; } /** * Initialize webhook manager * Sets up event listener */ initialize(): void { if (this.initialized) { return; } // Listen to all events via wildcard this.emitter.on('*', (event: DXPEvent) => { this.handleEvent(event); }); this.initialized = true; if (process.env.DEBUG === 'true') { console.error('[WEBHOOK MANAGER] Initialized'); } } /** * Register a webhook for an operation * @param operationId - Operation ID (deploymentId, exportId, etc.) * @param webhookUrl - Webhook URL * @param options - Webhook options * @returns { success: boolean, error?: string } */ register(operationId: string, webhookUrl: string, options: RegisterOptions = {}): RegisterResult { if (!operationId || !webhookUrl) { return { success: false, error: 'operationId and webhookUrl are required' }; } // Validate webhook URL const validation = WebhookValidator.validateUrl(webhookUrl, { allowHttp: process.env.NODE_ENV === 'development', allowLocalhost: process.env.NODE_ENV === 'development' }); if (!validation.valid) { return { success: false, error: validation.error }; } // Validate headers if provided if (options.headers) { const headersValidation = WebhookValidator.validateHeaders(options.headers); if (!headersValidation.valid) { return { success: false, error: headersValidation.error }; } } // Register webhook this.webhooks.set(operationId, { url: webhookUrl, headers: options.headers || {}, registeredAt: Date.now(), project: options.project || 'unknown', environment: options.environment || 'unknown' }); this.stats.registrations++; if (process.env.DEBUG === 'true') { console.error(`[WEBHOOK MANAGER] Registered webhook for operation ${operationId}: ${WebhookLogger.sanitizeUrl(webhookUrl)}`); } return { success: true }; } /** * Unregister a webhook * @param operationId - Operation ID * @returns True if unregistered */ unregister(operationId: string): boolean { const removed = this.webhooks.delete(operationId); if (removed && process.env.DEBUG === 'true') { console.error(`[WEBHOOK MANAGER] Unregistered webhook for operation ${operationId}`); } return removed; } /** * Handle an event from the event emitter * @param event - Event object */ private handleEvent(event: DXPEvent): void { const { operationId } = event; // Check if there's a webhook registered for this operation const webhookConfig = this.webhooks.get(operationId); if (!webhookConfig) { return; // No webhook registered for this operation } // Validate event payload const validation = WebhookValidator.validatePayload(event); if (!validation.valid) { WebhookLogger.logError('unknown', webhookConfig.url, `Invalid event payload: ${validation.error}`); this.stats.errors++; return; } // Enrich event with project/environment context const enrichedEvent: DXPEvent = { ...event, metadata: { ...event.metadata, project: webhookConfig.project, environment: webhookConfig.environment } }; // Queue webhook delivery const webhookId = this.queue.enqueue( webhookConfig.url, enrichedEvent, { headers: webhookConfig.headers } ); if (webhookId) { this.stats.deliveries++; if (process.env.DEBUG === 'true') { console.error(`[WEBHOOK MANAGER] Queued webhook ${webhookId} for ${event.eventType} (operation: ${operationId})`); } } else { this.stats.errors++; WebhookLogger.logError('unknown', webhookConfig.url, 'Failed to queue webhook (queue full?)'); } // Auto-cleanup: unregister webhook after terminal event if (isTerminalEvent(event.eventType)) { // Wait a bit to ensure all events are processed setTimeout(() => { this.unregister(operationId); }, 5000); // 5 second delay } } /** * Get webhook statistics * @returns Statistics */ getStats(): WebhookStats { return { ...this.stats, activeWebhooks: this.webhooks.size, queueStats: this.queue.getStats(), deliveryStats: WebhookLogger.getStats() }; } /** * Get active webhooks * @returns Array of active webhooks */ getActiveWebhooks(): ActiveWebhook[] { const active: ActiveWebhook[] = []; for (const [operationId, config] of this.webhooks.entries()) { active.push({ operationId, url: WebhookLogger.sanitizeUrl(config.url), project: config.project, environment: config.environment, registeredAt: config.registeredAt }); } return active; } /** * Clear all webhooks */ clear(): void { this.webhooks.clear(); this.queue.clear(); this.stats = { registrations: 0, deliveries: 0, errors: 0 }; } } // Singleton instance let globalManager: WebhookManager | null = null; /** * Get the global webhook manager instance * @returns Global manager */ export function getGlobalWebhookManager(): WebhookManager { if (!globalManager) { globalManager = new WebhookManager(); } return globalManager; } /** * Reset the global manager (for testing) */ export function resetGlobalWebhookManager(): void { if (globalManager) { globalManager.clear(); } globalManager = null; } export { WebhookManager };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JaxonDigital/optimizely-dxp-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server