@casoon/auditmysite
Version:
Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe
294 lines โข 10.3 kB
JavaScript
;
/**
* Webhook Management System for API notifications
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.webhookManager = exports.WebhookManager = void 0;
const axios_1 = __importDefault(require("axios"));
const crypto_1 = __importDefault(require("crypto"));
class WebhookManager {
constructor() {
this.webhooks = new Map();
this.deliveries = new Map();
this.retryQueue = new Set();
this.retryInterval = null;
this.startRetryProcessor();
}
/**
* Register a new webhook
*/
register(id, config) {
this.webhooks.set(id, {
...config,
retries: config.retries || 3,
timeout: config.timeout || 30000
});
console.log(`๐ฃ Webhook registered: ${id} -> ${config.url}`);
}
/**
* Unregister a webhook
*/
unregister(id) {
const removed = this.webhooks.delete(id);
if (removed) {
console.log(`๐๏ธ Webhook unregistered: ${id}`);
}
return removed;
}
/**
* Update webhook configuration
*/
update(id, config) {
const existing = this.webhooks.get(id);
if (!existing) {
return false;
}
this.webhooks.set(id, { ...existing, ...config });
console.log(`๐ Webhook updated: ${id}`);
return true;
}
/**
* Get webhook configuration
*/
getWebhook(id) {
return this.webhooks.get(id);
}
/**
* List all webhooks
*/
listWebhooks() {
return new Map(this.webhooks);
}
/**
* Trigger webhook for a specific event
*/
async trigger(event, data) {
const relevantWebhooks = Array.from(this.webhooks.entries())
.filter(([_, config]) => config.active && config.events.includes(event));
if (relevantWebhooks.length === 0) {
console.log(`๐ญ No active webhooks for event: ${event}`);
return [];
}
console.log(`๐จ Triggering ${relevantWebhooks.length} webhooks for event: ${event}`);
const deliveries = [];
for (const [webhookId, config] of relevantWebhooks) {
const delivery = await this.deliver(webhookId, config, event, data);
deliveries.push(delivery);
}
return deliveries;
}
/**
* Deliver webhook to a specific endpoint
*/
async deliver(webhookId, config, event, data) {
const deliveryId = `delivery-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const payload = {
event,
timestamp: Date.now(),
data
};
// Add signature if secret is provided
if (config.secret) {
payload.signature = this.generateSignature(payload, config.secret);
}
const delivery = {
id: deliveryId,
webhook_id: webhookId,
event,
payload,
status: 'pending',
attempts: 0,
last_attempt: Date.now()
};
this.deliveries.set(deliveryId, delivery);
try {
await this.attemptDelivery(delivery, config);
}
catch (error) {
delivery.status = 'failed';
delivery.error = error?.message || String(error);
// Add to retry queue if retries are configured
if (config.retries && config.retries > 0) {
delivery.status = 'retrying';
this.retryQueue.add(deliveryId);
}
}
this.deliveries.set(deliveryId, delivery);
return delivery;
}
/**
* Attempt to deliver a webhook
*/
async attemptDelivery(delivery, config) {
delivery.attempts++;
delivery.last_attempt = Date.now();
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'AuditMySite-Webhook/1.0',
'X-Webhook-Event': delivery.event,
'X-Webhook-Delivery': delivery.id,
...config.headers
};
if (delivery.payload.signature) {
headers['X-Hub-Signature-256'] = `sha256=${delivery.payload.signature}`;
}
const response = await axios_1.default.post(config.url, delivery.payload, {
headers,
timeout: config.timeout || 30000,
validateStatus: (status) => status >= 200 && status < 300
});
delivery.response_code = response.status;
delivery.response_body = typeof response.data === 'string'
? response.data.substring(0, 1000) // Limit response body size
: JSON.stringify(response.data).substring(0, 1000);
delivery.status = 'delivered';
console.log(`โ
Webhook delivered: ${delivery.id} -> ${config.url} (${response.status})`);
}
/**
* Generate HMAC signature for webhook payload
*/
generateSignature(payload, secret) {
const payloadString = JSON.stringify(payload);
return crypto_1.default.createHmac('sha256', secret).update(payloadString).digest('hex');
}
/**
* Verify webhook signature
*/
static verifySignature(payload, signature, secret) {
const expectedSignature = crypto_1.default.createHmac('sha256', secret).update(payload).digest('hex');
const providedSignature = signature.replace('sha256=', '');
return crypto_1.default.timingSafeEqual(Buffer.from(expectedSignature, 'hex'), Buffer.from(providedSignature, 'hex'));
}
/**
* Start retry processor for failed deliveries
*/
startRetryProcessor() {
this.retryInterval = setInterval(() => {
this.processRetries();
}, 60000); // Check every minute
console.log('๐ Webhook retry processor started');
}
/**
* Process retry queue
*/
async processRetries() {
if (this.retryQueue.size === 0) {
return;
}
console.log(`๐ Processing ${this.retryQueue.size} webhook retries...`);
const retries = Array.from(this.retryQueue);
this.retryQueue.clear();
for (const deliveryId of retries) {
const delivery = this.deliveries.get(deliveryId);
if (!delivery) {
continue;
}
const webhook = this.webhooks.get(delivery.webhook_id);
if (!webhook || !webhook.active) {
delivery.status = 'failed';
delivery.error = 'Webhook no longer active';
continue;
}
const maxRetries = webhook.retries || 3;
if (delivery.attempts >= maxRetries) {
delivery.status = 'failed';
delivery.error = `Max retries (${maxRetries}) exceeded`;
console.log(`โ Webhook delivery failed after ${maxRetries} attempts: ${deliveryId}`);
continue;
}
// Exponential backoff: wait longer between retries
const backoffTime = Math.pow(2, delivery.attempts - 1) * 60000; // 1min, 2min, 4min, etc.
const timeSinceLastAttempt = Date.now() - delivery.last_attempt;
if (timeSinceLastAttempt < backoffTime) {
this.retryQueue.add(deliveryId); // Re-queue for later
continue;
}
try {
await this.attemptDelivery(delivery, webhook);
}
catch (error) {
delivery.error = error?.message || String(error);
this.retryQueue.add(deliveryId); // Re-queue for next retry
}
this.deliveries.set(deliveryId, delivery);
}
}
/**
* Get delivery information
*/
getDelivery(deliveryId) {
return this.deliveries.get(deliveryId);
}
/**
* Get deliveries for a webhook
*/
getWebhookDeliveries(webhookId, limit = 50) {
return Array.from(this.deliveries.values())
.filter(delivery => delivery.webhook_id === webhookId)
.sort((a, b) => b.last_attempt - a.last_attempt)
.slice(0, limit);
}
/**
* Get delivery statistics
*/
getDeliveryStats(webhookId) {
const deliveries = webhookId
? Array.from(this.deliveries.values()).filter(d => d.webhook_id === webhookId)
: Array.from(this.deliveries.values());
const stats = {
total: deliveries.length,
delivered: deliveries.filter(d => d.status === 'delivered').length,
failed: deliveries.filter(d => d.status === 'failed').length,
pending: deliveries.filter(d => d.status === 'pending').length,
retrying: deliveries.filter(d => d.status === 'retrying').length,
successRate: 0
};
if (stats.total > 0) {
stats.successRate = (stats.delivered / stats.total) * 100;
}
return stats;
}
/**
* Clean up old deliveries
*/
cleanup(olderThanMs = 7 * 24 * 60 * 60 * 1000) {
const cutoff = Date.now() - olderThanMs;
let cleaned = 0;
for (const [id, delivery] of this.deliveries.entries()) {
if (delivery.last_attempt < cutoff) {
this.deliveries.delete(id);
this.retryQueue.delete(id);
cleaned++;
}
}
console.log(`๐งน Cleaned up ${cleaned} old webhook deliveries`);
return cleaned;
}
/**
* Shutdown webhook manager
*/
shutdown() {
if (this.retryInterval) {
clearInterval(this.retryInterval);
this.retryInterval = null;
}
console.log('๐ Webhook manager shutdown');
}
/**
* Export webhook configuration and deliveries
*/
export() {
return {
webhooks: Object.fromEntries(this.webhooks),
deliveries: Array.from(this.deliveries.values()),
stats: this.getDeliveryStats()
};
}
}
exports.WebhookManager = WebhookManager;
// Global webhook manager instance
exports.webhookManager = new WebhookManager();
//# sourceMappingURL=webhook-manager.js.map