Spaces:
Runtime error
Runtime error
| /** | |
| * FAILOVER CHAIN MANAGER | |
| * Builds redundancy chains and manages automatic failover for API resources | |
| */ | |
| const fs = require('fs'); | |
| class FailoverManager { | |
| constructor(reportPath = './api-monitor-report.json') { | |
| this.reportPath = reportPath; | |
| this.report = null; | |
| this.failoverChains = {}; | |
| } | |
| // Load monitoring report | |
| loadReport() { | |
| try { | |
| const data = fs.readFileSync(this.reportPath, 'utf8'); | |
| this.report = JSON.parse(data); | |
| return true; | |
| } catch (error) { | |
| console.error('Failed to load report:', error.message); | |
| return false; | |
| } | |
| } | |
| // Build failover chains for each data type | |
| buildFailoverChains() { | |
| console.log('\nββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ'); | |
| console.log('β FAILOVER CHAIN BUILDER β'); | |
| console.log('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n'); | |
| const chains = { | |
| ethereumPrice: this.buildPriceChain('ethereum'), | |
| bitcoinPrice: this.buildPriceChain('bitcoin'), | |
| ethereumExplorer: this.buildExplorerChain('ethereum'), | |
| bscExplorer: this.buildExplorerChain('bsc'), | |
| tronExplorer: this.buildExplorerChain('tron'), | |
| rpcEthereum: this.buildRPCChain('ethereum'), | |
| rpcBSC: this.buildRPCChain('bsc'), | |
| newsFeeds: this.buildNewsChain(), | |
| sentiment: this.buildSentimentChain() | |
| }; | |
| this.failoverChains = chains; | |
| // Display chains | |
| for (const [chainName, chain] of Object.entries(chains)) { | |
| this.displayChain(chainName, chain); | |
| } | |
| return chains; | |
| } | |
| // Build price data failover chain | |
| buildPriceChain(coin) { | |
| const chain = []; | |
| // Get market data resources | |
| const marketResources = this.report?.categories?.marketData || []; | |
| // Sort by status and tier | |
| const sorted = marketResources | |
| .filter(r => ['ONLINE', 'DEGRADED'].includes(r.status)) | |
| .sort((a, b) => { | |
| // Prioritize by tier first | |
| if (a.tier !== b.tier) return a.tier - b.tier; | |
| // Then by status | |
| const statusPriority = { ONLINE: 1, DEGRADED: 2, SLOW: 3 }; | |
| return statusPriority[a.status] - statusPriority[b.status]; | |
| }); | |
| for (const resource of sorted) { | |
| chain.push({ | |
| name: resource.name, | |
| url: resource.url, | |
| status: resource.status, | |
| tier: resource.tier, | |
| responseTime: resource.lastCheck?.responseTime | |
| }); | |
| } | |
| return chain; | |
| } | |
| // Build explorer failover chain | |
| buildExplorerChain(blockchain) { | |
| const chain = []; | |
| const explorerResources = this.report?.categories?.blockchainExplorers || []; | |
| const filtered = explorerResources | |
| .filter(r => { | |
| const name = r.name.toLowerCase(); | |
| return (blockchain === 'ethereum' && name.includes('eth')) || | |
| (blockchain === 'bsc' && name.includes('bsc')) || | |
| (blockchain === 'tron' && name.includes('tron')); | |
| }) | |
| .filter(r => ['ONLINE', 'DEGRADED'].includes(r.status)) | |
| .sort((a, b) => a.tier - b.tier); | |
| for (const resource of filtered) { | |
| chain.push({ | |
| name: resource.name, | |
| url: resource.url, | |
| status: resource.status, | |
| tier: resource.tier, | |
| responseTime: resource.lastCheck?.responseTime | |
| }); | |
| } | |
| return chain; | |
| } | |
| // Build RPC node failover chain | |
| buildRPCChain(network) { | |
| const chain = []; | |
| const rpcResources = this.report?.categories?.rpcNodes || []; | |
| const filtered = rpcResources | |
| .filter(r => { | |
| const name = r.name.toLowerCase(); | |
| return name.includes(network.toLowerCase()); | |
| }) | |
| .filter(r => ['ONLINE', 'DEGRADED'].includes(r.status)) | |
| .sort((a, b) => { | |
| if (a.tier !== b.tier) return a.tier - b.tier; | |
| return (a.lastCheck?.responseTime || 999999) - (b.lastCheck?.responseTime || 999999); | |
| }); | |
| for (const resource of filtered) { | |
| chain.push({ | |
| name: resource.name, | |
| url: resource.url, | |
| status: resource.status, | |
| tier: resource.tier, | |
| responseTime: resource.lastCheck?.responseTime | |
| }); | |
| } | |
| return chain; | |
| } | |
| // Build news feed failover chain | |
| buildNewsChain() { | |
| const chain = []; | |
| const newsResources = this.report?.categories?.newsAndSentiment || []; | |
| const filtered = newsResources | |
| .filter(r => ['ONLINE', 'DEGRADED'].includes(r.status)) | |
| .sort((a, b) => a.tier - b.tier); | |
| for (const resource of filtered) { | |
| chain.push({ | |
| name: resource.name, | |
| url: resource.url, | |
| status: resource.status, | |
| tier: resource.tier, | |
| responseTime: resource.lastCheck?.responseTime | |
| }); | |
| } | |
| return chain; | |
| } | |
| // Build sentiment data failover chain | |
| buildSentimentChain() { | |
| const chain = []; | |
| const newsResources = this.report?.categories?.newsAndSentiment || []; | |
| const filtered = newsResources | |
| .filter(r => r.name.toLowerCase().includes('fear') || | |
| r.name.toLowerCase().includes('greed') || | |
| r.name.toLowerCase().includes('sentiment')) | |
| .filter(r => ['ONLINE', 'DEGRADED'].includes(r.status)); | |
| for (const resource of filtered) { | |
| chain.push({ | |
| name: resource.name, | |
| url: resource.url, | |
| status: resource.status, | |
| tier: resource.tier, | |
| responseTime: resource.lastCheck?.responseTime | |
| }); | |
| } | |
| return chain; | |
| } | |
| // Display failover chain | |
| displayChain(chainName, chain) { | |
| console.log(`\nπ ${chainName.toUpperCase()} Failover Chain:`); | |
| console.log('β'.repeat(60)); | |
| if (chain.length === 0) { | |
| console.log(' β οΈ No available resources'); | |
| return; | |
| } | |
| chain.forEach((resource, index) => { | |
| const arrow = index === 0 ? 'π―' : ' β'; | |
| const priority = index === 0 ? '[PRIMARY]' : index === 1 ? '[BACKUP]' : `[BACKUP-${index}]`; | |
| const tierBadge = `[TIER-${resource.tier}]`; | |
| const rt = resource.responseTime ? `${resource.responseTime}ms` : 'N/A'; | |
| console.log(` ${arrow} ${priority.padEnd(12)} ${resource.name.padEnd(25)} ${resource.status.padEnd(10)} ${rt.padStart(8)} ${tierBadge}`); | |
| }); | |
| } | |
| // Generate failover configuration file | |
| exportFailoverConfig(filename = 'failover-config.json') { | |
| const config = { | |
| generatedAt: new Date().toISOString(), | |
| chains: this.failoverChains, | |
| usage: { | |
| description: 'Automatic failover configuration for API resources', | |
| example: ` | |
| // Example usage in your application: | |
| const failoverConfig = require('./failover-config.json'); | |
| async function fetchWithFailover(chainName, fetchFunction) { | |
| const chain = failoverConfig.chains[chainName]; | |
| for (const resource of chain) { | |
| try { | |
| const result = await fetchFunction(resource.url); | |
| return result; | |
| } catch (error) { | |
| console.log(\`Failed \${resource.name}, trying next...\`); | |
| continue; | |
| } | |
| } | |
| throw new Error('All resources in chain failed'); | |
| } | |
| // Use it: | |
| const data = await fetchWithFailover('ethereumPrice', async (url) => { | |
| const response = await fetch(url + '/api/v3/simple/price?ids=ethereum&vs_currencies=usd'); | |
| return response.json(); | |
| }); | |
| ` | |
| } | |
| }; | |
| fs.writeFileSync(filename, JSON.stringify(config, null, 2)); | |
| console.log(`\nβ Failover configuration exported to ${filename}`); | |
| } | |
| // Identify categories with single point of failure | |
| identifySinglePointsOfFailure() { | |
| console.log('\nββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ'); | |
| console.log('β SINGLE POINT OF FAILURE ANALYSIS β'); | |
| console.log('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n'); | |
| const spofs = []; | |
| for (const [chainName, chain] of Object.entries(this.failoverChains)) { | |
| const onlineCount = chain.filter(r => r.status === 'ONLINE').length; | |
| if (onlineCount === 0) { | |
| spofs.push({ | |
| chain: chainName, | |
| severity: 'CRITICAL', | |
| message: 'Zero available resources' | |
| }); | |
| } else if (onlineCount === 1) { | |
| spofs.push({ | |
| chain: chainName, | |
| severity: 'HIGH', | |
| message: 'Only one resource available (SPOF)' | |
| }); | |
| } else if (onlineCount === 2) { | |
| spofs.push({ | |
| chain: chainName, | |
| severity: 'MEDIUM', | |
| message: 'Only two resources available' | |
| }); | |
| } | |
| } | |
| if (spofs.length === 0) { | |
| console.log(' β No single points of failure detected\n'); | |
| } else { | |
| for (const spof of spofs) { | |
| const icon = spof.severity === 'CRITICAL' ? 'π΄' : | |
| spof.severity === 'HIGH' ? 'π ' : 'π‘'; | |
| console.log(` ${icon} [${spof.severity}] ${spof.chain}: ${spof.message}`); | |
| } | |
| console.log(); | |
| } | |
| return spofs; | |
| } | |
| // Generate redundancy report | |
| generateRedundancyReport() { | |
| console.log('\nββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ'); | |
| console.log('β REDUNDANCY ANALYSIS REPORT β'); | |
| console.log('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n'); | |
| const categories = this.report?.categories || {}; | |
| for (const [category, resources] of Object.entries(categories)) { | |
| const total = resources.length; | |
| const online = resources.filter(r => r.status === 'ONLINE').length; | |
| const degraded = resources.filter(r => r.status === 'DEGRADED').length; | |
| const offline = resources.filter(r => r.status === 'OFFLINE').length; | |
| let indicator = 'β'; | |
| if (online === 0) indicator = 'β'; | |
| else if (online === 1) indicator = 'β '; | |
| else if (online >= 3) indicator = 'ββ'; | |
| console.log(` ${indicator} ${category.padEnd(25)} Online: ${online}/${total} Degraded: ${degraded} Offline: ${offline}`); | |
| } | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // MAIN EXECUTION | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function main() { | |
| const manager = new FailoverManager(); | |
| if (!manager.loadReport()) { | |
| console.error('\nβ Please run the monitor first: node api-monitor.js'); | |
| process.exit(1); | |
| } | |
| // Build failover chains | |
| manager.buildFailoverChains(); | |
| // Export configuration | |
| manager.exportFailoverConfig(); | |
| // Identify SPOFs | |
| manager.identifySinglePointsOfFailure(); | |
| // Generate redundancy report | |
| manager.generateRedundancyReport(); | |
| console.log('\nβ Failover analysis complete\n'); | |
| } | |
| if (require.main === module) { | |
| main().catch(console.error); | |
| } | |
| module.exports = FailoverManager; | |