const express = require("express"); const http = require("http"); const socketIo = require("socket.io"); const cors = require("cors"); const axios = require("axios"); require("dotenv").config(); const app = express(); const server = http.createServer(app); let cachedPrices = null; let lastFetchTime = 0; const CACHE_DURATION = 30000; const io = socketIo(server, { cors: { origin: process.env.CORS_ORIGIN || "*", methods: ["GET", "POST"], }, }); // ========== (Middleware) ========== app.use(cors()); app.use(express.json()); // ========== HTTP Route ========== app.get("/health", (req, res) => { res.json({ status: "OK", message: "Server is running", timestamp: new Date().toISOString(), }); }); // ========== Socket.IO Logic ========== // Number of clients let connectedClients = 0; // Trigger when client connected io.on("connection", (socket) => { connectedClients++; console.log(`✅ Client Connected: ${socket.id}`); console.log(`📊 Current Number of Clients: ${connectedClients}`); // Broadcast to all clients io.emit("clientCount", connectedClients); // ========== Monitoring client side events ========== // Request Data socket.on("requestData", async (dataType) => { console.log(`📤 Request Data: ${dataType}`); const now = new Date(); try { let data; if (dataType === "crypto") { if (cachedPrices && now - lastFetchTime < CACHE_DURATION) { console.log("📦 Use caching data"); data = cachedPrices; } else { // Get data from "CoinGecko API" const response = await axios.get( "https://api.coingecko.com/api/v3/simple/price", { params: { // list of crypto requested to be shown ids: "bitcoin,ethereum,cardano,solana,ripple", vs_currencies: "usd", include_market_cap: true, include_24hr_vol: true, include_24hr_change: true, }, } ); // Construct data format data = { type: "crypto", timestamp: new Date(), prices: response.data, }; // Update Caches cachedPrices = data; lastFetchTime = now; console.log("✅ Cached New Data"); } } // Emit data to client side socket.emit("data", data); console.log(`✅ Data sent to client: ${socket.id}`); } catch (error) { console.error("❌ Error on Getting Data:", error.message); // Emit Error message to client side socket.emit("error", { message: "Failed to fetch data", details: error.message, }); } }); // ========== Push Data by interval ========== // Push every 5 seconds const dataInterval = setInterval(async () => { try { const now = new Date(); if (cachedPrices && now - lastFetchTime < CACHE_DURATION) { socket.emit("data", cachedPrices); console.log(`[${now.toISOString()}]📦 Push Cached Data`); return; } // Get latest data const response = await axios.get( "https://api.coingecko.com/api/v3/simple/price", { params: { ids: "bitcoin,ethereum,cardano,solana,ripple", vs_currencies: "usd", include_market_cap: true, include_24hr_vol: true, include_24hr_change: true, }, } ); const data = { type: "crypto", timestamp: new Date(), prices: response.data, }; cachedPrices = data; lastFetchTime = now; socket.emit("data", data); } catch (error) { console.error("❌ Error on pushing:", error.message); } }, CACHE_DURATION); // run every 30 seconds // ========== Client Disconnected ========== // Trigger when client disconnected socket.on("disconnect", () => { connectedClients--; console.log(`❌ Client Disconnected: ${socket.id}`); console.log(`📊 Current Number of Clients: ${connectedClients}`); // Broadcast number of clients io.emit("clientCount", connectedClients); // Clear client's interval clearInterval(dataInterval); }); }); // ========== Start service ========== const PORT = process.env.PORT || 5000; server.listen(PORT, () => { console.log(""); console.log("═══════════════════════════════════════"); console.log(`✅ Server running on: http://localhost:${PORT}`); console.log("📡 WebSocket ready to connect"); console.log("🔗 CORS Source:", process.env.CORS_ORIGIN || "*"); console.log("═══════════════════════════════════════"); console.log(""); }); // ========== Error Handling ========== // Exception Handling process.on("unhandledRejection", (err) => { console.error("❌ Exceptions:", err); }); process.on("uncaughtException", (err) => { console.error("❌ Exceptions:", err); process.exit(1); });