Files
GTA/project/public/Backend/webhook-utils.js
2025-12-02 17:49:04 +01:00

115 lines
4.0 KiB
JavaScript

// webhook-utils.js (VERSION ES MODULES)
// 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})`);
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;