|
|
|
@ -244,96 +244,4 @@ export class WebhookManager {
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===== Auto-ensure loop con reintentos y backoff =====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static autoEnsureTimer: any = null;
|
|
|
|
|
|
|
|
private static autoEnsureActive: boolean = false;
|
|
|
|
|
|
|
|
private static autoEnsureAttempts: number = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static getRetryConfig() {
|
|
|
|
|
|
|
|
const n = (v: any, d: number) => {
|
|
|
|
|
|
|
|
const x = Number(v);
|
|
|
|
|
|
|
|
return Number.isFinite(x) && x >= 0 ? x : d;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const initial = n(process.env.WEBHOOK_RETRY_INITIAL_DELAY_MS, 1000);
|
|
|
|
|
|
|
|
const max = Math.max(initial, n(process.env.WEBHOOK_RETRY_MAX_DELAY_MS, 60000));
|
|
|
|
|
|
|
|
const degraded = Math.max(max, n(process.env.WEBHOOK_RETRY_DEGRADED_INTERVAL_MS, 300000));
|
|
|
|
|
|
|
|
const jitter = String(process.env.WEBHOOK_RETRY_JITTER || 'true').toLowerCase();
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
initialDelayMs: initial,
|
|
|
|
|
|
|
|
maxDelayMs: max,
|
|
|
|
|
|
|
|
degradedIntervalMs: degraded,
|
|
|
|
|
|
|
|
jitter: ['true', '1', 'yes', 'on'].includes(jitter),
|
|
|
|
|
|
|
|
maxAttempts: Number.isFinite(Number(process.env.WEBHOOK_RETRY_MAX_ATTEMPTS))
|
|
|
|
|
|
|
|
? Math.max(0, Number(process.env.WEBHOOK_RETRY_MAX_ATTEMPTS))
|
|
|
|
|
|
|
|
: Infinity
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static jitter(val: number, enabled: boolean): number {
|
|
|
|
|
|
|
|
if (!enabled) return val;
|
|
|
|
|
|
|
|
const factor = 0.5 + Math.random(); // 0.5x - 1.5x
|
|
|
|
|
|
|
|
return Math.max(1, Math.floor(val * factor));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static nextDelayMs(attempt: number, cfg: ReturnType<typeof this.getRetryConfig>): number {
|
|
|
|
|
|
|
|
// attempt 0 -> initial, 1 -> 2x, etc. capped at max, luego degradado cada degradedIntervalMs
|
|
|
|
|
|
|
|
const pow = Math.min(cfg.maxDelayMs, cfg.initialDelayMs * Math.pow(2, attempt));
|
|
|
|
|
|
|
|
return this.jitter(pow, cfg.jitter);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static startAutoEnsure(): void {
|
|
|
|
|
|
|
|
if (this.autoEnsureActive) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.autoEnsureActive = true;
|
|
|
|
|
|
|
|
this.autoEnsureAttempts = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cfg = this.getRetryConfig();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const attemptOnce = async () => {
|
|
|
|
|
|
|
|
// Validación de config: si falta algo, abortar sin reintentos
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
this.validateConfig();
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
console.error('❌ Webhook auto-ensure aborted due to invalid configuration:', e instanceof Error ? e.message : e);
|
|
|
|
|
|
|
|
this.stopAutoEnsure();
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await this.registerWebhook();
|
|
|
|
|
|
|
|
const ok = await this.verifyWebhook();
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
|
|
console.log('✅ Webhook is active');
|
|
|
|
|
|
|
|
this.stopAutoEnsure();
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('verify returned false');
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
if (!this.autoEnsureActive) return; // pudo ser parado durante espera
|
|
|
|
|
|
|
|
const a = this.autoEnsureAttempts++;
|
|
|
|
|
|
|
|
const delay = a < 10 ? this.nextDelayMs(a, cfg) : cfg.degradedIntervalMs;
|
|
|
|
|
|
|
|
const msg = err instanceof Error ? `${err.message}` : String(err);
|
|
|
|
|
|
|
|
console.warn(`⚠️ Webhook ensure attempt #${a + 1} failed: ${msg}. Retrying in ${delay}ms`);
|
|
|
|
|
|
|
|
this.autoEnsureTimer = setTimeout(() => {
|
|
|
|
|
|
|
|
// Evitar múltiples overlapped
|
|
|
|
|
|
|
|
if (!this.autoEnsureActive) return;
|
|
|
|
|
|
|
|
attemptOnce();
|
|
|
|
|
|
|
|
}, delay);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Disparar el primer intento sin esperar
|
|
|
|
|
|
|
|
attemptOnce();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static stopAutoEnsure(): void {
|
|
|
|
|
|
|
|
if (this.autoEnsureTimer) {
|
|
|
|
|
|
|
|
try { clearTimeout(this.autoEnsureTimer); } catch {}
|
|
|
|
|
|
|
|
this.autoEnsureTimer = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.autoEnsureActive = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|