|  |  | import { REQUIRED_ENV } from '../server';
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | type WebhookConfig = {
 | 
						
						
						
							|  |  | 	url: string;
 | 
						
						
						
							|  |  | 	enabled: boolean;
 | 
						
						
						
							|  |  | 	webhook_by_events: boolean;
 | 
						
						
						
							|  |  | 	webhook_base64: boolean;
 | 
						
						
						
							|  |  | 	events: string[];
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | type WebhookResponse = {
 | 
						
						
						
							|  |  | 	webhook: {
 | 
						
						
						
							|  |  | 		instanceName: string;
 | 
						
						
						
							|  |  | 		webhook: {
 | 
						
						
						
							|  |  | 			url: string;
 | 
						
						
						
							|  |  | 			events: string[];
 | 
						
						
						
							|  |  | 			enabled: boolean;
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 	};
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | export class WebhookManager {
 | 
						
						
						
							|  |  | 	private static readonly REQUIRED_EVENTS = [
 | 
						
						
						
							|  |  | 		'APPLICATION_STARTUP',
 | 
						
						
						
							|  |  | 		'MESSAGES_UPSERT',
 | 
						
						
						
							|  |  | 		'GROUPS_UPSERT',
 | 
						
						
						
							|  |  | 		'MESSAGES_UPDATE',
 | 
						
						
						
							|  |  | 		'MESSAGES_DELETE',
 | 
						
						
						
							|  |  | 		'PRESENCE_UPDATE',
 | 
						
						
						
							|  |  | 		'CONTACTS_UPDATE',
 | 
						
						
						
							|  |  | 		'CHATS_UPDATE'
 | 
						
						
						
							|  |  | 	];
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	private static validateConfig() {
 | 
						
						
						
							|  |  | 		// Solo requerimos aquí las vars necesarias para registrar/verificar el webhook.
 | 
						
						
						
							|  |  | 		// Excluimos CHATBOT_PHONE_NUMBER para que los tests unitarios de validación no fallen.
 | 
						
						
						
							|  |  | 		const missing = REQUIRED_ENV
 | 
						
						
						
							|  |  | 			.filter(v => v !== 'CHATBOT_PHONE_NUMBER')
 | 
						
						
						
							|  |  | 			.filter(v => !process.env[v]);
 | 
						
						
						
							|  |  | 		if (missing.length) {
 | 
						
						
						
							|  |  | 			throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		if (!process.env.WEBHOOK_URL?.trim()) {
 | 
						
						
						
							|  |  | 			throw new Error('WEBHOOK_URL environment variable is required and cannot be empty');
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		try {
 | 
						
						
						
							|  |  | 			const url = new URL(process.env.WEBHOOK_URL);
 | 
						
						
						
							|  |  | 			
 | 
						
						
						
							|  |  | 			// Enhanced internal Docker URL validation
 | 
						
						
						
							|  |  | 			if (process.env.WEBHOOK_URL.startsWith('http://srv-captain--')) {
 | 
						
						
						
							|  |  | 				if (!url.port) {
 | 
						
						
						
							|  |  | 					console.warn('Internal Docker URL missing port number - this may cause connection issues');
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 			
 | 
						
						
						
							|  |  | 			// Allow internal docker URLs in production
 | 
						
						
						
							|  |  | 			if (process.env.NODE_ENV === 'production') {
 | 
						
						
						
							|  |  | 				const isOk = (url.protocol === 'http:' || url.protocol === 'https:') || process.env.WEBHOOK_URL.startsWith('http://srv-captain--');
 | 
						
						
						
							|  |  | 				if (!isOk) {
 | 
						
						
						
							|  |  | 					console.warn('Production WEBHOOK_URL should use http/https or internal docker URL');
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			} else if (!(url.protocol === 'http:' || url.protocol === 'https:')) {
 | 
						
						
						
							|  |  | 				throw new Error('WEBHOOK_URL must use http or https protocol');
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		} catch (e) {
 | 
						
						
						
							|  |  | 			throw new Error(`Invalid WEBHOOK_URL: ${e.message}`);
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	private static getConfig(): { webhook: WebhookConfig } {
 | 
						
						
						
							|  |  | 		return {
 | 
						
						
						
							|  |  | 			webhook: {
 | 
						
						
						
							|  |  | 				url: process.env.WEBHOOK_URL!,
 | 
						
						
						
							|  |  | 				enabled: true,
 | 
						
						
						
							|  |  | 				webhook_by_events: true,
 | 
						
						
						
							|  |  | 				webhook_base64: true,
 | 
						
						
						
							|  |  | 				events: this.REQUIRED_EVENTS,
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	private static getApiUrl(): string {
 | 
						
						
						
							|  |  | 		return `${process.env.EVOLUTION_API_URL}/webhook/set/${process.env.EVOLUTION_API_INSTANCE}`;
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	private static getHeaders(): HeadersInit {
 | 
						
						
						
							|  |  | 		return {
 | 
						
						
						
							|  |  | 			apikey: process.env.EVOLUTION_API_KEY!,
 | 
						
						
						
							|  |  | 			'Content-Type': 'application/json',
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	static async registerWebhook(): Promise<WebhookResponse> {
 | 
						
						
						
							|  |  | 		this.validateConfig();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		// First test if our own endpoint is reachable
 | 
						
						
						
							|  |  | 		const endpointTest = await this.testWebhookEndpoint();
 | 
						
						
						
							|  |  | 		if (!endpointTest) {
 | 
						
						
						
							|  |  | 			throw new Error('Webhook endpoint test failed - check your server logs');
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		const config = this.getConfig();
 | 
						
						
						
							|  |  | 		const apiUrl = this.getApiUrl();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		const logSafeUrl = (url: string) => url ? `${url.substring(0, 20)}...` : 'invalid-url';
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		console.log('ℹ️ Attempting to register webhook:', {
 | 
						
						
						
							|  |  | 			apiUrl,
 | 
						
						
						
							|  |  | 			config: {
 | 
						
						
						
							|  |  | 				...config,
 | 
						
						
						
							|  |  | 				webhook: {
 | 
						
						
						
							|  |  | 					...config.webhook,
 | 
						
						
						
							|  |  | 					url: logSafeUrl(config.webhook.url),
 | 
						
						
						
							|  |  | 					events: config.webhook.events
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		const response = await fetch(apiUrl, {
 | 
						
						
						
							|  |  | 			method: 'POST',
 | 
						
						
						
							|  |  | 			headers: this.getHeaders(),
 | 
						
						
						
							|  |  | 			body: JSON.stringify(config),
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		if (!response.ok) {
 | 
						
						
						
							|  |  | 			let errorDetails = response.statusText;
 | 
						
						
						
							|  |  | 			try {
 | 
						
						
						
							|  |  | 				const errorBody = await response.text();
 | 
						
						
						
							|  |  | 				errorDetails += ` - ${errorBody}`;
 | 
						
						
						
							|  |  | 			} catch { }
 | 
						
						
						
							|  |  | 			throw new Error(`Failed to register webhook: ${errorDetails}`);
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		const data = await response.json();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		if (!data?.enabled) {
 | 
						
						
						
							|  |  | 			throw new Error('Webhook registration failed - not enabled in response');
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		console.log('✅ Webhook successfully registered:', {
 | 
						
						
						
							|  |  | 			url: data.url,
 | 
						
						
						
							|  |  | 			events: data.events,
 | 
						
						
						
							|  |  | 			id: data.id
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		return data;
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	static async verifyWebhook(): Promise<boolean> {
 | 
						
						
						
							|  |  | 		try {
 | 
						
						
						
							|  |  | 			const url = `${process.env.EVOLUTION_API_URL}/webhook/find/${process.env.EVOLUTION_API_INSTANCE}`;
 | 
						
						
						
							|  |  | 			console.log('ℹ️ Verifying webhook at:', url);
 | 
						
						
						
							|  |  | 			
 | 
						
						
						
							|  |  | 			const response = await fetch(url, {
 | 
						
						
						
							|  |  | 				method: 'GET',
 | 
						
						
						
							|  |  | 				headers: this.getHeaders(),
 | 
						
						
						
							|  |  | 			});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			if (!response.ok) {
 | 
						
						
						
							|  |  | 				const body = await response.text();
 | 
						
						
						
							|  |  | 				console.error('❌ Webhook verification failed:', {
 | 
						
						
						
							|  |  | 					status: response.status,
 | 
						
						
						
							|  |  | 					statusText: response.statusText,
 | 
						
						
						
							|  |  | 					body
 | 
						
						
						
							|  |  | 				});
 | 
						
						
						
							|  |  | 				return false;
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const data = await response.json();
 | 
						
						
						
							|  |  | 			console.log('ℹ️ Webhook verification response:', data);
 | 
						
						
						
							|  |  | 			
 | 
						
						
						
							|  |  | 			const isEnabled = data?.enabled === true;
 | 
						
						
						
							|  |  | 			if (!isEnabled) {
 | 
						
						
						
							|  |  | 				console.error('❌ Webhook not enabled in verification response');
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 			return isEnabled;
 | 
						
						
						
							|  |  | 		} catch (error) {
 | 
						
						
						
							|  |  | 			console.error('❌ Webhook verification error:', error instanceof Error ? error.stack : error);
 | 
						
						
						
							|  |  | 			return false;
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	static async testWebhookEndpoint(): Promise<boolean> {
 | 
						
						
						
							|  |  | 		try {
 | 
						
						
						
							|  |  | 			if (!process.env.WEBHOOK_URL) {
 | 
						
						
						
							|  |  | 				throw new Error('WEBHOOK_URL is not set');
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Skip self-test in production if using internal URL
 | 
						
						
						
							|  |  | 			if (process.env.NODE_ENV === 'production' && 
 | 
						
						
						
							|  |  | 				process.env.WEBHOOK_URL.startsWith('http://srv-captain--')) {
 | 
						
						
						
							|  |  | 				console.log('ℹ️ Skipping self-test for internal production URL');
 | 
						
						
						
							|  |  | 				return true;
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			console.log('ℹ️ Testing webhook endpoint:', process.env.WEBHOOK_URL);
 | 
						
						
						
							|  |  | 			
 | 
						
						
						
							|  |  | 			const testPayload = {
 | 
						
						
						
							|  |  | 				test: true,
 | 
						
						
						
							|  |  | 				timestamp: new Date().toISOString()
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const response = await fetch(process.env.WEBHOOK_URL, {
 | 
						
						
						
							|  |  | 				method: 'POST',
 | 
						
						
						
							|  |  | 				headers: {
 | 
						
						
						
							|  |  | 					'Content-Type': 'application/json',
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				body: JSON.stringify(testPayload),
 | 
						
						
						
							|  |  | 				timeout: 5000 // Add timeout to prevent hanging
 | 
						
						
						
							|  |  | 			});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			if (!response.ok) {
 | 
						
						
						
							|  |  | 				const body = await response.text();
 | 
						
						
						
							|  |  | 				console.error('❌ Webhook test failed:', {
 | 
						
						
						
							|  |  | 					status: response.status,
 | 
						
						
						
							|  |  | 					statusText: response.statusText,
 | 
						
						
						
							|  |  | 					body
 | 
						
						
						
							|  |  | 				});
 | 
						
						
						
							|  |  | 				return false;
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			console.log('✅ Webhook endpoint test successful');
 | 
						
						
						
							|  |  | 			return true;
 | 
						
						
						
							|  |  | 		} catch (error) {
 | 
						
						
						
							|  |  | 			console.error('❌ Webhook test error:', error instanceof Error ? error.stack : error);
 | 
						
						
						
							|  |  | 			return false;
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | }
 |