codtracker-js/app.js
2025-04-16 16:16:32 -04:00

986 lines
29 KiB
JavaScript

const express = require('express');
const rateLimit = require('express-rate-limit');
const path = require('path');
const bodyParser = require('body-parser');
const API = require('./src/js/index.js');
const demoTracker = require('./src/js/demoTracker.js');
const { logger } = require('./src/js/logger.js');
const favicon = require('serve-favicon');
const fs = require('fs');
const app = express();
const port = process.env.PORT || 3512;
const {
processJsonOutput,
sanitizeHeaders,
timeoutPromise,
ensureLogin,
handleApiError,
activeSessions
} = require('./src/js/serverUtils.js');
require('./src/js/utils.js');
app.set('trust proxy', true);
// Middleware
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
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(demoTracker.demoModeMiddleware);
// Set up demo mode cleanup interval
setInterval(
() => demoTracker.demoModeRequestTracker.cleanupExpiredEntries(),
3600000
); // Clean up every hour
app.get('/health', (req, res) => {
const clientIP =
req.headers['cf-connecting-ip'] ||
req.headers['x-forwarded-for']?.split(',')[0] ||
req.ip ||
req.connection.remoteAddress;
const isDemoMode = demoTracker.isDemoModeActive();
res.json({
status: 'ok',
timestamp: global.Utils.toIsoString(new Date()),
demoMode: isDemoMode,
requestsRemaining:
isDemoMode ?
demoTracker.demoModeRequestTracker.maxRequestsPerHour -
(demoTracker.demoModeRequestTracker.requests.get(clientIP)?.count || 0)
: null,
});
});
// Serve the main HTML file
app.get('/', async (req, res) => {
// Check demo mode query parameter
if (req.query.demo === 'true') {
await demoTracker.initializeDemoMode(true);
} else if (req.query.demo === 'false') {
await demoTracker.initializeDemoMode(false);
}
res.sendFile(path.join(__dirname, 'src', 'index.html'));
});
// Demo mode route - enables demo mode when visited
app.get('/demo', async (req, res) => {
const success = await demoTracker.initializeDemoMode(true);
if (success) {
logger.info('Demo mode activated via /demo route');
res.redirect('/?demo=true');
} else {
res
.status(500)
.send('Failed to enable demo mode. Check server logs for details.');
}
});
// Demo mode deactivation route
app.get('/demo/disable', async (req, res) => {
await demoTracker.initializeDemoMode(false);
logger.info('Demo mode deactivated via /demo/disable route');
res.redirect('/?demo=false');
});
// API endpoint to fetch stats
app.post('/api/stats', async (req, res) => {
logger.debug('Received request for /api/stats');
logger.debug(
`Request IP: ${
req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
}`
);
logger.debug(
`Request JSON: ${JSON.stringify({
username: req.body.username,
platform: req.body.platform,
game: req.body.game,
apiCall: req.body.apiCall,
sanitize: req.body.sanitize,
replaceKeys: req.body.replaceKeys,
})}`
);
try {
let { username, ssoToken, platform, game, apiCall, sanitize, replaceKeys } =
req.body;
const defaultToken = demoTracker.getDefaultSsoToken();
if (!ssoToken && defaultToken) {
ssoToken = defaultToken;
logger.info('Using default SSO token for demo mode');
} else if (!ssoToken) {
return res.status(200).json({
status: 'error',
message: 'SSO Token is required',
timestamp: global.Utils.toIsoString(new Date()),
});
}
/*
logger.debug(
`Request details - Username: ${username}, Platform: ${platform}, Game: ${game}, API Call: ${apiCall}`
);
logger.debug("=== STATS REQUEST ===");
logger.debug(`Username: ${username || 'Not provided'}`);
logger.debug(`Platform: ${platform}`);
logger.debug(`Game: ${game}`);
logger.debug(`API Call: ${apiCall}`);
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
logger.debug("====================="); */
/*
if (!ssoToken) {
return res.status(400).json({ error: 'SSO Token is required' });
} */
// For mapList, username is not required
if (apiCall !== 'mapList' && !username) {
return res.status(400).json({ error: 'Username is required' });
}
// Clear previous session if it exists
if (activeSessions.has(ssoToken)) {
logger.debug('Clearing previous session');
activeSessions.delete(ssoToken);
}
// Login with the provided SSO token
try {
await ensureLogin(ssoToken);
} catch (loginError) {
logger.error('Login error:', loginError);
return res.status(200).json({
status: 'error',
error_type: 'LoginError',
message: 'SSO token login failed',
details: loginError.message || 'Unknown login error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
// Create a wrapped function for each API call to handle timeout
const fetchWithTimeout = async (apiFn) => {
return Promise.race([
apiFn(),
timeoutPromise(30000), // 30 second timeout
]);
};
// Check if the platform is valid for the game
const requiresUno = ['mw2', 'wz2', 'mw3', 'wzm'].includes(game);
if (requiresUno && platform !== 'uno' && apiCall !== 'mapList') {
logger.warn(`${game} requires Uno ID`);
return res.status(200).json({
status: 'error',
message: `${game} requires Uno ID (numerical ID)`,
timestamp: global.Utils.toIsoString(new Date()),
});
}
try {
logger.debug(
`Attempting to fetch ${game} data for ${username} on ${platform}`
);
let data;
if (apiCall === 'fullData') {
// Fetch lifetime stats based on game
switch (game) {
case 'mw':
data = await fetchWithTimeout(() =>
API.ModernWarfare.fullData(username, platform)
);
break;
case 'wz':
data = await fetchWithTimeout(() =>
API.Warzone.fullData(username, platform)
);
break;
case 'mw2':
data = await fetchWithTimeout(() =>
API.ModernWarfare2.fullData(username)
);
break;
case 'wz2':
data = await fetchWithTimeout(() =>
API.Warzone2.fullData(username)
);
break;
case 'mw3':
data = await fetchWithTimeout(() =>
API.ModernWarfare3.fullData(username)
);
break;
case 'cw':
data = await fetchWithTimeout(() =>
API.ColdWar.fullData(username, platform)
);
break;
case 'vg':
data = await fetchWithTimeout(() =>
API.Vanguard.fullData(username, platform)
);
break;
case 'wzm':
data = await fetchWithTimeout(() =>
API.WarzoneMobile.fullData(username)
);
break;
default:
return res.status(200).json({
status: 'error',
message: 'Invalid game selected',
timestamp: global.Utils.toIsoString(new Date()),
});
}
} else if (apiCall === 'combatHistory') {
// Fetch recent match history based on game
switch (game) {
case 'mw':
data = await fetchWithTimeout(() =>
API.ModernWarfare.combatHistory(username, platform)
);
break;
case 'wz':
data = await fetchWithTimeout(() =>
API.Warzone.combatHistory(username, platform)
);
break;
case 'mw2':
data = await fetchWithTimeout(() =>
API.ModernWarfare2.combatHistory(username)
);
break;
case 'wz2':
data = await fetchWithTimeout(() =>
API.Warzone2.combatHistory(username)
);
break;
case 'mw3':
data = await fetchWithTimeout(() =>
API.ModernWarfare3.combatHistory(username)
);
break;
case 'cw':
data = await fetchWithTimeout(() =>
API.ColdWar.combatHistory(username, platform)
);
break;
case 'vg':
data = await fetchWithTimeout(() =>
API.Vanguard.combatHistory(username, platform)
);
break;
case 'wzm':
data = await fetchWithTimeout(() =>
API.WarzoneMobile.combatHistory(username)
);
break;
default:
return res.status(200).json({
status: 'error',
message: 'Invalid game selected',
timestamp: global.Utils.toIsoString(new Date()),
});
}
} else if (apiCall === 'mapList') {
// Fetch map list (only valid for MW)
if (game === 'mw') {
data = await fetchWithTimeout(() =>
API.ModernWarfare.mapList(platform)
);
} else {
return res.status(200).json({
status: 'error',
message: 'Map list is only available for Modern Warfare',
timestamp: global.Utils.toIsoString(new Date()),
});
}
}
logger.debug('Data fetched successfully');
logger.debug(`Response Size: ~${JSON.stringify(data).length / 1024} KB`);
logger.debug(`Response Time: ${global.Utils.toIsoString(new Date())}`);
// Safely handle the response data
if (!data) {
logger.warn('No data returned from API');
return res.json({
status: 'partial_success',
message: 'No data returned from API, but no error thrown',
data: null,
timestamp: global.Utils.toIsoString(new Date()),
});
}
// logger.debug("Returning data to client");
const { sanitize, replaceKeys } = req.body;
demoTracker.incrementDemoCounter(req, ssoToken);
return res.json({
// status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: global.Utils.toIsoString(new Date()),
});
} catch (apiError) {
return handleApiError(apiError, res);
}
} catch (serverError) {
logger.error('Server Error:', serverError);
// Return a structured error response even for unexpected errors
return res.status(200).json({
status: 'server_error',
message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
});
// API endpoint to fetch recent matches
app.post('/api/matches', async (req, res) => {
logger.debug('Received request for /api/matches');
logger.debug(
`Request IP: ${
req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
}`
);
logger.debug(
`Request JSON: ${JSON.stringify({
username: req.body.username,
platform: req.body.platform,
game: req.body.game,
sanitize: req.body.sanitize,
replaceKeys: req.body.replaceKeys,
})}`
);
try {
let { username, ssoToken, platform, game, sanitize, replaceKeys } =
req.body;
/*
logger.debug(
`Request details - Username: ${username}, Platform: ${platform}, Game: ${game}`
);
logger.debug("=== MATCHES REQUEST ===");
logger.debug(`Username: ${username}`);
logger.debug(`Platform: ${platform}`);
logger.debug(`Game: ${game}`);
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
logger.debug("========================"); */
const defaultToken = demoTracker.getDefaultSsoToken();
if (!ssoToken && defaultToken) {
ssoToken = defaultToken;
logger.info('Using default SSO token for demo mode');
} else if (!ssoToken) {
return res.status(200).json({
status: 'error',
message: 'SSO Token is required',
timestamp: global.Utils.toIsoString(new Date()),
});
}
if (!username) {
return res.status(400).json({ error: 'Username is required' });
}
if (!username || !ssoToken) {
return res
.status(400)
.json({ error: 'Username and SSO Token are required' });
}
try {
await ensureLogin(ssoToken);
} catch (loginError) {
return res.status(200).json({
status: 'error',
error_type: 'LoginError',
message: 'SSO token login failed',
details: loginError.message || 'Unknown login error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
// Create a wrapped function for each API call to handle timeout
const fetchWithTimeout = async (apiFn) => {
return Promise.race([
apiFn(),
timeoutPromise(30000), // 30 second timeout
]);
};
try {
logger.debug(
`Attempting to fetch combat history for ${username} on ${platform}`
);
let data;
// Check if the platform is valid for the game
const requiresUno = ['mw2', 'wz2', 'mw3', 'wzm'].includes(game);
if (requiresUno && platform !== 'uno') {
return res.status(200).json({
status: 'error',
message: `${game} requires Uno ID (numerical ID)`,
timestamp: global.Utils.toIsoString(new Date()),
});
}
// Fetch combat history based on game
switch (game) {
case 'mw':
data = await fetchWithTimeout(() =>
API.ModernWarfare.combatHistory(username, platform)
);
break;
case 'wz':
data = await fetchWithTimeout(() =>
API.Warzone.combatHistory(username, platform)
);
break;
case 'mw2':
data = await fetchWithTimeout(() =>
API.ModernWarfare2.combatHistory(username)
);
break;
case 'wz2':
data = await fetchWithTimeout(() =>
API.Warzone2.combatHistory(username)
);
break;
case 'mw3':
data = await fetchWithTimeout(() =>
API.ModernWarfare3.combatHistory(username)
);
break;
case 'cw':
data = await fetchWithTimeout(() =>
API.ColdWar.combatHistory(username, platform)
);
break;
case 'vg':
data = await fetchWithTimeout(() =>
API.Vanguard.combatHistory(username, platform)
);
break;
case 'wzm':
data = await fetchWithTimeout(() =>
API.WarzoneMobile.combatHistory(username)
);
break;
default:
return res.status(200).json({
status: 'error',
message: 'Invalid game selected',
timestamp: global.Utils.toIsoString(new Date()),
});
}
const { sanitize, replaceKeys } = req.body;
demoTracker.incrementDemoCounter(req, ssoToken);
return res.json({
// status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: global.Utils.toIsoString(new Date()),
});
} catch (apiError) {
return handleApiError(apiError, res);
}
} catch (serverError) {
return res.status(200).json({
status: 'server_error',
message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
});
// API endpoint to fetch match info
app.post('/api/matchInfo', async (req, res) => {
logger.debug('Received request for /api/matchInfo');
logger.debug(
`Request IP: ${
req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
}`
);
logger.debug(
`Request JSON: ${JSON.stringify({
matchId: req.body.matchId,
platform: req.body.platform,
game: req.body.game,
sanitize: req.body.sanitize,
replaceKeys: req.body.replaceKeys,
})}`
);
try {
let { matchId, ssoToken, platform, game, sanitize, replaceKeys } = req.body;
/*
logger.debug(
`Request details - Match ID: ${matchId}, Platform: ${platform}, Game: ${game}`
);
logger.debug("=== MATCH INFO REQUEST ===");
logger.debug(`Match ID: ${matchId}`);
logger.debug(`Platform: ${platform}`);
logger.debug(`Game: ${game}`);
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
logger.debug("=========================="); */
const defaultToken = demoTracker.getDefaultSsoToken();
if (!ssoToken && defaultToken) {
ssoToken = defaultToken;
logger.info('Using default SSO token for demo mode');
} else if (!ssoToken) {
return res.status(200).json({
status: 'error',
message: 'SSO Token is required',
timestamp: global.Utils.toIsoString(new Date()),
});
}
if (!matchId) {
return res.status(400).json({ error: 'Match ID is required' });
}
if (!matchId || !ssoToken) {
return res
.status(400)
.json({ error: 'Match ID and SSO Token are required' });
}
try {
await ensureLogin(ssoToken);
} catch (loginError) {
return res.status(200).json({
status: 'error',
error_type: 'LoginError',
message: 'SSO token login failed',
details: loginError.message || 'Unknown login error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
// Create a wrapped function for each API call to handle timeout
const fetchWithTimeout = async (apiFn) => {
return Promise.race([
apiFn(),
timeoutPromise(30000), // 30 second timeout
]);
};
try {
logger.debug(`Attempting to fetch match info for match ID: ${matchId}`);
let data;
// Fetch match info based on game
switch (game) {
case 'mw':
data = await fetchWithTimeout(() =>
API.ModernWarfare.matchInfo(matchId, platform)
);
break;
case 'wz':
data = await fetchWithTimeout(() =>
API.Warzone.matchInfo(matchId, platform)
);
break;
case 'mw2':
data = await fetchWithTimeout(() =>
API.ModernWarfare2.matchInfo(matchId)
);
break;
case 'wz2':
data = await fetchWithTimeout(() => API.Warzone2.matchInfo(matchId));
break;
case 'mw3':
data = await fetchWithTimeout(() =>
API.ModernWarfare3.matchInfo(matchId)
);
break;
case 'cw':
data = await fetchWithTimeout(() =>
API.ColdWar.matchInfo(matchId, platform)
);
break;
case 'vg':
data = await fetchWithTimeout(() =>
API.Vanguard.matchInfo(matchId, platform)
);
break;
case 'wzm':
data = await fetchWithTimeout(() =>
API.WarzoneMobile.matchInfo(matchId)
);
break;
default:
return res.status(200).json({
status: 'error',
message: 'Invalid game selected',
timestamp: global.Utils.toIsoString(new Date()),
});
}
const { sanitize, replaceKeys } = req.body;
demoTracker.incrementDemoCounter(req, ssoToken);
return res.json({
// status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: global.Utils.toIsoString(new Date()),
});
} catch (apiError) {
return handleApiError(apiError, res);
}
} catch (serverError) {
return res.status(200).json({
status: 'server_error',
message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
});
// API endpoint for user-related API calls
app.post('/api/user', async (req, res) => {
logger.debug('Received request for /api/user');
logger.debug(
`Request IP: ${
req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
}`
);
logger.debug(
`Request JSON: ${JSON.stringify({
username: req.body.username,
platform: req.body.platform,
userCall: req.body.userCall,
sanitize: req.body.sanitize,
replaceKeys: req.body.replaceKeys,
})}`
);
try {
let { username, ssoToken, platform, userCall, sanitize, replaceKeys } =
req.body;
/*
logger.debug(
`Request details - Username: ${username}, Platform: ${platform}, User Call: ${userCall}`
);
logger.debug("=== USER DATA REQUEST ===");
logger.debug(`Username: ${username || 'Not provided'}`);
logger.debug(`Platform: ${platform}`);
logger.debug(`User Call: ${userCall}`);
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
logger.debug("========================="); */
const defaultToken = demoTracker.getDefaultSsoToken();
if (!ssoToken && defaultToken) {
ssoToken = defaultToken;
logger.info('Using default SSO token for demo mode');
} else if (!ssoToken) {
return res.status(200).json({
status: 'error',
message: 'SSO Token is required',
timestamp: global.Utils.toIsoString(new Date()),
});
}
// For eventFeed and identities, username is not required
if (
!username &&
userCall !== 'eventFeed' &&
userCall !== 'friendFeed' &&
userCall !== 'identities'
) {
return res
.status(400)
.json({ error: 'Username is required for this API call' });
}
try {
await ensureLogin(ssoToken);
} catch (loginError) {
return res.status(200).json({
status: 'error',
error_type: 'LoginError',
message: 'SSO token login failed',
details: loginError.message || 'Unknown login error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
// Create a wrapped function for each API call to handle timeout
const fetchWithTimeout = async (apiFn) => {
return Promise.race([
apiFn(),
timeoutPromise(30000), // 30 second timeout
]);
};
try {
logger.debug(`Attempting to fetch user data for ${userCall}`);
let data;
// Fetch user data based on userCall
switch (userCall) {
case 'codPoints':
data = await fetchWithTimeout(() =>
API.Me.codPoints(username, platform)
);
break;
case 'connectedAccounts':
data = await fetchWithTimeout(() =>
API.Me.connectedAccounts(username, platform)
);
break;
case 'eventFeed':
data = await fetchWithTimeout(() => API.Me.eventFeed());
break;
case 'friendFeed':
data = await fetchWithTimeout(() =>
API.Me.friendFeed(username, platform)
);
break;
case 'identities':
data = await fetchWithTimeout(() => API.Me.loggedInIdentities());
break;
case 'friendsList':
data = await fetchWithTimeout(() => API.Me.friendsList());
break;
// case "settings":
// data = await fetchWithTimeout(() =>
// API.Me.settings(username, platform)
// );
// break;
default:
return res.status(200).json({
status: 'error',
message: 'Invalid user API call selected',
timestamp: global.Utils.toIsoString(new Date()),
});
}
const { sanitize, replaceKeys } = req.body;
demoTracker.incrementDemoCounter(req, ssoToken);
return res.json({
// status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: global.Utils.toIsoString(new Date()),
});
} catch (apiError) {
return handleApiError(apiError, res);
}
} catch (serverError) {
return res.status(200).json({
status: 'server_error',
message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
});
// API endpoint for fuzzy search
app.post('/api/search', async (req, res) => {
logger.debug('Received request for /api/search');
logger.debug(
`Request IP: ${
req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress
}`
);
logger.debug(
`Request JSON: ${JSON.stringify({
username: req.body.username,
platform: req.body.platform,
sanitize: req.body.sanitize,
replaceKeys: req.body.replaceKeys,
})}`
);
try {
let { username, ssoToken, platform, sanitize, replaceKeys } = req.body;
/*
logger.debug(
`Request details - Match ID: ${matchId}, Platform: ${platform}, Game: ${game}`
);
logger.debug("=== MATCH INFO REQUEST ===");
logger.debug(`Match ID: ${matchId}`);
logger.debug(`Platform: ${platform}`);
logger.debug(`Game: ${game}`);
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
logger.debug("=========================="); */
const defaultToken = demoTracker.getDefaultSsoToken();
if (!ssoToken && defaultToken) {
ssoToken = defaultToken;
logger.info('Using default SSO token for demo mode');
} else if (!ssoToken) {
return res.status(200).json({
status: 'error',
message: 'SSO Token is required',
timestamp: global.Utils.toIsoString(new Date()),
});
}
if (!username) {
return res.status(400).json({ error: 'Username is required' });
}
if (!username || !ssoToken) {
return res
.status(400)
.json({ error: 'Username and SSO Token are required' });
}
try {
await ensureLogin(ssoToken);
} catch (loginError) {
return res.status(200).json({
status: 'error',
error_type: 'LoginError',
message: 'SSO token login failed',
details: loginError.message || 'Unknown login error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
// Create a wrapped function for each API call to handle timeout
const fetchWithTimeout = async (apiFn) => {
return Promise.race([
apiFn(),
timeoutPromise(30000), // 30 second timeout
]);
};
try {
logger.debug(
`Attempting fuzzy search for ${username} on platform ${platform}`
);
const data = await fetchWithTimeout(() =>
API.Misc.search(username, platform)
);
const { sanitize, replaceKeys } = req.body;
demoTracker.incrementDemoCounter(req, ssoToken);
return res.json({
// status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: global.Utils.toIsoString(new Date()),
// link: "Stats pulled using codtracker.rimmyscorner.com",
});
} catch (apiError) {
return handleApiError(apiError, res);
}
} catch (serverError) {
return res.status(200).json({
status: 'server_error',
message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error',
timestamp: global.Utils.toIsoString(new Date()),
});
}
});
// Improved logging endpoint
app.post('/api/log', (req, res) => {
const clientIP =
req.headers['cf-connecting-ip'] ||
req.headers['x-forwarded-for']?.split(',')[0] ||
req.ip ||
req.connection.remoteAddress;
const userAgent = req.headers['user-agent'];
const referer = req.headers['referer'];
const origin = req.headers['origin'];
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: global.Utils.toIsoString(new Date()),
};
}
// Enrich log with server-side data
const enrichedLog = {
...logData,
meta: {
clientIP,
userAgent,
referer,
origin,
requestHeaders: sanitizeHeaders(req.headers),
serverTimestamp: global.Utils.toIsoString(new Date()),
requestId: req.id || Math.random().toString(36).substring(2, 15),
},
};
// Use the dedicated user activity logger
logger.userActivity(enrichedLog.eventType || 'unknown', enrichedLog);
} catch (error) {
logger.error('Error processing log data', {
error: error.message,
rawBody: typeof req.body === 'object' ? '[Object]' : req.body,
});
}
// Always return 200 to avoid client-side errors
res.status(200).send();
});
// Database storage function
/*
function storeLogInDatabase(logData) {
// Example with MongoDB
db.collection('user_logs').insertOne(logData)
.catch(err => logger.error('Failed to store log in database:', err));
}
*/
// Start the server
app.listen(port, () => {
logger.info(`Server running on http://localhost:${port}`);
});