refactor: optimize runtime
This commit is contained in:
parent
18284f7483
commit
13ea56dc66
252
app.js
252
app.js
@ -6,6 +6,7 @@ const API = require('./src/js/index.js');
|
|||||||
const demoTracker = require('./src/js/demoTracker.js');
|
const demoTracker = require('./src/js/demoTracker.js');
|
||||||
const { logger } = require('./src/js/logger.js');
|
const { logger } = require('./src/js/logger.js');
|
||||||
const favicon = require('serve-favicon');
|
const favicon = require('serve-favicon');
|
||||||
|
const fs = require('fs');
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 3512;
|
const port = process.env.PORT || 3512;
|
||||||
require('./src/js/utils.js');
|
require('./src/js/utils.js');
|
||||||
@ -13,7 +14,6 @@ require('./src/js/utils.js');
|
|||||||
app.set('trust proxy', true);
|
app.set('trust proxy', true);
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
// app.use(bodyParser.json({ limit: '10mb' }));
|
|
||||||
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
|
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
|
||||||
app.use(express.static(__dirname));
|
app.use(express.static(__dirname));
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
@ -27,17 +27,8 @@ app.use(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// app.use(express.raw({ type: 'application/json', limit: '10mb' }));
|
|
||||||
app.use(demoTracker.demoModeMiddleware);
|
app.use(demoTracker.demoModeMiddleware);
|
||||||
|
|
||||||
// Set up demo mode cleanup interval
|
|
||||||
setInterval(
|
|
||||||
() => demoTracker.demoModeRequestTracker.cleanupExpiredEntries(),
|
|
||||||
3600000
|
|
||||||
); // Clean up every hour
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
// Initialize key replacements
|
// Initialize key replacements
|
||||||
let keyReplacements = {};
|
let keyReplacements = {};
|
||||||
|
|
||||||
@ -213,6 +204,80 @@ const handleApiError = (error, res) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to remove sensitive data from headers
|
||||||
|
function sanitizeHeaders(headers) {
|
||||||
|
const safeHeaders = { ...headers };
|
||||||
|
|
||||||
|
// Remove potential sensitive information
|
||||||
|
const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie'];
|
||||||
|
sensitiveHeaders.forEach((header) => {
|
||||||
|
if (safeHeaders[header]) {
|
||||||
|
safeHeaders[header] = '[REDACTED]';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return safeHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// API endpoint to fetch stats
|
||||||
app.post('/api/stats', async (req, res) => {
|
app.post('/api/stats', async (req, res) => {
|
||||||
logger.debug('Received request for /api/stats');
|
logger.debug('Received request for /api/stats');
|
||||||
@ -507,16 +572,6 @@ app.post('/api/matches', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("========================"); */
|
logger.debug("========================"); */
|
||||||
|
|
||||||
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' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultToken = demoTracker.getDefaultSsoToken();
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
if (!ssoToken && defaultToken) {
|
if (!ssoToken && defaultToken) {
|
||||||
ssoToken = defaultToken;
|
ssoToken = defaultToken;
|
||||||
@ -529,6 +584,16 @@ app.post('/api/matches', async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
await ensureLogin(ssoToken);
|
await ensureLogin(ssoToken);
|
||||||
} catch (loginError) {
|
} catch (loginError) {
|
||||||
@ -670,16 +735,6 @@ app.post('/api/matchInfo', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("=========================="); */
|
logger.debug("=========================="); */
|
||||||
|
|
||||||
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' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultToken = demoTracker.getDefaultSsoToken();
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
if (!ssoToken && defaultToken) {
|
if (!ssoToken && defaultToken) {
|
||||||
ssoToken = defaultToken;
|
ssoToken = defaultToken;
|
||||||
@ -692,6 +747,16 @@ app.post('/api/matchInfo', async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
await ensureLogin(ssoToken);
|
await ensureLogin(ssoToken);
|
||||||
} catch (loginError) {
|
} catch (loginError) {
|
||||||
@ -820,10 +885,17 @@ app.post('/api/user', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("========================="); */
|
logger.debug("========================="); */
|
||||||
|
|
||||||
/*
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
if (!ssoToken) {
|
if (!ssoToken && defaultToken) {
|
||||||
return res.status(400).json({ error: 'SSO Token is required' });
|
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
|
// For eventFeed and identities, username is not required
|
||||||
if (
|
if (
|
||||||
@ -837,18 +909,6 @@ app.post('/api/user', async (req, res) => {
|
|||||||
.json({ error: 'Username is required for this API call' });
|
.json({ error: 'Username is required for this API call' });
|
||||||
}
|
}
|
||||||
|
|
||||||
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()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ensureLogin(ssoToken);
|
await ensureLogin(ssoToken);
|
||||||
} catch (loginError) {
|
} catch (loginError) {
|
||||||
@ -966,16 +1026,6 @@ app.post('/api/search', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("=========================="); */
|
logger.debug("=========================="); */
|
||||||
|
|
||||||
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' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultToken = demoTracker.getDefaultSsoToken();
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
if (!ssoToken && defaultToken) {
|
if (!ssoToken && defaultToken) {
|
||||||
ssoToken = defaultToken;
|
ssoToken = defaultToken;
|
||||||
@ -988,6 +1038,16 @@ app.post('/api/search', async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
await ensureLogin(ssoToken);
|
await ensureLogin(ssoToken);
|
||||||
} catch (loginError) {
|
} catch (loginError) {
|
||||||
@ -1092,21 +1152,6 @@ app.post('/api/log', (req, res) => {
|
|||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper function to remove sensitive data from headers
|
|
||||||
function sanitizeHeaders(headers) {
|
|
||||||
const safeHeaders = { ...headers };
|
|
||||||
|
|
||||||
// Remove potential sensitive information
|
|
||||||
const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie'];
|
|
||||||
sensitiveHeaders.forEach((header) => {
|
|
||||||
if (safeHeaders[header]) {
|
|
||||||
safeHeaders[header] = '[REDACTED]';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return safeHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database storage function
|
// Database storage function
|
||||||
/*
|
/*
|
||||||
function storeLogInDatabase(logData) {
|
function storeLogInDatabase(logData) {
|
||||||
@ -1116,71 +1161,6 @@ function storeLogInDatabase(logData) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
app.get('/demo', (req, res) => {
|
|
||||||
// Set a cookie or session variable to enable demo mode
|
|
||||||
if (!req.session) {
|
|
||||||
req.session = {};
|
|
||||||
}
|
|
||||||
req.session.forceDemoMode = true;
|
|
||||||
|
|
||||||
// Redirect to the main app
|
|
||||||
res.redirect('/');
|
|
||||||
}); */
|
|
||||||
|
|
||||||
// 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');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
logger.info(`Server running on http://localhost:${port}`);
|
logger.info(`Server running on http://localhost:${port}`);
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
window.appState = {
|
||||||
|
currentData: null,
|
||||||
|
outputFormat: 'json',
|
||||||
|
tutorialDismissed: false,
|
||||||
|
};
|
||||||
|
|
||||||
// Export the functions that frontend.js needs to call
|
// Export the functions that frontend.js needs to call
|
||||||
window.backendAPI = {
|
window.backendAPI = {
|
||||||
fetchData,
|
fetchData,
|
||||||
@ -7,16 +13,57 @@ window.backendAPI = {
|
|||||||
processTimestamps,
|
processTimestamps,
|
||||||
};
|
};
|
||||||
|
|
||||||
window.appState = {
|
|
||||||
currentData: null,
|
|
||||||
outputFormat: 'json',
|
|
||||||
tutorialDismissed: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// Backend-specific initialization
|
// Backend-specific initialization
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to convert seconds to human readable duration
|
||||||
|
function formatDuration(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return seconds;
|
||||||
|
|
||||||
|
// Convert to number in case it's a string
|
||||||
|
const totalSeconds = parseFloat(seconds);
|
||||||
|
|
||||||
|
// Calculate days, hours, minutes, seconds
|
||||||
|
const days = Math.floor(totalSeconds / 86400);
|
||||||
|
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const remainingSeconds = Math.floor(totalSeconds % 60);
|
||||||
|
|
||||||
|
return `${days} Days ${hours} Hours ${minutes} Minutes ${remainingSeconds} Seconds`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to convert epoch time to human-readable format
|
||||||
|
function formatEpochTime(epoch, timezone) {
|
||||||
|
if (!epoch) return epoch;
|
||||||
|
|
||||||
|
// Check if epoch is in milliseconds (13 digits) or seconds (10 digits)
|
||||||
|
const epochNumber = parseInt(epoch);
|
||||||
|
if (isNaN(epochNumber)) return epoch;
|
||||||
|
|
||||||
|
// Convert to milliseconds if needed
|
||||||
|
const epochMs =
|
||||||
|
epochNumber.toString().length <= 10 ? epochNumber * 1000 : epochNumber;
|
||||||
|
|
||||||
|
// Parse the timezone offset
|
||||||
|
let offset = 0;
|
||||||
|
if (timezone !== 'UTC') {
|
||||||
|
const match = timezone.match(/GMT([+-])(\d+)(?::(\d+))?/);
|
||||||
|
if (match) {
|
||||||
|
const sign = match[1] === '+' ? 1 : -1;
|
||||||
|
const hours = parseInt(match[2]);
|
||||||
|
const minutes = match[3] ? parseInt(match[3]) : 0;
|
||||||
|
offset = sign * (hours * 60 + minutes) * 60 * 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a date object and adjust for timezone
|
||||||
|
const date = new Date(epochMs + offset);
|
||||||
|
|
||||||
|
// Format the date
|
||||||
|
return date.toUTCString().replace('GMT', timezone);
|
||||||
|
}
|
||||||
|
|
||||||
// YAML conversion function
|
// YAML conversion function
|
||||||
function jsonToYAML(json) {
|
function jsonToYAML(json) {
|
||||||
const INDENT_SIZE = 2;
|
const INDENT_SIZE = 2;
|
||||||
@ -76,6 +123,61 @@ function jsonToYAML(json) {
|
|||||||
return formatValue(json, 0).substring(1); // Remove first newline
|
return formatValue(json, 0).substring(1); // Remove first newline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to recursively process timestamps and durations in the data
|
||||||
|
function processTimestamps(
|
||||||
|
data,
|
||||||
|
timezone,
|
||||||
|
keysToConvert = [
|
||||||
|
'date',
|
||||||
|
'dateAdded',
|
||||||
|
'utcStartSeconds',
|
||||||
|
'utcEndSeconds',
|
||||||
|
'timestamp',
|
||||||
|
'startTime',
|
||||||
|
'endTime',
|
||||||
|
],
|
||||||
|
durationKeys = [
|
||||||
|
'time',
|
||||||
|
'timePlayedTotal',
|
||||||
|
'timePlayed',
|
||||||
|
'avgLifeTime',
|
||||||
|
'duration',
|
||||||
|
'objTime',
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
if (!data || typeof data !== 'object') return data;
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map((item) =>
|
||||||
|
processTimestamps(item, timezone, keysToConvert, durationKeys)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
if (keysToConvert.includes(key) && typeof value === 'number') {
|
||||||
|
result[key] = formatEpochTime(value, timezone);
|
||||||
|
} else if (
|
||||||
|
durationKeys.includes(key) &&
|
||||||
|
typeof value === 'number' &&
|
||||||
|
document.getElementById('replaceKeysOption').checked
|
||||||
|
) {
|
||||||
|
result[key] = formatDuration(value);
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
result[key] = processTimestamps(
|
||||||
|
value,
|
||||||
|
timezone,
|
||||||
|
keysToConvert,
|
||||||
|
durationKeys
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Common fetch function
|
// Common fetch function
|
||||||
async function fetchData(endpoint, requestData) {
|
async function fetchData(endpoint, requestData) {
|
||||||
console.log(`[CLIENT] Request to ${endpoint} at ${new Date().toISOString()}`);
|
console.log(`[CLIENT] Request to ${endpoint} at ${new Date().toISOString()}`);
|
||||||
@ -169,105 +271,3 @@ async function fetchData(endpoint, requestData) {
|
|||||||
loadingElement.style.display = 'none';
|
loadingElement.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to convert seconds to human readable duration
|
|
||||||
function formatDuration(seconds) {
|
|
||||||
if (!seconds || isNaN(seconds)) return seconds;
|
|
||||||
|
|
||||||
// Convert to number in case it's a string
|
|
||||||
const totalSeconds = parseFloat(seconds);
|
|
||||||
|
|
||||||
// Calculate days, hours, minutes, seconds
|
|
||||||
const days = Math.floor(totalSeconds / 86400);
|
|
||||||
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
||||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
||||||
const remainingSeconds = Math.floor(totalSeconds % 60);
|
|
||||||
|
|
||||||
return `${days} Days ${hours} Hours ${minutes} Minutes ${remainingSeconds} Seconds`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to convert epoch time to human-readable format
|
|
||||||
function formatEpochTime(epoch, timezone) {
|
|
||||||
if (!epoch) return epoch;
|
|
||||||
|
|
||||||
// Check if epoch is in milliseconds (13 digits) or seconds (10 digits)
|
|
||||||
const epochNumber = parseInt(epoch);
|
|
||||||
if (isNaN(epochNumber)) return epoch;
|
|
||||||
|
|
||||||
// Convert to milliseconds if needed
|
|
||||||
const epochMs =
|
|
||||||
epochNumber.toString().length <= 10 ? epochNumber * 1000 : epochNumber;
|
|
||||||
|
|
||||||
// Parse the timezone offset
|
|
||||||
let offset = 0;
|
|
||||||
if (timezone !== 'UTC') {
|
|
||||||
const match = timezone.match(/GMT([+-])(\d+)(?::(\d+))?/);
|
|
||||||
if (match) {
|
|
||||||
const sign = match[1] === '+' ? 1 : -1;
|
|
||||||
const hours = parseInt(match[2]);
|
|
||||||
const minutes = match[3] ? parseInt(match[3]) : 0;
|
|
||||||
offset = sign * (hours * 60 + minutes) * 60 * 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a date object and adjust for timezone
|
|
||||||
const date = new Date(epochMs + offset);
|
|
||||||
|
|
||||||
// Format the date
|
|
||||||
return date.toUTCString().replace('GMT', timezone);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to recursively process timestamps and durations in the data
|
|
||||||
function processTimestamps(
|
|
||||||
data,
|
|
||||||
timezone,
|
|
||||||
keysToConvert = [
|
|
||||||
'date',
|
|
||||||
'dateAdded',
|
|
||||||
'utcStartSeconds',
|
|
||||||
'utcEndSeconds',
|
|
||||||
'timestamp',
|
|
||||||
'startTime',
|
|
||||||
'endTime',
|
|
||||||
],
|
|
||||||
durationKeys = [
|
|
||||||
'time',
|
|
||||||
'timePlayedTotal',
|
|
||||||
'timePlayed',
|
|
||||||
'avgLifeTime',
|
|
||||||
'duration',
|
|
||||||
'objTime',
|
|
||||||
]
|
|
||||||
) {
|
|
||||||
if (!data || typeof data !== 'object') return data;
|
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
return data.map((item) =>
|
|
||||||
processTimestamps(item, timezone, keysToConvert, durationKeys)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {};
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
|
||||||
if (keysToConvert.includes(key) && typeof value === 'number') {
|
|
||||||
result[key] = formatEpochTime(value, timezone);
|
|
||||||
} else if (
|
|
||||||
durationKeys.includes(key) &&
|
|
||||||
typeof value === 'number' &&
|
|
||||||
document.getElementById('replaceKeysOption').checked
|
|
||||||
) {
|
|
||||||
result[key] = formatDuration(value);
|
|
||||||
} else if (typeof value === 'object' && value !== null) {
|
|
||||||
result[key] = processTimestamps(
|
|
||||||
value,
|
|
||||||
timezone,
|
|
||||||
keysToConvert,
|
|
||||||
durationKeys
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
result[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,61 @@ window.uiAPI = {
|
|||||||
displayError,
|
displayError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Function to handle time and duration conversion
|
||||||
|
function displayResults(data) {
|
||||||
|
const resultsElement = document.getElementById('results');
|
||||||
|
const downloadContainer = document.getElementById('download-container');
|
||||||
|
|
||||||
|
// Apply time conversion if enabled
|
||||||
|
const convertTime = document.getElementById('convertTimeOption').checked;
|
||||||
|
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
||||||
|
let displayData = data;
|
||||||
|
|
||||||
|
if (convertTime || replaceKeys) {
|
||||||
|
const timezone = document.getElementById('timezoneSelect').value;
|
||||||
|
displayData = window.backendAPI.processTimestamps(
|
||||||
|
structuredClone(data),
|
||||||
|
timezone
|
||||||
|
); // Use structured clone API instead of JSON.parse/stringify
|
||||||
|
// displayData = window.backendAPI.processTimestamps(JSON.parse(JSON.stringify(data)), timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the data
|
||||||
|
let formattedData = '';
|
||||||
|
if (window.appState.outputFormat === 'yaml') {
|
||||||
|
formattedData = window.backendAPI.jsonToYAML(displayData);
|
||||||
|
document.getElementById('downloadJson').textContent = 'Download YAML Data';
|
||||||
|
} else {
|
||||||
|
formattedData = JSON.stringify(displayData, null, 2);
|
||||||
|
document.getElementById('downloadJson').textContent = 'Download JSON Data';
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsElement.textContent = formattedData;
|
||||||
|
resultsElement.style.display = 'block';
|
||||||
|
downloadContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to display errors
|
||||||
|
function displayError(message) {
|
||||||
|
const errorElement = document.getElementById('error');
|
||||||
|
const loadingElement = document.getElementById('loading');
|
||||||
|
const resultsElement = document.getElementById('results');
|
||||||
|
|
||||||
|
errorElement.textContent = message;
|
||||||
|
loadingElement.style.display = 'none';
|
||||||
|
|
||||||
|
// Clear previous results to ensure they can be redrawn
|
||||||
|
resultsElement.style.display = 'none';
|
||||||
|
resultsElement.textContent = '';
|
||||||
|
|
||||||
|
// Keep tutorial hidden if previously dismissed
|
||||||
|
if (window.appState.tutorialDismissed) {
|
||||||
|
document.querySelectorAll('.tutorial').forEach((element) => {
|
||||||
|
element.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Configure client-side logging settings
|
// Configure client-side logging settings
|
||||||
const clientLogger = {
|
const clientLogger = {
|
||||||
// Control how much we log to reduce spam
|
// Control how much we log to reduce spam
|
||||||
@ -57,25 +112,6 @@ const clientLogger = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('popstate', checkDemoMode);
|
|
||||||
|
|
||||||
// Initialize once DOM is loaded
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
initTabSwitching();
|
|
||||||
addEnterKeyListeners();
|
|
||||||
setupDownloadButton();
|
|
||||||
setupFormatSelector();
|
|
||||||
setupProcessingOptions();
|
|
||||||
setupTimeOptions();
|
|
||||||
addSyncListeners();
|
|
||||||
initializeSessionTracking();
|
|
||||||
checkDemoMode();
|
|
||||||
// Call initially and then set up a refresh interval
|
|
||||||
updateRequestLimitInfo();
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(updateRequestLimitInfo, 60000); // Update every minute
|
|
||||||
|
|
||||||
// Function to check if we're in demo mode
|
// Function to check if we're in demo mode
|
||||||
function checkDemoMode() {
|
function checkDemoMode() {
|
||||||
console.log('Checking for demo mode...');
|
console.log('Checking for demo mode...');
|
||||||
@ -182,6 +218,23 @@ function updateRequestLimitInfo() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('popstate', checkDemoMode);
|
||||||
|
|
||||||
|
// Initialize once DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
checkDemoMode(); // Check demo mode first
|
||||||
|
initTabSwitching();
|
||||||
|
addEnterKeyListeners();
|
||||||
|
addSyncListeners();
|
||||||
|
setupProcessingOptions();
|
||||||
|
setupFormatSelector();
|
||||||
|
setupTimeOptions();
|
||||||
|
setupDownloadButton();
|
||||||
|
initializeSessionTracking();
|
||||||
|
// Call initially and then set up a refresh interval
|
||||||
|
updateRequestLimitInfo();
|
||||||
|
});
|
||||||
|
|
||||||
// Tab switching logic
|
// Tab switching logic
|
||||||
function initTabSwitching() {
|
function initTabSwitching() {
|
||||||
document.querySelectorAll('.tab').forEach((tab) => {
|
document.querySelectorAll('.tab').forEach((tab) => {
|
||||||
@ -200,6 +253,60 @@ function initTabSwitching() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function triggerActiveTabButton() {
|
||||||
|
const activeTab = document
|
||||||
|
.querySelector('.tab.active')
|
||||||
|
.getAttribute('data-tab');
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'stats':
|
||||||
|
document.getElementById('fetchStats').click();
|
||||||
|
break;
|
||||||
|
case 'matches':
|
||||||
|
document.getElementById('fetchMatches').click();
|
||||||
|
break;
|
||||||
|
case 'user':
|
||||||
|
document.getElementById('fetchUserInfo').click();
|
||||||
|
break;
|
||||||
|
case 'other':
|
||||||
|
document.getElementById('fuzzySearch').click();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEnterKeyListeners() {
|
||||||
|
// Use event delegation for handling Enter key press
|
||||||
|
document.addEventListener('keypress', function (event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
// Get the active element
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
|
||||||
|
if (!activeElement || !activeElement.id) return;
|
||||||
|
|
||||||
|
// Mapping of input fields to their submit buttons
|
||||||
|
const inputToButtonMapping = {
|
||||||
|
ssoToken: null, // Will trigger active tab button
|
||||||
|
username: null, // Will trigger active tab button
|
||||||
|
matchUsername: 'fetchMatches',
|
||||||
|
matchId: 'fetchMatchInfo',
|
||||||
|
userUsername: 'fetchUserInfo',
|
||||||
|
searchUsername: 'fuzzySearch',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (activeElement.id in inputToButtonMapping) {
|
||||||
|
if (inputToButtonMapping[activeElement.id]) {
|
||||||
|
// Click the specific button
|
||||||
|
document
|
||||||
|
.getElementById(inputToButtonMapping[activeElement.id])
|
||||||
|
.click();
|
||||||
|
} else {
|
||||||
|
// Trigger the active tab button
|
||||||
|
triggerActiveTabButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Setup processing options (sanitize/replace)
|
// Setup processing options (sanitize/replace)
|
||||||
function setupProcessingOptions() {
|
function setupProcessingOptions() {
|
||||||
document
|
document
|
||||||
@ -240,238 +347,6 @@ function setupFormatSelector() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch stats
|
|
||||||
document.getElementById('fetchStats').addEventListener('click', async () => {
|
|
||||||
const username = document.getElementById('username').value.trim();
|
|
||||||
const ssoToken = document.getElementById('ssoToken').value.trim();
|
|
||||||
const platform = document.getElementById('platform').value;
|
|
||||||
const game = document.getElementById('game').value;
|
|
||||||
const apiCall = document.getElementById('apiCall').value;
|
|
||||||
|
|
||||||
const sanitize = document.getElementById('sanitizeOption').checked;
|
|
||||||
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
|
||||||
|
|
||||||
await window.backendAPI.fetchData('/api/stats', {
|
|
||||||
username,
|
|
||||||
ssoToken,
|
|
||||||
platform,
|
|
||||||
game,
|
|
||||||
apiCall,
|
|
||||||
sanitize,
|
|
||||||
replaceKeys,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch match history
|
|
||||||
document.getElementById('fetchMatches').addEventListener('click', async () => {
|
|
||||||
const username = document.getElementById('matchUsername').value.trim();
|
|
||||||
const ssoToken = document.getElementById('ssoToken').value.trim();
|
|
||||||
const platform = document.getElementById('matchPlatform').value;
|
|
||||||
const game = document.getElementById('matchGame').value;
|
|
||||||
|
|
||||||
const sanitize = document.getElementById('sanitizeOption').checked;
|
|
||||||
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
|
||||||
|
|
||||||
await window.backendAPI.fetchData('/api/matches', {
|
|
||||||
username,
|
|
||||||
ssoToken,
|
|
||||||
platform,
|
|
||||||
game,
|
|
||||||
sanitize,
|
|
||||||
replaceKeys,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch match details
|
|
||||||
document
|
|
||||||
.getElementById('fetchMatchInfo')
|
|
||||||
.addEventListener('click', async () => {
|
|
||||||
const matchId = document.getElementById('matchId').value.trim();
|
|
||||||
const ssoToken = document.getElementById('ssoToken').value.trim();
|
|
||||||
const platform = document.getElementById('matchPlatform').value;
|
|
||||||
const game = document.getElementById('matchGame').value;
|
|
||||||
|
|
||||||
const sanitize = document.getElementById('sanitizeOption').checked;
|
|
||||||
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
|
||||||
|
|
||||||
if (!matchId) {
|
|
||||||
displayError('Match ID is required');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.backendAPI.fetchData('/api/matchInfo', {
|
|
||||||
matchId,
|
|
||||||
ssoToken,
|
|
||||||
platform,
|
|
||||||
game,
|
|
||||||
sanitize,
|
|
||||||
replaceKeys,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch user info
|
|
||||||
document.getElementById('fetchUserInfo').addEventListener('click', async () => {
|
|
||||||
const username = document.getElementById('userUsername').value.trim();
|
|
||||||
const ssoToken = document.getElementById('ssoToken').value.trim();
|
|
||||||
const platform = document.getElementById('userPlatform').value;
|
|
||||||
const userCall = document.getElementById('userCall').value;
|
|
||||||
|
|
||||||
const sanitize = document.getElementById('sanitizeOption').checked;
|
|
||||||
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
|
||||||
|
|
||||||
// For event feed and identities, username is not required
|
|
||||||
if (
|
|
||||||
!username &&
|
|
||||||
userCall !== 'eventFeed' &&
|
|
||||||
userCall !== 'friendFeed' &&
|
|
||||||
userCall !== 'identities'
|
|
||||||
) {
|
|
||||||
displayError('Username is required for this API call');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.backendAPI.fetchData('/api/user', {
|
|
||||||
username,
|
|
||||||
ssoToken,
|
|
||||||
platform,
|
|
||||||
userCall,
|
|
||||||
sanitize,
|
|
||||||
replaceKeys,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fuzzy search
|
|
||||||
document.getElementById('fuzzySearch').addEventListener('click', async () => {
|
|
||||||
const username = document.getElementById('searchUsername').value.trim();
|
|
||||||
const ssoToken = document.getElementById('ssoToken').value.trim();
|
|
||||||
const platform = document.getElementById('searchPlatform').value;
|
|
||||||
|
|
||||||
const sanitize = document.getElementById('sanitizeOption').checked;
|
|
||||||
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
|
||||||
|
|
||||||
if (!username) {
|
|
||||||
displayError('Username is required for search');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.backendAPI.fetchData('/api/search', {
|
|
||||||
username,
|
|
||||||
ssoToken,
|
|
||||||
platform,
|
|
||||||
sanitize,
|
|
||||||
replaceKeys,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to handle time and duration conversion
|
|
||||||
function displayResults(data) {
|
|
||||||
const resultsElement = document.getElementById('results');
|
|
||||||
const downloadContainer = document.getElementById('download-container');
|
|
||||||
|
|
||||||
// Apply time conversion if enabled
|
|
||||||
const convertTime = document.getElementById('convertTimeOption').checked;
|
|
||||||
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
|
||||||
let displayData = data;
|
|
||||||
|
|
||||||
if (convertTime || replaceKeys) {
|
|
||||||
const timezone = document.getElementById('timezoneSelect').value;
|
|
||||||
displayData = window.backendAPI.processTimestamps(
|
|
||||||
structuredClone(data),
|
|
||||||
timezone
|
|
||||||
); // Use structured clone API instead of JSON.parse/stringify
|
|
||||||
// displayData = window.backendAPI.processTimestamps(JSON.parse(JSON.stringify(data)), timezone);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the data
|
|
||||||
let formattedData = '';
|
|
||||||
if (window.appState.outputFormat === 'yaml') {
|
|
||||||
formattedData = window.backendAPI.jsonToYAML(displayData);
|
|
||||||
document.getElementById('downloadJson').textContent = 'Download YAML Data';
|
|
||||||
} else {
|
|
||||||
formattedData = JSON.stringify(displayData, null, 2);
|
|
||||||
document.getElementById('downloadJson').textContent = 'Download JSON Data';
|
|
||||||
}
|
|
||||||
|
|
||||||
resultsElement.textContent = formattedData;
|
|
||||||
resultsElement.style.display = 'block';
|
|
||||||
downloadContainer.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to display errors
|
|
||||||
function displayError(message) {
|
|
||||||
const errorElement = document.getElementById('error');
|
|
||||||
const loadingElement = document.getElementById('loading');
|
|
||||||
const resultsElement = document.getElementById('results');
|
|
||||||
|
|
||||||
errorElement.textContent = message;
|
|
||||||
loadingElement.style.display = 'none';
|
|
||||||
|
|
||||||
// Clear previous results to ensure they can be redrawn
|
|
||||||
resultsElement.style.display = 'none';
|
|
||||||
resultsElement.textContent = '';
|
|
||||||
|
|
||||||
// Keep tutorial hidden if previously dismissed
|
|
||||||
if (window.appState.tutorialDismissed) {
|
|
||||||
document.querySelectorAll('.tutorial').forEach((element) => {
|
|
||||||
element.style.display = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addEnterKeyListeners() {
|
|
||||||
// Use event delegation for handling Enter key press
|
|
||||||
document.addEventListener('keypress', function (event) {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
// Get the active element
|
|
||||||
const activeElement = document.activeElement;
|
|
||||||
|
|
||||||
if (!activeElement || !activeElement.id) return;
|
|
||||||
|
|
||||||
// Mapping of input fields to their submit buttons
|
|
||||||
const inputToButtonMapping = {
|
|
||||||
ssoToken: null, // Will trigger active tab button
|
|
||||||
username: null, // Will trigger active tab button
|
|
||||||
matchUsername: 'fetchMatches',
|
|
||||||
matchId: 'fetchMatchInfo',
|
|
||||||
userUsername: 'fetchUserInfo',
|
|
||||||
searchUsername: 'fuzzySearch',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (activeElement.id in inputToButtonMapping) {
|
|
||||||
if (inputToButtonMapping[activeElement.id]) {
|
|
||||||
// Click the specific button
|
|
||||||
document
|
|
||||||
.getElementById(inputToButtonMapping[activeElement.id])
|
|
||||||
.click();
|
|
||||||
} else {
|
|
||||||
// Trigger the active tab button
|
|
||||||
triggerActiveTabButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerActiveTabButton() {
|
|
||||||
const activeTab = document
|
|
||||||
.querySelector('.tab.active')
|
|
||||||
.getAttribute('data-tab');
|
|
||||||
switch (activeTab) {
|
|
||||||
case 'stats':
|
|
||||||
document.getElementById('fetchStats').click();
|
|
||||||
break;
|
|
||||||
case 'matches':
|
|
||||||
document.getElementById('fetchMatches').click();
|
|
||||||
break;
|
|
||||||
case 'user':
|
|
||||||
document.getElementById('fetchUserInfo').click();
|
|
||||||
break;
|
|
||||||
case 'other':
|
|
||||||
document.getElementById('fuzzySearch').click();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time options
|
// Time options
|
||||||
function setupTimeOptions() {
|
function setupTimeOptions() {
|
||||||
const convertTimeCheckbox = document.getElementById('convertTimeOption');
|
const convertTimeCheckbox = document.getElementById('convertTimeOption');
|
||||||
@ -535,6 +410,7 @@ function setupDownloadButton() {
|
|||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to synchronize username across tabs
|
// Function to synchronize username across tabs
|
||||||
function syncUsernames() {
|
function syncUsernames() {
|
||||||
const mainUsername = document.getElementById('username').value.trim();
|
const mainUsername = document.getElementById('username').value.trim();
|
||||||
@ -823,3 +699,126 @@ function initializeSessionTracking() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch stats
|
||||||
|
document.getElementById('fetchStats').addEventListener('click', async () => {
|
||||||
|
const username = document.getElementById('username').value.trim();
|
||||||
|
const ssoToken = document.getElementById('ssoToken').value.trim();
|
||||||
|
const platform = document.getElementById('platform').value;
|
||||||
|
const game = document.getElementById('game').value;
|
||||||
|
const apiCall = document.getElementById('apiCall').value;
|
||||||
|
|
||||||
|
const sanitize = document.getElementById('sanitizeOption').checked;
|
||||||
|
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
||||||
|
|
||||||
|
await window.backendAPI.fetchData('/api/stats', {
|
||||||
|
username,
|
||||||
|
ssoToken,
|
||||||
|
platform,
|
||||||
|
game,
|
||||||
|
apiCall,
|
||||||
|
sanitize,
|
||||||
|
replaceKeys,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch match history
|
||||||
|
document.getElementById('fetchMatches').addEventListener('click', async () => {
|
||||||
|
const username = document.getElementById('matchUsername').value.trim();
|
||||||
|
const ssoToken = document.getElementById('ssoToken').value.trim();
|
||||||
|
const platform = document.getElementById('matchPlatform').value;
|
||||||
|
const game = document.getElementById('matchGame').value;
|
||||||
|
|
||||||
|
const sanitize = document.getElementById('sanitizeOption').checked;
|
||||||
|
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
||||||
|
|
||||||
|
await window.backendAPI.fetchData('/api/matches', {
|
||||||
|
username,
|
||||||
|
ssoToken,
|
||||||
|
platform,
|
||||||
|
game,
|
||||||
|
sanitize,
|
||||||
|
replaceKeys,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch match details
|
||||||
|
document
|
||||||
|
.getElementById('fetchMatchInfo')
|
||||||
|
.addEventListener('click', async () => {
|
||||||
|
const matchId = document.getElementById('matchId').value.trim();
|
||||||
|
const ssoToken = document.getElementById('ssoToken').value.trim();
|
||||||
|
const platform = document.getElementById('matchPlatform').value;
|
||||||
|
const game = document.getElementById('matchGame').value;
|
||||||
|
|
||||||
|
const sanitize = document.getElementById('sanitizeOption').checked;
|
||||||
|
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
||||||
|
|
||||||
|
if (!matchId) {
|
||||||
|
displayError('Match ID is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.backendAPI.fetchData('/api/matchInfo', {
|
||||||
|
matchId,
|
||||||
|
ssoToken,
|
||||||
|
platform,
|
||||||
|
game,
|
||||||
|
sanitize,
|
||||||
|
replaceKeys,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch user info
|
||||||
|
document.getElementById('fetchUserInfo').addEventListener('click', async () => {
|
||||||
|
const username = document.getElementById('userUsername').value.trim();
|
||||||
|
const ssoToken = document.getElementById('ssoToken').value.trim();
|
||||||
|
const platform = document.getElementById('userPlatform').value;
|
||||||
|
const userCall = document.getElementById('userCall').value;
|
||||||
|
|
||||||
|
const sanitize = document.getElementById('sanitizeOption').checked;
|
||||||
|
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
||||||
|
|
||||||
|
// For event feed and identities, username is not required
|
||||||
|
if (
|
||||||
|
!username &&
|
||||||
|
userCall !== 'eventFeed' &&
|
||||||
|
userCall !== 'friendFeed' &&
|
||||||
|
userCall !== 'identities'
|
||||||
|
) {
|
||||||
|
displayError('Username is required for this API call');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.backendAPI.fetchData('/api/user', {
|
||||||
|
username,
|
||||||
|
ssoToken,
|
||||||
|
platform,
|
||||||
|
userCall,
|
||||||
|
sanitize,
|
||||||
|
replaceKeys,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fuzzy search
|
||||||
|
document.getElementById('fuzzySearch').addEventListener('click', async () => {
|
||||||
|
const username = document.getElementById('searchUsername').value.trim();
|
||||||
|
const ssoToken = document.getElementById('ssoToken').value.trim();
|
||||||
|
const platform = document.getElementById('searchPlatform').value;
|
||||||
|
|
||||||
|
const sanitize = document.getElementById('sanitizeOption').checked;
|
||||||
|
const replaceKeys = document.getElementById('replaceKeysOption').checked;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
displayError('Username is required for search');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.backendAPI.fetchData('/api/search', {
|
||||||
|
username,
|
||||||
|
ssoToken,
|
||||||
|
platform,
|
||||||
|
sanitize,
|
||||||
|
replaceKeys,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user