/**
* Enhanced Crypto API Hub - Seamless Backend Integration
* Features:
* - Real backend data fetching with self-healing
* - Automatic retry and fallback mechanisms
* - Smooth error handling
* - Live API testing with CORS proxy
* - Export functionality
*/
import { showToast } from '../shared/js/components/toast-helper.js';
import { showLoading, hideLoading } from '../shared/js/components/loading-helper.js';
class CryptoAPIHub {
constructor() {
this.services = null;
this.currentFilter = 'all';
this.searchQuery = '';
this.retryCount = 0;
this.maxRetries = 3;
this.fallbackData = this.getFallbackData();
this.corsProxyEnabled = true;
}
/**
* Initialize the hub
*/
async init() {
console.log('[CryptoAPIHub] Initializing...');
// Show loading state
this.renderLoadingState();
// Fetch services data with self-healing
await this.fetchServicesWithHealing();
// Render services
this.renderServices();
// Setup event listeners
this.setupEventListeners();
// Update statistics
this.updateStats();
console.log('[CryptoAPIHub] Initialized successfully');
}
/**
* Fetch services with self-healing mechanism
*/
async fetchServicesWithHealing() {
try {
console.log('[CryptoAPIHub] Fetching services from backend...');
// Try to fetch from backend
const response = await this.fetchFromBackend();
if (response && response.categories) {
this.services = response;
this.retryCount = 0;
showToast('✅', 'Services loaded successfully', 'success');
return;
}
} catch (error) {
console.warn('[CryptoAPIHub] Backend fetch failed:', error);
}
// Self-healing: Try fallback
await this.healWithFallback();
}
/**
* Fetch from backend
*/
async fetchFromBackend() {
try {
// Try the crypto-hub API endpoint
const response = await fetch('/api/crypto-hub/services', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
return await response.json();
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
console.error('[CryptoAPIHub] Backend error:', error);
throw error;
}
}
/**
* Self-healing with fallback data
*/
async healWithFallback() {
console.log('[CryptoAPIHub] Activating self-healing mechanism...');
if (this.retryCount < this.maxRetries) {
this.retryCount++;
showToast('🔄', `Retrying... (${this.retryCount}/${this.maxRetries})`, 'info');
// Wait before retry
await this.sleep(2000 * this.retryCount);
// Try again
await this.fetchServicesWithHealing();
return;
}
// All retries failed, use fallback data
console.log('[CryptoAPIHub] Using fallback data...');
this.services = this.fallbackData;
showToast('⚠️', 'Using cached data (backend unavailable)', 'warning');
}
/**
* Get fallback data (embedded for self-healing)
*/
getFallbackData() {
return {
metadata: {
version: "1.0.0",
total_services: 74,
total_endpoints: 150,
api_keys_count: 10,
last_updated: new Date().toISOString()
},
categories: {
explorer: {
name: "Blockchain Explorers",
description: "Track transactions and addresses",
services: [
{
name: "Etherscan",
url: "https://api.etherscan.io/api",
key: "SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2",
endpoints: [
"?module=account&action=balance&address={address}&apikey={KEY}",
"?module=gastracker&action=gasoracle&apikey={KEY}"
]
},
{
name: "BscScan",
url: "https://api.bscscan.com/api",
key: "K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT",
endpoints: ["?module=account&action=balance&address={address}&apikey={KEY}"]
},
{
name: "TronScan",
url: "https://apilist.tronscanapi.com/api",
key: "7ae72726-bffe-4e74-9c33-97b761eeea21",
endpoints: ["/account?address={address}"]
}
]
},
market: {
name: "Market Data",
description: "Real-time prices and market metrics",
services: [
{
name: "CoinGecko",
url: "https://api.coingecko.com/api/v3",
key: "",
endpoints: [
"/simple/price?ids=bitcoin,ethereum&vs_currencies=usd",
"/coins/markets?vs_currency=usd&per_page=100"
]
},
{
name: "CoinMarketCap",
url: "https://pro-api.coinmarketcap.com/v1",
key: "04cf4b5b-9868-465c-8ba0-9f2e78c92eb1",
endpoints: ["/cryptocurrency/quotes/latest?symbol=BTC&convert=USD"]
},
{
name: "Binance",
url: "https://api.binance.com/api/v3",
key: "",
endpoints: ["/ticker/price?symbol=BTCUSDT"]
}
]
},
news: {
name: "News & Media",
description: "Crypto news and updates",
services: [
{
name: "CryptoPanic",
url: "https://cryptopanic.com/api/v1",
key: "",
endpoints: ["/posts/?auth_token={KEY}"]
},
{
name: "NewsAPI",
url: "https://newsapi.org/v2",
key: "pub_346789abc123def456789ghi012345jkl",
endpoints: ["/everything?q=crypto&apiKey={KEY}"]
}
]
},
sentiment: {
name: "Sentiment Analysis",
description: "Market sentiment indicators",
services: [
{
name: "Fear & Greed",
url: "https://api.alternative.me/fng/",
key: "",
endpoints: ["?limit=1", "?limit=30"]
},
{
name: "LunarCrush",
url: "https://api.lunarcrush.com/v2",
key: "",
endpoints: ["?data=assets&key={KEY}"]
}
]
},
analytics: {
name: "Analytics & Tools",
description: "Advanced analytics and whale tracking",
services: [
{
name: "Whale Alert",
url: "https://api.whale-alert.io/v1",
key: "",
endpoints: ["/transactions?api_key={KEY}&min_value=1000000"]
},
{
name: "Glassnode",
url: "https://api.glassnode.com/v1",
key: "",
endpoints: []
},
{
name: "Hugging Face",
url: "https://api-inference.huggingface.co/models",
// API key should be retrieved from backend that reads HF_API_TOKEN env var
key: "",
endpoints: ["/ElKulako/cryptobert"]
}
]
}
}
};
}
/**
* Render services grid
*/
renderServices() {
const grid = document.getElementById('servicesGrid');
if (!grid) return;
let html = '';
let count = 0;
const categories = this.services?.categories || {};
Object.entries(categories).forEach(([categoryKey, category]) => {
const services = category.services || [];
services.forEach((service, index) => {
// Apply filter
if (this.currentFilter !== 'all' && categoryKey !== this.currentFilter) {
return;
}
// Apply search
if (this.searchQuery) {
const searchLower = this.searchQuery.toLowerCase();
const matchesSearch =
service.name.toLowerCase().includes(searchLower) ||
service.url.toLowerCase().includes(searchLower) ||
categoryKey.toLowerCase().includes(searchLower);
if (!matchesSearch) return;
}
count++;
const hasKey = service.key ? `🔑 Has Key` : '';
const endpoints = service.endpoints?.length || 0;
html += `
${categoryKey}
${endpoints > 0 ? `${endpoints} endpoints` : ''}
${hasKey}
${this.renderEndpoints(service, categoryKey)}
`;
});
});
if (html === '') {
html = '';
}
grid.innerHTML = html;
}
/**
* Render endpoints for a service
*/
renderEndpoints(service, category) {
const endpoints = service.endpoints || [];
if (endpoints.length === 0) {
return 'Base endpoint available
';
}
let html = '';
endpoints.slice(0, 2).forEach(endpoint => {
const fullUrl = service.url + endpoint;
const encodedUrl = encodeURIComponent(fullUrl);
html += `
`;
});
if (endpoints.length > 2) {
html += `
+${endpoints.length - 2} more endpoints
`;
}
html += '
';
return html;
}
/**
* Get icon for category
*/
getIcon(category) {
const icons = {
explorer: '',
market: '',
news: '',
sentiment: '',
analytics: ''
};
return icons[category] || icons.analytics;
}
/**
* Render loading state
*/
renderLoadingState() {
const grid = document.getElementById('servicesGrid');
if (!grid) return;
grid.innerHTML = `
`;
}
/**
* Update statistics
*/
updateStats() {
const metadata = this.services?.metadata || {};
const statsData = {
services: metadata.total_services || 74,
endpoints: metadata.total_endpoints || 150,
keys: metadata.api_keys_count || 10
};
// Update stat values
document.querySelectorAll('.stat-value').forEach((el, index) => {
const values = [statsData.services, statsData.endpoints + '+', statsData.keys];
if (el && values[index]) {
el.textContent = values[index];
}
});
}
/**
* Setup event listeners
*/
setupEventListeners() {
// Search input
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
this.searchQuery = e.target.value;
this.renderServices();
});
}
// Filter tabs
document.querySelectorAll('.filter-tab').forEach(tab => {
tab.addEventListener('click', (e) => {
this.setFilter(e.target.dataset.filter);
});
});
// Method buttons
document.querySelectorAll('.method-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const method = e.target.dataset.method;
this.setMethod(method);
});
});
// Update last update time
this.updateLastUpdateTime();
}
/**
* Set HTTP method
*/
setMethod(method) {
this.currentMethod = method;
// Update active button
document.querySelectorAll('.method-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.method === method) {
btn.classList.add('active');
}
});
// Show/hide body field
const bodyGroup = document.getElementById('bodyGroup');
if (bodyGroup) {
bodyGroup.style.display = (method === 'POST' || method === 'PUT') ? 'block' : 'none';
}
}
/**
* Update last update time
*/
updateLastUpdateTime() {
const el = document.getElementById('lastUpdate');
if (el) {
el.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
}
}
/**
* Set filter
*/
setFilter(filter) {
this.currentFilter = filter;
// Update active tab
document.querySelectorAll('.filter-tab').forEach(t => t.classList.remove('active'));
const activeTab = document.querySelector(`[data-filter="${filter}"]`);
if (activeTab) activeTab.classList.add('active');
// Re-render
this.renderServices();
}
/**
* Copy text to clipboard
*/
async copyText(text) {
try {
await navigator.clipboard.writeText(text);
showToast('✅', 'Copied to clipboard!', 'success');
} catch (error) {
showToast('❌', 'Failed to copy', 'error');
}
}
/**
* Test endpoint
*/
async testEndpoint(url, key) {
// Replace key placeholders
let finalUrl = url;
if (key) {
finalUrl = url.replace('{KEY}', key).replace('{key}', key);
}
// Open tester modal with URL
this.openTester(finalUrl);
}
/**
* Open API tester modal
*/
openTester(url = '') {
const modal = document.getElementById('testerModal');
const urlInput = document.getElementById('testUrl');
if (modal) {
modal.classList.add('active');
if (urlInput && url) {
urlInput.value = url;
}
}
}
/**
* Close API tester modal
*/
closeTester() {
const modal = document.getElementById('testerModal');
if (modal) {
modal.classList.remove('active');
}
}
/**
* Send API test request
*/
async sendTestRequest() {
const url = document.getElementById('testUrl')?.value;
const headersText = document.getElementById('testHeaders')?.value || '{}';
const bodyText = document.getElementById('testBody')?.value;
const responseBox = document.getElementById('responseBox');
const responseJson = document.getElementById('responseJson');
const method = this.currentMethod || 'GET';
if (!url) {
showToast('⚠️', 'Please enter a URL', 'warning');
return;
}
if (responseBox) responseBox.style.display = 'block';
if (responseJson) responseJson.textContent = '⏳ Sending request...';
try {
// Use CORS proxy if enabled
const requestUrl = this.corsProxyEnabled
? `/api/crypto-hub/test`
: url;
const requestOptions = this.corsProxyEnabled
? {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: url,
method: method,
headers: JSON.parse(headersText),
body: bodyText
})
}
: {
method: method,
headers: JSON.parse(headersText),
body: (method === 'POST' || method === 'PUT') ? bodyText : undefined
};
const response = await fetch(requestUrl, requestOptions);
const data = await response.json();
if (responseJson) {
responseJson.textContent = JSON.stringify(data, null, 2);
}
showToast('✅', 'Request successful!', 'success');
} catch (error) {
if (responseJson) {
responseJson.textContent = `❌ Error: ${error.message}\n\nThis might be due to CORS policy. Try using the CORS proxy.`;
}
showToast('❌', 'Request failed', 'error');
}
}
/**
* Export services as JSON
*/
exportJSON() {
const data = {
metadata: {
exported_at: new Date().toISOString(),
...this.services?.metadata
},
services: this.services
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `crypto-api-hub-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
showToast('✅', 'JSON exported successfully!', 'success');
}
/**
* Sleep utility
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
window.cryptoAPIHub = new CryptoAPIHub();
window.cryptoAPIHub.init();
});
// Export for module usage
export default CryptoAPIHub;