From 408df0e2f6d322715ae5361da0c3cb4b33b1329e Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 13 Sep 2026 11:41:09 +0200 Subject: [PATCH] feat: send logs feature that send logs to a distant service. Full integration in ui + expose logger method to frontEnd (#53) --- .env.example | 2 + .gitignore | 3 + README.md | 22 ++++++- app/ipcHandlers.js | 17 +++++- app/sendReport.js | 54 +++++++++++++++++ main.js | 5 ++ package-lock.json | 1 + package.json | 1 + preload.js | 29 +++++++++ public/index.html | 3 +- public/script/reportBug.js | 121 +++++++++++++++++++++++++++++++++++++ 11 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 .env.example create mode 100644 app/sendReport.js create mode 100644 public/script/reportBug.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b5b9b88 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +BUG_REPORT_URL= +BUG_REPORT_API_KEY= diff --git a/.gitignore b/.gitignore index 81ae903..3990917 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ /out /srpm-out +# .env +*.env + # Config JSON File. Create it and fill it if you want to have dev only settings config/config.dev.json diff --git a/README.md b/README.md index 6944a2e..200fdcb 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ Freedom Loader can be configured either through the settings panel in the UI or | Option | Type | Default | Description | |------------------------------|---------|----------|------------------------------------------------------------| | `autoUpdate` | boolean | `true` | Enable automatic application updates | -| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] | +| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | | `customTopBar` | boolean | `true` | Use custom application top bar | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | @@ -367,13 +367,29 @@ Head over to the [Freedom Loader Workshop repository](https://github.com/MasterA ### Bug Reports -Use the GitHub Issues system and include: +You can report bugs either using the built-in application system or via the GitHub Issues system. + +#### Using the Built-in Report Tool + +1. Open Freedom Loader. +2. Trigger the bug report interface. +3. Fill in the title and description of the issue. +4. Choose whether to include today's logs automatically. +5. Submit the report. + +> [!NOTE] +> If you need to attach extra context like screenshots or screen recordings, it is recommended to use the GitHub Issues +system instead. + +#### Using GitHub Issues + +Open a new issue on the GitHub repository and include: - Clear description of the issue - Steps to reproduce - Expected vs actual behavior - Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/` -- Screenshots if applicable +- Screenshots or recordings if applicable ### Feature Requests diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 1f3510a..529437c 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -19,6 +19,7 @@ const config = require("../config"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); const { userThemesPath } = require("../server/helpers/path.helpers"); const { createSystemTray, destroyTray } = require("./tray"); +const {sendReport} = require("./sendReport"); /** * Security whitelist for feature flags that can be modified at runtime. @@ -180,8 +181,13 @@ function registerIpcHandlers(getMainWindow) { */ ipcMain.on("open-config", () => shell.openPath(configFolderPath)); - - + /** + * Front end logger + */ + ipcMain.on("log-error", (_, message) => logger.error(`[Frontend] ${message}`)); + ipcMain.on("log-info", (_, message) => logger.info(`[Frontend] ${message}`)); + ipcMain.on("log-warn", (_, message) => logger.warn(`[Frontend] ${message}`)); + /** * Retrieves available themes from filesystem. */ @@ -199,6 +205,13 @@ function registerIpcHandlers(getMainWindow) { return await reloadThemes(); }); + /** + * Send bug report + */ + ipcMain.handle("send-report", async (_, params) => { + return await sendReport(params); + }) + /** * Updates a runtime feature flag and persists it to disk. * diff --git a/app/sendReport.js b/app/sendReport.js new file mode 100644 index 0000000..48b50da --- /dev/null +++ b/app/sendReport.js @@ -0,0 +1,54 @@ +const fs = require("fs"); +const path = require("path"); +const {logger, logDir} = require("../server/logger"); + +async function sendReport(params) { + try { + const {title, description, includeLogs} = params; + + if (!title || !description) { + throw new Error("Title and description are required."); + } + + const formData = new FormData(); + formData.append("title", title); + formData.append("context", description); + + if (includeLogs === "yes" && logDir) { + // YYYY-MM-DD + const today = new Date().toISOString().split("T")[0]; + + const logFilePath = path.join(logDir, `LOGS-${today}.log`); + + if (fs.existsSync(logFilePath)) { + const logFileContent = fs.readFileSync(logFilePath); + const blob = new Blob([logFileContent], {type: "text/plain"}); + formData.append("file", blob, `LOGS-${today}.log`); + logger.info("Logs attached to bug report."); + } else { + logger.warn(`Log file not found for today: ${logFilePath}`); + } + } + + const response = await fetch(process.env.BUG_REPORT_URL, { + method: "POST", + headers: { + "X-Api-Key": process.env.BUG_REPORT_API_KEY + }, + body: formData + }); + + if (!response.ok) { + logger.error(`Server returned an error: ${response.status}`); + throw new Error(`Server responded with status ${response.status}`); + } + + logger.info("Bug report successfully sent."); + return true; + } catch (err) { + logger.error(`Failed to send bug report: ${err.message}`); + throw err; + } +} + +module.exports = {sendReport}; \ No newline at end of file diff --git a/main.js b/main.js index 46f56b4..3f063b8 100644 --- a/main.js +++ b/main.js @@ -78,6 +78,11 @@ const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder const {createSystemTray, destroyTray} = require("./app/tray"); const { stopServer } = require("./server/server"); +/** + * Expose .env in process.env + */ +require("dotenv").config(); + /** * Global flag indicating if the application is intentionally shutting down. * Used across the app to bypass the "minimize to tray on close" behavior. diff --git a/package-lock.json b/package-lock.json index ec010cf..661bdd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "chalk": "^4.1.2", "debug": "^4.4.1", "discord-rpc": "^4.0.1", + "dotenv": "^17.4.2", "electron-updater": "^6.6.2", "express": "^5.1.0", "express-rate-limit": "^8.2.1", diff --git a/package.json b/package.json index 56b35e4..66ec5ac 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "chalk": "^4.1.2", "debug": "^4.4.1", "discord-rpc": "^4.0.1", + "dotenv": "^17.4.2", "electron-updater": "^6.6.2", "express": "^5.1.0", "express-rate-limit": "^8.2.1", diff --git a/preload.js b/preload.js index aee2ac8..b3c3637 100644 --- a/preload.js +++ b/preload.js @@ -9,6 +9,35 @@ const { contextBridge, ipcRenderer } = require("electron"); */ contextBridge.exposeInMainWorld("electronAPI", { + /** + * Sends error log message. + * + * @param {...any} args - Values to log + */ + logError: (...args) => ipcRenderer.send("log-error", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")), + + /** + * Sends info log message. + * + * @param {...any} args - Values to log + */ + logInfo: (...args) => ipcRenderer.send("log-info", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")), + + /** + * Sends warning log message. + * + * @param {...any} args - Values to log + */ + logWarn: (...args) => ipcRenderer.send("log-warn", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")), + + /** + * Send Bug Report + * + * @param params + * @returns {Promise} + */ + sendReport: (params) => ipcRenderer.invoke('send-report', params), + /** * Return process.platform to renderer */ diff --git a/public/index.html b/public/index.html index 8502f85..b3c2b33 100644 --- a/public/index.html +++ b/public/index.html @@ -21,6 +21,7 @@
+ @@ -295,7 +296,6 @@ -
@@ -314,6 +314,7 @@ + diff --git a/public/script/reportBug.js b/public/script/reportBug.js new file mode 100644 index 0000000..e40079e --- /dev/null +++ b/public/script/reportBug.js @@ -0,0 +1,121 @@ +class BugReportModal { + constructor() { + this.modal = null; + this.form = null; + this.init(); + } + + init() { + this.modal = document.createElement("div"); + this.modal.className = "bug-modal-overlay"; + this.modal.style.cssText = "display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); justify-content:center; align-items:center; z-index:99999; font-family:sans-serif;"; + + this.modal.innerHTML = ` +
+

Report a Bug

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ `; + + document.body.appendChild(this.modal); + this.form = this.modal.querySelector("#bugReportForm"); + + this.bindEvents(); + } + + bindEvents() { + const titleInput = this.modal.querySelector("#bugTitle"); + const descInput = this.modal.querySelector("#bugDescription"); + + titleInput.addEventListener("input", () => { + localStorage.setItem("draft_bug_title", titleInput.value); + }); + + descInput.addEventListener("input", () => { + localStorage.setItem("draft_bug_desc", descInput.value); + }); + + this.modal.querySelector("#bugCancelBtn").addEventListener("click", () => { + this.close(); + }); + + this.modal.addEventListener("click", (e) => { + if (e.target === this.modal) { + this.close(); + } + }); + + this.form.addEventListener("submit", async (e) => { + e.preventDefault(); + const data = { + title: titleInput.value, + description: descInput.value, + includeLogs: this.modal.querySelector("#bugIncludeLogs").value + }; + + window.electronAPI.logInfo("Submitting:", data); + + try { + const success = await window.electronAPI.sendReport(data); + + if (success) { + localStorage.removeItem("draft_bug_title"); + localStorage.removeItem("draft_bug_desc"); + this.form.reset(); + this.close(); + window.showSuccess("Bug report sent successfully!"); + } + } catch (err) { + window.showError("Failed to send bug report."); + window.electronAPI.logError("Failed to send bug report:", err.message); + } + }); + } + + loadState() { + const savedTitle = localStorage.getItem("draft_bug_title"); + const savedDesc = localStorage.getItem("draft_bug_desc"); + + if (savedTitle) { + this.modal.querySelector("#bugTitle").value = savedTitle; + } + if (savedDesc) { + this.modal.querySelector("#bugDescription").value = savedDesc; + } + } + + open() { + this.loadState(); + this.modal.style.display = "flex"; + } + + close() { + this.modal.style.display = "none"; + } +} + +window.bugModal = new BugReportModal(); + +document.getElementById("report-bug-btn").addEventListener("click", () => { + window.bugModal.open(); +}); \ No newline at end of file