|
| 1 | +/** |
| 2 | + * server.js |
| 3 | + * |
| 4 | + * Main Entry Point for combined Soroban Indexer and WebSocket streaming server. |
| 5 | + * This file orchestrates the real-time data flow from the blockchain to the frontend. |
| 6 | + */ |
| 7 | + |
| 8 | +const WebSocket = require('ws'); |
| 9 | +const wsEvents = require('./services/wsEvents'); |
| 10 | +const { startIndexer } = require('./services/eventIndexer'); |
| 11 | + |
| 12 | +// Config and Port Setup |
| 13 | +const WS_PORT = process.env.WS_PORT || 3001; |
| 14 | +const wss = new WebSocket.Server({ port: WS_PORT }); |
| 15 | + |
| 16 | +console.log('--- 🌐 TradeFlow Real-Time Stream Server ---'); |
| 17 | +console.log(`📡 WebSocket server running on ws://localhost:${WS_PORT}`); |
| 18 | + |
| 19 | +// Connection tracking |
| 20 | +let activeConnections = 0; |
| 21 | + |
| 22 | +/** |
| 23 | + * Listen for incoming WebSocket connections. |
| 24 | + */ |
| 25 | +wss.on('connection', (ws) => { |
| 26 | + activeConnections++; |
| 27 | + console.log(`✅ New Web3 client connected. Active: ${activeConnections}`); |
| 28 | + |
| 29 | + // Initial Connection ACK |
| 30 | + ws.send(JSON.stringify({ |
| 31 | + event: 'INDEXER_CONNECTED', |
| 32 | + status: 'ONLINE', |
| 33 | + timestamp: new Date().toISOString() |
| 34 | + })); |
| 35 | + |
| 36 | + ws.on('close', () => { |
| 37 | + activeConnections--; |
| 38 | + console.log(`❌ Web3 client disconnected. Active: ${activeConnections}`); |
| 39 | + }); |
| 40 | + |
| 41 | + ws.on('error', (err) => { |
| 42 | + console.error('⚠️ WS Socket Error:', err.message); |
| 43 | + }); |
| 44 | +}); |
| 45 | + |
| 46 | +/** |
| 47 | + * BROADCASTER: Listens to the internal 'newTrade' event emitter. |
| 48 | + * Broadcasts every new blockchain event caught by the Indexer daemon |
| 49 | + * to all connected browser clients. |
| 50 | + */ |
| 51 | +wsEvents.on('newTrade', (tradeData) => { |
| 52 | + console.log(`📣 BROADCASTING: New trade found in pool ${tradeData.poolId.slice(0, 8)}...`); |
| 53 | + |
| 54 | + const payload = JSON.stringify({ |
| 55 | + event: 'NEW_TRADE_EVENT', |
| 56 | + data: tradeData, |
| 57 | + receivedAt: new Date().toISOString() |
| 58 | + }); |
| 59 | + |
| 60 | + // Iterative broadcast to all active subscribers |
| 61 | + wss.clients.forEach((client) => { |
| 62 | + if (client.readyState === WebSocket.OPEN) { |
| 63 | + client.send(payload); |
| 64 | + } |
| 65 | + }); |
| 66 | +}); |
| 67 | + |
| 68 | +/** |
| 69 | + * 🚀 START DAEMON ORCHESTRATION |
| 70 | + * We start the Soroban Indexer in the same Node.js process to bridge |
| 71 | + * the blockchain data with the WebSocket emitter via internal memory. |
| 72 | + */ |
| 73 | +startIndexer().catch((err) => { |
| 74 | + console.error('❌ CRITICAL ERROR: Event Indexer failed to start:', err.message); |
| 75 | + process.exit(1); |
| 76 | +}); |
| 77 | + |
| 78 | +// Process Management |
| 79 | +process.on('SIGTERM', () => { |
| 80 | + console.log('🛑 Closing WebSocket connections and shutting down.'); |
| 81 | + wss.close(); |
| 82 | + process.exit(0); |
| 83 | +}); |
0 commit comments