193 lines
5.0 KiB
JavaScript
193 lines
5.0 KiB
JavaScript
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);
|
|
});
|