From 6710c4fd732436c3a38b87ce903391f553c4bd80 Mon Sep 17 00:00:00 2001 From: "borja (aider)" Date: Thu, 27 Mar 2025 16:12:23 +0100 Subject: [PATCH] feat: implement webhook registration service --- src/server.ts | 15 ++++- src/services/webhook-manager.ts | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/services/webhook-manager.ts diff --git a/src/server.ts b/src/server.ts index c4d06c0..9399c2e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -104,11 +104,24 @@ export class WebhookServer { } } - static start() { + static async start() { this.validateEnv(); const PORT = process.env.PORT || '3007'; console.log('✅ Environment variables validated'); + + if (process.env.NODE_ENV !== 'test') { + try { + await WebhookManager.registerWebhook(); + const isActive = await WebhookManager.verifyWebhook(); + if (!isActive) { + console.error('❌ Webhook verification failed'); + process.exit(1); + } + } catch (error) { + console.error('❌ Failed to setup webhook:', error instanceof Error ? error.message : error); + process.exit(1); + } if (process.env.NODE_ENV !== 'test') { const server = Bun.serve({ diff --git a/src/services/webhook-manager.ts b/src/services/webhook-manager.ts new file mode 100644 index 0000000..99538f9 --- /dev/null +++ b/src/services/webhook-manager.ts @@ -0,0 +1,109 @@ +import { REQUIRED_ENV } from '../server'; + +type WebhookConfig = { + url: string; + 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', + 'messages.update', + 'messages.delete', + 'groups.update', + ]; + + private static validateConfig() { + const missing = REQUIRED_ENV.filter(v => !process.env[v]); + if (missing.length) { + throw new Error(`Missing required environment variables: ${missing.join(', ')}`); + } + + if (!process.env.WEBHOOK_URL) { + throw new Error('WEBHOOK_URL environment variable is required'); + } + + try { + new URL(process.env.WEBHOOK_URL); + } catch { + throw new Error('WEBHOOK_URL must be a valid URL'); + } + } + + private static getConfig(): WebhookConfig { + return { + url: process.env.WEBHOOK_URL!, + 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 { + this.validateConfig(); + + const response = await fetch(this.getApiUrl(), { + method: 'POST', + headers: this.getHeaders(), + body: JSON.stringify(this.getConfig()), + }); + + if (!response.ok) { + throw new Error(`Failed to register webhook: ${response.statusText}`); + } + + const data = await response.json(); + + if (!data?.webhook?.webhook?.enabled) { + throw new Error('Webhook registration failed - not enabled in response'); + } + + console.log('✅ Webhook successfully registered:', { + url: data.webhook.webhook.url, + events: data.webhook.webhook.events, + }); + + return data; + } + + static async verifyWebhook(): Promise { + try { + const response = await fetch(`${process.env.EVOLUTION_API_URL}/webhook/find/${process.env.EVOLUTION_API_INSTANCE}`, { + method: 'GET', + headers: this.getHeaders(), + }); + + if (!response.ok) return false; + + const data = await response.json(); + return data?.webhook?.enabled === true; + } catch { + return false; + } + } +}