diff --git a/app.js b/app.js index 9b4729b..30c9a47 100644 --- a/app.js +++ b/app.js @@ -6,6 +6,8 @@ const favicon = require('serve-favicon'); const app = express(); const port = process.env.PORT || 3512; +app.set('trust proxy', true); + // Middleware app.use(bodyParser.json({ limit: "10mb" })); app.use(bodyParser.urlencoded({ extended: true, limit: "10mb" })); @@ -13,6 +15,10 @@ app.use(express.static(__dirname)); app.use(express.static(path.join(__dirname, 'public'))); app.use('/images', express.static(path.join(__dirname, 'src/images'))); app.use(favicon(path.join(__dirname, 'src', 'images', 'favicon.ico'))); +app.use(bodyParser.json({ limit: "10mb", verify: (req, res, buf) => { + req.rawBody = buf.toString(); +}})); +// app.use(express.raw({ type: 'application/json', limit: '10mb' })); const fs = require('fs'); @@ -183,7 +189,7 @@ const handleApiError = (error, res) => { // API endpoint to fetch stats app.post("/api/stats", async (req, res) => { console.log("Received request for /api/stats"); - console.log(`Request IP: ${req.ip || req.connection.remoteAddress}`); + console.log(`Request IP: ${req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress}`); console.log(`Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, @@ -421,7 +427,7 @@ app.post("/api/stats", async (req, res) => { // API endpoint to fetch recent matches app.post("/api/matches", async (req, res) => { console.log("Received request for /api/matches"); - console.log(`Request IP: ${req.ip || req.connection.remoteAddress}`); + console.log(`Request IP: ${req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress}`); console.log(`Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, @@ -560,7 +566,7 @@ app.post("/api/matches", async (req, res) => { // API endpoint to fetch match info app.post("/api/matchInfo", async (req, res) => { console.log("Received request for /api/matchInfo"); - console.log(`Request IP: ${req.ip || req.connection.remoteAddress}`); + console.log(`Request IP: ${req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress}`); console.log(`Request JSON: ${JSON.stringify({ matchId: req.body.matchId, platform: req.body.platform, @@ -686,7 +692,7 @@ app.post("/api/matchInfo", async (req, res) => { // API endpoint for user-related API calls app.post("/api/user", async (req, res) => { console.log("Received request for /api/user"); - console.log(`Request IP: ${req.ip || req.connection.remoteAddress}`); + console.log(`Request IP: ${req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress}`); console.log(`Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, @@ -812,7 +818,7 @@ app.post("/api/user", async (req, res) => { // API endpoint for fuzzy search app.post("/api/search", async (req, res) => { console.log("Received request for /api/search"); - console.log(`Request IP: ${req.ip || req.connection.remoteAddress}`); + console.log(`Request IP: ${req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress}`); console.log(`Request JSON: ${JSON.stringify({ username: req.body.username, platform: req.body.platform, @@ -889,6 +895,38 @@ app.post("/api/search", async (req, res) => { } }); +// Improved logging endpoint +app.post('/api/log', (req, res) => { + const clientIP = req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress; + const userAgent = req.headers['user-agent']; + let logData; + + try { + // Handle data whether it comes as already parsed JSON or as a string + if (typeof req.body === 'string') { + logData = JSON.parse(req.body); + } else if (req.body && typeof req.body === 'object') { + logData = req.body; + } else { + // If no parsable data found, create a basic log entry + logData = { eventType: 'unknown', timestamp: new Date().toISOString() }; + } + + // Log the data + console.log(`[USER ACTIVITY] ${new Date().toISOString()} | IP: ${clientIP} | Type: ${logData.eventType} | ${JSON.stringify({ + ...logData, + userAgent + })}`); + + } catch (error) { + console.error('Error processing log data:', error); + console.log('Raw request body:', req.body); + } + + // Always return 200 to avoid client-side errors + res.status(200).send(); +}); + // Basic health check endpoint app.get("/health", (req, res) => { res.json({ status: "ok", timestamp: new Date().toISOString() }); diff --git a/src/js/backend.js b/src/js/backend.js index e2a41f0..50b0c8f 100644 --- a/src/js/backend.js +++ b/src/js/backend.js @@ -584,3 +584,102 @@ function addSyncListeners() { // Add change listeners for platform sync document.getElementById("platform").addEventListener("change", syncUsernames); } + +// Initialize session tracking when the page loads +document.addEventListener('DOMContentLoaded', () => { + // Generate a unique session ID + const sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2); + const sessionStart = Date.now(); + let lastActivity = Date.now(); + let activityTimer; + + // Log initial session start + logEvent('session_start', { sessionId }); + + // Update last activity time on user interactions + ['click', 'mousemove', 'keypress', 'scroll', 'touchstart'].forEach(event => { + document.addEventListener(event, () => lastActivity = Date.now()); + }); + + // Activity check every 60 seconds + activityTimer = setInterval(() => { + const inactiveTime = Date.now() - lastActivity; + // Log if user has been inactive for more than 3 minutes + if (inactiveTime > 180000) { + logEvent('user_inactive', { + sessionId, + inactiveTime: Math.round(inactiveTime / 1000) + }); + } + }, 60000); + + // Log session end when page is closed + window.addEventListener('beforeunload', () => { + clearInterval(activityTimer); + const duration = Math.round((Date.now() - sessionStart) / 1000); + logEvent('session_end', { + sessionId, + duration + }); + }); + + // Function to send logs to server + function logEvent(eventType, data) { + // Use sendBeacon for reliability during page unload + if (navigator.sendBeacon) { + const blob = new Blob([JSON.stringify({ + eventType, + timestamp: new Date().toISOString(), + ...data + })], { type: 'application/json' }); + + navigator.sendBeacon('/api/log', blob); + } else { + // Fallback for browsers that don't support sendBeacon + fetch('/api/log', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + keepalive: true, + body: JSON.stringify({ + eventType, + timestamp: new Date().toISOString(), + ...data + }) + }).catch(e => console.error('Logging error:', e)); + } + } + + // Track page navigation within the SPA (if applicable) + window.addEventListener('popstate', () => { + logEvent('page_view', { + sessionId, + path: window.location.pathname + }); + }); + + // Optional: Track specific user actions + // Example: Track when a player search is performed + document.querySelectorAll('form').forEach(form => { + form.addEventListener('submit', (e) => { + const formData = new FormData(form); + let actionData = {}; + + // Collect non-sensitive form data + if (formData.get('username')) { + actionData.username = formData.get('username'); + } + if (formData.get('platform')) { + actionData.platform = formData.get('platform'); + } + if (formData.get('game')) { + actionData.game = formData.get('game'); + } + + logEvent('user_action', { + sessionId, + action: 'search', + ...actionData + }); + }); + }); +});