116 lines
4.1 KiB
JavaScript
116 lines
4.1 KiB
JavaScript
// webhook-utils.js (VERSION ES MODULES - CORRIGÉE)
|
|
// Pour projets avec "type": "module" dans package.json
|
|
|
|
import axios from 'axios';
|
|
import crypto from 'crypto';
|
|
|
|
class WebhookManager {
|
|
constructor(secretKey) {
|
|
this.secretKey = secretKey;
|
|
}
|
|
|
|
/**
|
|
* Génère une signature HMAC SHA-256 pour sécuriser le webhook
|
|
* @param {Object} payload - Les données à signer
|
|
* @returns {string} La signature hexadécimale
|
|
*/
|
|
generateSignature(payload) {
|
|
return crypto
|
|
.createHmac('sha256', this.secretKey)
|
|
.update(JSON.stringify(payload))
|
|
.digest('hex');
|
|
}
|
|
|
|
/**
|
|
* Vérifie la signature d'un webhook reçu
|
|
* @param {Object} payload - Les données reçues
|
|
* @param {string} receivedSignature - La signature reçue dans le header
|
|
* @returns {boolean} True si la signature est valide
|
|
*/
|
|
verifySignature(payload, receivedSignature) {
|
|
if (!receivedSignature) {
|
|
console.error('❌ Aucune signature fournie');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const expectedSignature = this.generateSignature(payload);
|
|
return crypto.timingSafeEqual(
|
|
Buffer.from(expectedSignature),
|
|
Buffer.from(receivedSignature)
|
|
);
|
|
} catch (error) {
|
|
console.error('❌ Erreur vérification signature:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Envoie un webhook à une URL cible avec retry automatique
|
|
* @param {string} targetUrl - URL du serveur cible
|
|
* @param {string} eventType - Type d'événement (ex: 'demande.validated')
|
|
* @param {Object} data - Données de l'événement
|
|
* @param {number} retries - Nombre de tentatives (défaut: 3)
|
|
* @returns {Promise<Object>} La réponse du serveur
|
|
*/
|
|
async sendWebhook(targetUrl, eventType, data, retries = 3) {
|
|
const payload = {
|
|
event: eventType,
|
|
data: data,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
const signature = this.generateSignature(payload);
|
|
|
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
try {
|
|
console.log(`📤 Envoi webhook: ${eventType} vers ${targetUrl} (tentative ${attempt}/${retries})`);
|
|
console.log(` Données:`, JSON.stringify(data, null, 2));
|
|
|
|
const response = await axios.post(
|
|
`${targetUrl}/api/webhook/receive`,
|
|
payload,
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Webhook-Signature': signature
|
|
},
|
|
timeout: 5000 // 5 secondes de timeout
|
|
}
|
|
);
|
|
|
|
console.log(`✅ Webhook envoyé avec succès: ${eventType}`);
|
|
return response.data;
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Erreur envoi webhook (tentative ${attempt}/${retries}):`, error.message);
|
|
|
|
if (attempt === retries) {
|
|
console.error(`❌ Échec définitif du webhook après ${retries} tentatives`);
|
|
throw error;
|
|
}
|
|
|
|
// Attendre avant de réessayer (backoff exponentiel)
|
|
const waitTime = 1000 * attempt;
|
|
console.log(`⏳ Nouvelle tentative dans ${waitTime}ms...`);
|
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Envoie un webhook sans attendre la réponse (fire and forget)
|
|
* Utile pour ne pas bloquer l'exécution
|
|
* @param {string} targetUrl - URL du serveur cible
|
|
* @param {string} eventType - Type d'événement
|
|
* @param {Object} data - Données de l'événement
|
|
*/
|
|
sendWebhookAsync(targetUrl, eventType, data) {
|
|
this.sendWebhook(targetUrl, eventType, data)
|
|
.catch(error => {
|
|
console.error('❌ Webhook async échoué (non bloquant):', error.message);
|
|
});
|
|
}
|
|
}
|
|
|
|
export default WebhookManager; |