diff --git a/app.js b/app.js
index 47aaeed..64685b3 100644
--- a/app.js
+++ b/app.js
@@ -895,6 +895,7 @@ app.post("/api/search", async (req, res) => {
}
});
+
// Improved logging endpoint
app.post('/api/log', (req, res) => {
const clientIP = req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress;
diff --git a/src/index.html b/src/index.html
index 11ef4cb..9a4d62a 100644
--- a/src/index.html
+++ b/src/index.html
@@ -32,6 +32,7 @@
+
diff --git a/src/js/backend.js b/src/js/backend.js
index a0aa20a..d252000 100644
--- a/src/js/backend.js
+++ b/src/js/backend.js
@@ -1,184 +1,20 @@
-let tutorialDismissed = false;
-let currentData = null;
-let outputFormat = "json";
+// Export the functions that frontend.js needs to call
+window.backendAPI = {
+ fetchData,
+ jsonToYAML,
+ formatDuration,
+ formatEpochTime,
+ processTimestamps
+};
+
+window.appState = {
+ currentData: null,
+ outputFormat: "json",
+ tutorialDismissed: false
+};
-// Initialize once DOM is loaded
document.addEventListener("DOMContentLoaded", function() {
- initTabSwitching();
- addEnterKeyListeners();
- setupDownloadButton();
- setupFormatSelector();
- setupProcessingOptions();
- setupTimeOptions();
- addSyncListeners();
-});
-
-// Tab switching logic
-function initTabSwitching() {
- document.querySelectorAll(".tab").forEach((tab) => {
- tab.addEventListener("click", () => {
- document
- .querySelectorAll(".tab")
- .forEach((t) => t.classList.remove("active"));
- document
- .querySelectorAll(".tab-content")
- .forEach((c) => c.classList.remove("active"));
-
- tab.classList.add("active");
- const tabId = tab.getAttribute("data-tab");
- document.getElementById(`${tabId}-tab`).classList.add("active");
- });
- });
-}
-
-// Setup processing options (sanitize/replace)
-function setupProcessingOptions() {
- document.getElementById("sanitizeOption").addEventListener("change", function() {
- if (currentData) {
- // Re-fetch with new options
- const activeTab = document.querySelector(".tab.active").getAttribute("data-tab");
- triggerActiveTabButton();
- }
- });
-
- document.getElementById("replaceKeysOption").addEventListener("change", function() {
- if (currentData) {
- // Re-fetch with new options
- const activeTab = document.querySelector(".tab.active").getAttribute("data-tab");
- triggerActiveTabButton();
- }
- });
-}
-
-// Setup format selector
-function setupFormatSelector() {
- document.getElementById("outputFormat").addEventListener("change", function() {
- outputFormat = this.value;
- if (currentData) {
- displayResults(currentData);
- }
- });
-}
-
-// 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 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 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 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 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 fetchData("/api/search", {
- username,
- ssoToken,
- platform,
- sanitize,
- replaceKeys
- });
+ // Backend-specific initialization
});
// YAML conversion function
@@ -251,8 +87,8 @@ async function fetchData(endpoint, requestData) {
loadingElement.style.display = "block";
// Hide tutorial if not already dismissed
- if (!tutorialDismissed) {
- tutorialDismissed = true;
+ if (!window.appState.tutorialDismissed) {
+ window.appState.tutorialDismissed = true;
document.querySelectorAll(".tutorial").forEach(element => {
element.style.display = "none";
});
@@ -260,7 +96,7 @@ async function fetchData(endpoint, requestData) {
// Validate request data
if (!requestData.ssoToken) {
- displayError("SSO Token is required");
+ window.uiAPI.displayError("SSO Token is required");
loadingElement.style.display = "none";
return;
}
@@ -297,18 +133,18 @@ async function fetchData(endpoint, requestData) {
}
if (data.error) {
- displayError(data.error);
+ window.uiAPI.displayError(data.error);
} else if (data.status === "error") {
- displayError(data.message || "An error occurred");
+ window.uiAPI.displayError(data.message || "An error occurred");
} else {
- currentData = data;
- displayResults(data);
+ window.appState.currentData = data;
+ window.uiAPI.displayResults(data);
}
} catch (error) {
if (error.name === 'AbortError') {
- displayError("Request timed out. Please try again.");
+ window.uiAPI.displayError("Request timed out. Please try again.");
} else {
- displayError(
+ window.uiAPI.displayError(
`Error: ${error.message || "An error occurred while fetching data."}`
);
console.error("Fetch error:", error);
@@ -318,108 +154,6 @@ async function fetchData(endpoint, requestData) {
}
}
-// 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 = processTimestamps(structuredClone(data), timezone); // Use structured clone API instead of JSON.parse/stringify
- // displayData = processTimestamps(JSON.parse(JSON.stringify(data)), timezone);
- }
-
- // Format the data
- let formattedData = '';
- if (outputFormat === 'yaml') {
- formattedData = 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 (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;
- }
-}
-
// Function to convert seconds to human readable duration
function formatDuration(seconds) {
if (!seconds || isNaN(seconds)) return seconds;
@@ -490,101 +224,6 @@ function processTimestamps(data, timezone, keysToConvert = ['date', 'dateAdded',
return result;
}
-// Time options
-function setupTimeOptions() {
- const convertTimeCheckbox = document.getElementById('convertTimeOption');
- const timezoneSelect = document.getElementById('timezoneSelect');
-
- convertTimeCheckbox.addEventListener('change', function() {
- timezoneSelect.disabled = !this.checked;
-
- if (currentData) {
- displayResults(currentData); // Refresh the display
- }
- });
-
- timezoneSelect.addEventListener('change', function() {
- if (currentData) {
- displayResults(currentData); // Refresh the display
- }
- });
-}
-
-// Download Button
-function setupDownloadButton() {
- const downloadBtn = document.getElementById("downloadJson");
- if (!downloadBtn) return;
-
- downloadBtn.addEventListener("click", function() {
- const resultsElement = document.getElementById("results");
- const jsonData = resultsElement.textContent;
-
- if (!jsonData) {
- alert("No data to download");
- return;
- }
-
- // Create a Blob with the data
- const contentType = outputFormat === 'yaml' ? 'text/yaml' : 'application/json';
- const blob = new Blob([jsonData], { type: contentType });
-
- // Create a temporary link element
- const a = document.createElement("a");
- a.href = URL.createObjectURL(blob);
-
- // Generate a filename with timestamp
- const date = new Date();
- const timestamp = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}_${String(date.getHours()).padStart(2, '0')}-${String(date.getMinutes()).padStart(2, '0')}`;
- const extension = outputFormat === 'yaml' ? 'yaml' : 'json';
- a.download = `cod_stats_${timestamp}.${extension}`;
-
- // Trigger download
- document.body.appendChild(a);
- a.click();
-
- // Clean up
- document.body.removeChild(a);
- });
-}
-// Function to synchronize username across tabs
-function syncUsernames() {
- const mainUsername = document.getElementById("username").value.trim();
-
- // Only sync if there's a value
- if (mainUsername) {
- document.getElementById("matchUsername").value = mainUsername;
- document.getElementById("userUsername").value = mainUsername;
- document.getElementById("searchUsername").value = mainUsername;
- }
-
- // Also sync platform across tabs when it changes
- const mainPlatform = document.getElementById("platform").value;
- document.getElementById("matchPlatform").value = mainPlatform;
- document.getElementById("userPlatform").value = mainPlatform;
- document.getElementById("searchPlatform").value = mainPlatform;
-}
-
-// Sync listeners for persistent usernames
-function addSyncListeners() {
- // Add change listeners for username sync
- document.getElementById("username").addEventListener("change", syncUsernames);
- document.getElementById("matchUsername").addEventListener("change", function() {
- document.getElementById("username").value = this.value;
- syncUsernames();
- });
- document.getElementById("userUsername").addEventListener("change", function() {
- document.getElementById("username").value = this.value;
- syncUsernames();
- });
- document.getElementById("searchUsername").addEventListener("change", function() {
- document.getElementById("username").value = this.value;
- syncUsernames();
- });
-
- // Add change listeners for platform sync
- document.getElementById("platform").addEventListener("change", syncUsernames);
-}
-
// Initialize session tracking when the page loads
document.addEventListener('DOMContentLoaded', () => {
// Generate a unique session ID
@@ -888,4 +527,4 @@ document.addEventListener('DOMContentLoaded', () => {
stack: e.error?.stack
});
});
-});
\ No newline at end of file
+});
diff --git a/src/js/frontend.js b/src/js/frontend.js
new file mode 100644
index 0000000..85707ed
--- /dev/null
+++ b/src/js/frontend.js
@@ -0,0 +1,380 @@
+window.uiAPI = {
+ displayResults,
+ displayError
+ };
+
+// Initialize once DOM is loaded
+document.addEventListener("DOMContentLoaded", function() {
+ initTabSwitching();
+ addEnterKeyListeners();
+ setupDownloadButton();
+ setupFormatSelector();
+ setupProcessingOptions();
+ setupTimeOptions();
+ addSyncListeners();
+});
+
+// Tab switching logic
+function initTabSwitching() {
+ document.querySelectorAll(".tab").forEach((tab) => {
+ tab.addEventListener("click", () => {
+ document
+ .querySelectorAll(".tab")
+ .forEach((t) => t.classList.remove("active"));
+ document
+ .querySelectorAll(".tab-content")
+ .forEach((c) => c.classList.remove("active"));
+
+ tab.classList.add("active");
+ const tabId = tab.getAttribute("data-tab");
+ document.getElementById(`${tabId}-tab`).classList.add("active");
+ });
+ });
+}
+
+// Setup processing options (sanitize/replace)
+function setupProcessingOptions() {
+ document.getElementById("sanitizeOption").addEventListener("change", function() {
+ if (window.appState.currentData) { // Call window.appState
+ // Re-fetch with new options
+ const activeTab = document.querySelector(".tab.active").getAttribute("data-tab");
+ triggerActiveTabButton();
+ }
+ });
+
+ document.getElementById("replaceKeysOption").addEventListener("change", function() {
+ if (window.appState.currentData) {
+ // Re-fetch with new options
+ const activeTab = document.querySelector(".tab.active").getAttribute("data-tab");
+ triggerActiveTabButton();
+ }
+ });
+}
+
+// Setup format selector
+function setupFormatSelector() {
+ document.getElementById("outputFormat").addEventListener("change", function() {
+ window.appState.outputFormat = this.value;
+ if (window.appState.currentData) {
+ displayResults(window.appState.currentData);
+ }
+ });
+ }
+
+ // 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
+function setupTimeOptions() {
+ const convertTimeCheckbox = document.getElementById('convertTimeOption');
+ const timezoneSelect = document.getElementById('timezoneSelect');
+
+ convertTimeCheckbox.addEventListener('change', function() {
+ timezoneSelect.disabled = !this.checked;
+
+ if ((window.appState.currentData)) {
+ displayResults((window.appState.currentData)); // Refresh the display
+ }
+ });
+
+ timezoneSelect.addEventListener('change', function() {
+ if ((window.appState.currentData)) {
+ displayResults((window.appState.currentData)); // Refresh the display
+ }
+ });
+}
+
+// Download Button
+function setupDownloadButton() {
+ const downloadBtn = document.getElementById("downloadJson");
+ if (!downloadBtn) return;
+
+ downloadBtn.addEventListener("click", function() {
+ const resultsElement = document.getElementById("results");
+ const jsonData = resultsElement.textContent;
+
+ if (!jsonData) {
+ alert("No data to download");
+ return;
+ }
+
+ // Create a Blob with the data
+ const contentType = window.appState.outputFormat === 'yaml' ? 'text/yaml' : 'application/json';
+ const blob = new Blob([jsonData], { type: contentType });
+
+ // Create a temporary link element
+ const a = document.createElement("a");
+ a.href = URL.createObjectURL(blob);
+
+ // Generate a filename with timestamp
+ const date = new Date();
+ const timestamp = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}_${String(date.getHours()).padStart(2, '0')}-${String(date.getMinutes()).padStart(2, '0')}`;
+ const extension = window.appState.outputFormat === 'yaml' ? 'yaml' : 'json';
+ a.download = `cod_stats_${timestamp}.${extension}`;
+
+ // Trigger download
+ document.body.appendChild(a);
+ a.click();
+
+ // Clean up
+ document.body.removeChild(a);
+ });
+}
+// Function to synchronize username across tabs
+function syncUsernames() {
+ const mainUsername = document.getElementById("username").value.trim();
+
+ // Only sync if there's a value
+ if (mainUsername) {
+ document.getElementById("matchUsername").value = mainUsername;
+ document.getElementById("userUsername").value = mainUsername;
+ document.getElementById("searchUsername").value = mainUsername;
+ }
+
+ // Also sync platform across tabs when it changes
+ const mainPlatform = document.getElementById("platform").value;
+ document.getElementById("matchPlatform").value = mainPlatform;
+ document.getElementById("userPlatform").value = mainPlatform;
+ document.getElementById("searchPlatform").value = mainPlatform;
+}
+
+// Sync listeners for persistent usernames
+function addSyncListeners() {
+ // Add change listeners for username sync
+ document.getElementById("username").addEventListener("change", syncUsernames);
+ document.getElementById("matchUsername").addEventListener("change", function() {
+ document.getElementById("username").value = this.value;
+ syncUsernames();
+ });
+ document.getElementById("userUsername").addEventListener("change", function() {
+ document.getElementById("username").value = this.value;
+ syncUsernames();
+ });
+ document.getElementById("searchUsername").addEventListener("change", function() {
+ document.getElementById("username").value = this.value;
+ syncUsernames();
+ });
+
+ // Add change listeners for platform sync
+ document.getElementById("platform").addEventListener("change", syncUsernames);
+}