//instance-health.js const HEALTH_CHECK_INTERVAL = 5 * 60 * 1000; const HEALTH_CHECK_TIMEOUT = 8000; const MAX_CONSECUTIVE_FAILURES = 3; const INITIAL_DELAY = 10000; const IS_DEV = typeof window !== 'undefined' && window.location?.hostname === 'localhost'; class InstanceHealthChecker { constructor() { this.healthCache = new Map(); this.checkTimers = {}; this.listeners = []; } onHealthUpdate(callback) { this.listeners.push(callback); } notifyListeners(instances) { this.listeners.forEach((cb) => cb(instances)); } async checkInstance(url, type = 'api') { const testPath = '/'; const fullUrl = url.endsWith('/') ? `${url}${testPath.substring(1)}` : `${url}${testPath}`; const startTime = Date.now(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT); try { const response = await fetch(fullUrl, { method: 'GET', signal: controller.signal, }); clearTimeout(timeoutId); const latency = Date.now() - startTime; const existing = this.healthCache.get(url) || { url, failures: 0 }; const status = response.ok || response.status === 401 || response.status === 404 ? 'online' : 'degraded'; this.healthCache.set(url, { ...existing, status, latency, lastCheck: Date.now(), failures: response.ok ? 0 : existing.failures, type, }); return { url, status, latency, online: response.ok }; } catch (error) { clearTimeout(timeoutId); const existing = this.healthCache.get(url) || { url, failures: 0 }; const newFailures = existing.failures + 1; const status = newFailures >= MAX_CONSECUTIVE_FAILURES ? 'offline' : 'degraded'; this.healthCache.set(url, { url, status, latency: null, lastCheck: Date.now(), failures: newFailures, error: error.message, type, }); return { url, status, latency: null, online: false, error: error.message }; } } async checkAll(instances, type = 'api') { const checks = instances.map((inst) => { const url = typeof inst === 'string' ? inst : inst.url; return this.checkInstance(url, type); }); const results = await Promise.allSettled(checks); return results.map((r) => r.value || { url: 'unknown', status: 'offline', online: false }); } getHealthStatus(url) { return this.healthCache.get(url) || { url, status: 'unknown', latency: null, lastCheck: null }; } getHealthyInstances(instances, type = 'api') { return instances.filter((inst) => { const url = typeof inst === 'string' ? inst : inst.url; const health = this.healthCache.get(url); return !health || health.status === 'online' || health.status === 'degraded'; }); } getHealthyAndSorted(instances, type = 'api') { const withHealth = instances.map((inst) => { const url = typeof inst === 'string' ? inst : inst.url; const health = this.healthCache.get(url); return { ...inst, healthStatus: health?.status || 'unknown', latency: health?.latency ?? null, }; }); return withHealth.sort((a, b) => { if (a.healthStatus === 'offline' && b.healthStatus !== 'offline') return 1; if (b.healthStatus === 'offline' && a.healthStatus !== 'offline') return -1; if (a.latency !== null && b.latency !== null) return a.latency - b.latency; if (a.latency !== null) return -1; if (b.latency !== null) return 1; return 0; }); } startPeriodicCheck(instancesGetter, type = 'api') { if (this.checkTimers[type]) { clearInterval(this.checkTimers[type]); } if (IS_DEV) { console.log(`Health check disabled in dev mode for ${type}`); return; } const check = async () => { const instances = await instancesGetter(type); if (!instances || instances.length === 0) return; const results = await this.checkAll(instances, type); this.notifyListeners(results); }; setTimeout(check, INITIAL_DELAY); this.checkTimers[type] = setInterval(check, HEALTH_CHECK_INTERVAL); } stopPeriodicCheck(type = 'api') { if (this.checkTimers[type]) { clearInterval(this.checkTimers[type]); delete this.checkTimers[type]; } } stopAllChecks() { Object.keys(this.checkTimers).forEach((type) => this.stopPeriodicCheck(type)); } async quickCheck(url, type = 'api') { return this.checkInstance(url, type); } getCachedStatus(url) { return this.healthCache.get(url); } clearCache() { this.healthCache.clear(); } } export const instanceHealth = new InstanceHealthChecker();