import type { Logger } from "pino"; import type { AlertEvent, AlertSender } from "./types.js"; /** * Envoie les alertes à un webhook n8n (qui envoie le mail). Fail-soft : une * panne d'alerte ne doit jamais casser la boucle agent ni le gatekeeper — on * logge et on continue. */ export class HttpAlertSender implements AlertSender { constructor( private readonly webhookUrl: string, private readonly logger: Logger, private readonly timeoutMs = 10_000, ) {} async send(event: AlertEvent): Promise { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), this.timeoutMs); try { const res = await fetch(this.webhookUrl, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ source: "chlova", ts: Date.now(), ...event }), signal: controller.signal, }); if (!res.ok) { this.logger.warn({ kind: event.kind, status: res.status }, "alerte: HTTP non-OK"); } } catch (err) { // Jamais propagé : l'alerte est best-effort. this.logger.warn( { kind: event.kind, err: err instanceof Error ? err.message : String(err) }, "alerte: envoi échoué (best-effort)", ); } finally { clearTimeout(timer); } } } /** Sender inactif : aucun webhook configuré → on logge seulement (fail-safe). */ export class NullAlertSender implements AlertSender { constructor(private readonly logger: Logger) {} async send(event: AlertEvent): Promise { this.logger.info({ alert: event }, "alerte (non envoyée : pas de webhook)"); } }