refactor: optimize runtime

This commit is contained in:
Rim 2025-04-16 09:07:54 -04:00
parent 18284f7483
commit 13ea56dc66
3 changed files with 474 additions and 495 deletions

252
app.js
View File

@ -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}`);

View File

@ -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;
}

View File

@ -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,
});
});