feat: WhatsApp Nucleo con Nuxt 4 + Baileys v7
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 6m46s

Reemplazo completo de Evolution API por implementación directa con Baileys.

Características:
- Dashboard completo con Nuxt UI v4
- Soporte para múltiples instancias de WhatsApp
- Conexión via QR code o pairing code
- Persistencia de mensajes en PostgreSQL
- API REST para integraciones externas
- Webhooks con firma HMAC
- SSE para actualizaciones en tiempo real
- Autenticación con Authentik
This commit is contained in:
2025-12-02 17:54:31 -06:00
parent 327118440b
commit faedec47d7
62 changed files with 4489 additions and 92 deletions

View File

@@ -0,0 +1,171 @@
/**
* Webhook Dispatcher
* Sends events to configured webhooks
*/
import crypto from 'crypto'
import { query } from '../../utils/database'
import { baileysManager } from '../baileys/manager'
interface Webhook {
id: string
url: string
secret: string | null
events: string[]
headers: Record<string, string>
retry_count: number
timeout_ms: number
instance_id: string | null
}
class WebhookDispatcher {
private initialized = false
async initialize() {
if (this.initialized) return
// Listen to all Baileys events
const events = [
'message.received',
'message.sent',
'message.status',
'instance.connected',
'instance.disconnected',
'instance.status',
'instance.qr'
]
for (const eventType of events) {
baileysManager.on(eventType, (data: any) => {
this.dispatch(data.instanceId || null, eventType, data)
})
}
this.initialized = true
console.log('[WebhookDispatcher] Initialized')
}
async dispatch(instanceId: string | null, eventType: string, payload: any) {
try {
// Get matching webhooks
const webhooks = await query<Webhook>(
`SELECT id, url, secret, events, headers, retry_count, timeout_ms, instance_id
FROM webhooks
WHERE is_active = TRUE
AND $1 = ANY(events)
AND (instance_id IS NULL OR instance_id = $2)`,
[eventType, instanceId]
)
for (const webhook of webhooks.rows) {
this.deliverWithRetry(webhook, eventType, payload)
}
} catch (error) {
console.error('[WebhookDispatcher] Error dispatching:', error)
}
}
private async deliverWithRetry(
webhook: Webhook,
eventType: string,
payload: any,
attempt = 1
) {
try {
await this.deliver(webhook, eventType, payload)
// Log success
await this.logDelivery(webhook.id, eventType, payload, {
status: 200,
attempt,
success: true
})
} catch (error) {
const errorMessage = (error as Error).message
// Log failure
await this.logDelivery(webhook.id, eventType, payload, {
status: 0,
attempt,
success: false,
error: errorMessage
})
// Retry if under limit
if (attempt < webhook.retry_count) {
const delay = Math.pow(2, attempt) * 1000 // Exponential backoff
setTimeout(() => {
this.deliverWithRetry(webhook, eventType, payload, attempt + 1)
}, delay)
}
}
}
private async deliver(webhook: Webhook, eventType: string, payload: any) {
const body = JSON.stringify({
event: eventType,
timestamp: new Date().toISOString(),
data: payload
})
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Webhook-Event': eventType,
'X-Webhook-Timestamp': Date.now().toString(),
...(webhook.headers || {})
}
// Add HMAC signature if secret is configured
if (webhook.secret) {
const signature = crypto
.createHmac('sha256', webhook.secret)
.update(body)
.digest('hex')
headers['X-Webhook-Signature'] = `sha256=${signature}`
}
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), webhook.timeout_ms)
try {
const response = await fetch(webhook.url, {
method: 'POST',
headers,
body,
signal: controller.signal
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
} finally {
clearTimeout(timeout)
}
}
private async logDelivery(
webhookId: string,
eventType: string,
payload: any,
result: { status: number; attempt: number; success: boolean; error?: string }
) {
try {
await query(
`INSERT INTO webhook_logs (webhook_id, event_type, payload, response_status, error_message, attempt, delivered_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
[
webhookId,
eventType,
JSON.stringify(payload),
result.status,
result.error || null,
result.attempt,
result.success ? new Date() : null
]
)
} catch (error) {
console.error('[WebhookDispatcher] Error logging delivery:', error)
}
}
}
export const webhookDispatcher = new WebhookDispatcher()