Compare commits

...

12 Commits

14 changed files with 577 additions and 63 deletions

View File

@@ -61,9 +61,9 @@ The primary goal is to make media downloading accessible to users who want offli
### User Interface ### User Interface
- **Custom window controls** - Frameless window with custom top bar (optional) - **Custom window controls** - Frameless window with custom top bar (optional)
- **Custom themes** — ZIP-based theme system with live preview - **Custom themes** - ZIP-based theme system with live preview
- **Theme Workshop** — web-based theme creator and browser - **Theme Workshop** - web-based theme creator and browser
- **Toast notifications** — in-app feedback system - **Toast notifications** - in-app feedback system
- **Real-time progress tracking** - Live download progress bar with network speed indicator - **Real-time progress tracking** - Live download progress bar with network speed indicator
- **Server-Sent Events (SSE)** - Non-blocking progress updates via event streaming - **Server-Sent Events (SSE)** - Non-blocking progress updates via event streaming
- **Settings panel** - In-app configuration interface with live updates - **Settings panel** - In-app configuration interface with live updates
@@ -80,9 +80,10 @@ The primary goal is to make media downloading accessible to users who want offli
- **Rate limiting** - Express-based request throttling to prevent server overload - **Rate limiting** - Express-based request throttling to prevent server overload
- **Graceful shutdown** - Proper cleanup of servers, logs, and RPC connections - **Graceful shutdown** - Proper cleanup of servers, logs, and RPC connections
- **System notifications** - Windows notifications on download completion (clickable to open folder) - **System notifications** - Windows notifications on download completion (clickable to open folder)
- **Splash screen** — animated loading screen on startup - **Splash screen** - animated loading screen on startup
- **Stop download** — ability to cancel in-progress downloads - **Stop download** - ability to cancel in-progress downloads
- **Playlist folders** — dedicated folder creation per playlist - **Playlist folders** - dedicated folder creation per playlist
- **Keep Playlist Order** - Add an Index before the title, could be useful for an album for example.
## Installation ## Installation
@@ -105,7 +106,7 @@ The primary goal is to make media downloading accessible to users who want offli
### Linux ### Linux
#### Fedora (via Copr — recommended) #### Fedora (via Copr - recommended)
Installing via Copr lets `dnf` handle future updates automatically: Installing via Copr lets `dnf` handle future updates automatically:
@@ -178,7 +179,8 @@ The application supports various configuration options:
- Custom download directory selection - Custom download directory selection
- Discord Rich Presence activation - Discord Rich Presence activation
- Theme customization - Theme customization
- Log level configuration - Log level
- And somes to see in the list [Configuration Options](#configuration-options) available
### Troubleshooting ### Troubleshooting
@@ -226,6 +228,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
```json ```json
{ {
"autoUpdate": true, "autoUpdate": true,
"systemTray": true,
"discordRPC": true, "discordRPC": true,
"customTopBar": true, "customTopBar": true,
"autoCheckInfo": true, "autoCheckInfo": true,
@@ -250,6 +253,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
| Option | Type | Default | Description | | Option | Type | Default | Description |
|------------------------------|---------|----------|------------------------------------------------------------| |------------------------------|---------|----------|------------------------------------------------------------|
| `autoUpdate` | boolean | `true` | Enable automatic application updates | | `autoUpdate` | boolean | `true` | Enable automatic application updates |
| `systemTray` | boolean | `true` | Allow app to minimize on close |
| `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration |
| `customTopBar` | boolean | `true` | Use custom application top bar | | `customTopBar` | boolean | `true` | Use custom application top bar |
| `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste |
@@ -292,15 +296,21 @@ Freedom-Loader/
│ ├── pathValidator.js │ ├── pathValidator.js
│ ├── splashManager.js │ ├── splashManager.js
│ ├── themeManager.js │ ├── themeManager.js
│ ├── tray.js
│ ├── windowManager.js │ ├── windowManager.js
│ └── ytDlpUpdater.js │ └── ytDlpUpdater.js
├── build/ # Build resources and assets ├── build/ # Build resources and assets
│ ├── app-icon.ico
│ ├── app-icon.png
│ ├── banner.png
│ └── ...
├── config/ # Configuration files ├── config/ # Configuration files
│ ├── config.default.json │ ├── config.default.json
│ └── config.dev.json │ └── config.dev.json # Configuration Used when i dev mode
├── dist/ # Compiled executables (.exe, .AppImage, .deb, .rpm, .snap)
├── public/ # Frontend assets and UI ├── public/ # Frontend assets and UI
│ ├── index.html │ ├── index.html
│ ├── splash.html │ ├── splash.html # Splash Screen (Start of the application)
│ ├── assets/ │ ├── assets/
│ │ ├── icon/ # Application icons │ │ ├── icon/ # Application icons
│ │ └── logo/ # Logo assets │ │ └── logo/ # Logo assets
@@ -319,19 +329,8 @@ Freedom-Loader/
│ ├── styles.css │ ├── styles.css
│ ├── variables.css │ ├── variables.css
│ ├── components/ # Component-specific styles │ ├── components/ # Component-specific styles
│ │ ├── checkbox.css
│ │ ├── editpathbutton.css
│ │ ├── loader.css
│ │ ├── progressBar.css
│ │ └── toast.css
│ └── layout/ # Layout styles │ └── layout/ # Layout styles
│ ├── container.css ├── resources/ # Internal resources (binaries : yt-dlp, ffmpeg...)
│ ├── form.css
│ ├── header.css
│ ├── settingsPanel.css
│ ├── topbar.css
│ └── videoinfo.css
├── resources/ # Internal resources (binaries)
├── server/ # Express backend server ├── server/ # Express backend server
│ ├── logger.js │ ├── logger.js
│ ├── server.js │ ├── server.js
@@ -355,16 +354,16 @@ Freedom-Loader/
├── theme/ # Theme system ├── theme/ # Theme system
│ ├── template.theme.json # Theme template │ ├── template.theme.json # Theme template
│ ├── Dark/ # Default dark theme │ ├── Dark/ # Default dark theme
│ │ └── dark.theme.json
│ └── Light/ # Default light theme │ └── Light/ # Default light theme
│ └── light.theme.json ├── BINARIES.md # External dependencies & binaries documentation
├── main.js # Electron main process entry point
├── preload.js # Electron preload script (IPC bridge)
├── config.js # Global configuration loader
├── package.json # Project metadata and dependencies
├── CODE_OF_CONDUCT.md # Community guidelines ├── CODE_OF_CONDUCT.md # Community guidelines
├── config.js # Global configuration loader
├── CONTRIBUTING.md # Contribution guidelines ├── CONTRIBUTING.md # Contribution guidelines
├── jest.config.js # Unit testing configuration
├── LICENSE # GPLv3 license ├── LICENSE # GPLv3 license
├── main.js # Electron main process entry point
├── package.json # Project metadata and dependencies
├── preload.js # Electron preload script (IPC bridge)
└── README.md # This file └── README.md # This file
``` ```
@@ -391,7 +390,7 @@ Freedom Loader includes a web-based theme creator available at [Freedom Loader W
### Installing a custom theme ### Installing a custom theme
1. Download a `.zip` theme file 1. Download a `.zip` theme file
2. Drop it in the `theme/` folder of your Freedom Loader installation 2. Drop it in the `theme/` folder of your Freedom Loader installation
3. Restart the app — your theme appears in the settings panel 3. Restart the app - your theme appears in the settings panel
## Technology Stack ## Technology Stack

View File

@@ -1,6 +1,8 @@
const config = require('../config'); const config = require('../config');
const RPC = require("discord-rpc"); const RPC = require("discord-rpc");
const { logger } = require("../server/logger"); const { logger } = require("../server/logger");
const fs = require("fs");
const path = require("path");
/** /**
* Discord Application Client ID used to authenticate the RPC connection. * Discord Application Client ID used to authenticate the RPC connection.
@@ -20,6 +22,51 @@ const rpc = new RPC.Client({ transport: "ipc" });
*/ */
let intervalId; let intervalId;
/**
* Automagically fixes Discord RPC on Linux for Sandboxed versions (Flatpak & Snap).
* It creates a symlink from the sandbox IPC socket to the standard system location.
*
* @returns {void}
*/
function fixLinuxDiscordRPC() {
// Only execute on Linux
if (process.platform !== "linux") return;
/**
* Get the user's runtime directory (e.g., /run/user/1000).
* @type {string}
*/
const xdgDir = process.env.XDG_RUNTIME_DIR || `/run/user/${process.getuid()}`;
const standardSocket = path.join(xdgDir, "discord-ipc-0");
// If the standard socket already exists (Native Discord), do nothing.
if (fs.existsSync(standardSocket)) return;
/**
* Paths for known sandboxed Discord IPC sockets.
*/
const flatpakSocket = path.join(xdgDir, "app/com.discordapp.Discord/discord-ipc-0");
const snapSocket = path.join(xdgDir, "snap.discord/discord-ipc-0");
let targetSocket = null;
if (fs.existsSync(flatpakSocket)) {
targetSocket = flatpakSocket;
} else if (fs.existsSync(snapSocket)) {
targetSocket = snapSocket;
}
// If a sandboxed socket is found, create the symlink stealthily.
if (targetSocket) {
try {
fs.symlinkSync(targetSocket, standardSocket);
logger.info(`Discord RPC bridge created automatically targeting: ${targetSocket}`);
} catch (err) {
logger.warn(`Failed to create Discord RPC bridge: ${err.message}`);
}
}
}
/** /**
* Initializes Discord Rich Presence (RPC) connection. * Initializes Discord Rich Presence (RPC) connection.
* *
@@ -32,6 +79,11 @@ let intervalId;
*/ */
function startRPC() { function startRPC() {
/**
* Patch that create Simlinks for Linux environment;
*/
fixLinuxDiscordRPC();
/** /**
* Rich Presence payload describing current application state * Rich Presence payload describing current application state
* shown in Discord user profile. * shown in Discord user profile.
@@ -60,28 +112,30 @@ function startRPC() {
* *
* Responsibilities: * Responsibilities:
* - Clear any running intervals * - Clear any running intervals
* - Remove current activity from Discord * - Remove current activity from Discord if connected
* - Destroy RPC transport connection * - Destroy RPC transport connection
* - Handle cleanup errors safely * - Handle cleanup errors safely
*/ */
async function stopRPC(){ async function stopRPC() {
if (rpc) {
try { try {
if (intervalId) clearInterval(intervalId); if (intervalId) clearInterval(intervalId);
/** /**
* Ensures RPC connection exists before attempting cleanup. * Ensures RPC connection AND the underlying socket exist
* before attempting to clear activity to prevent crash.
*/ */
if (rpc && rpc.transport) { if (rpc.transport && rpc.transport.socket) {
await rpc.clearActivity() await rpc.clearActivity();
await rpc.destroy();
} else{
logger.error("Not Able to close RPC")
} }
await rpc.destroy();
} catch (err) { } catch (err) {
logger.error("Error while closing the RPC:", err); logger.error("Error while closing the RPC:", err);
} }
} }
}
module.exports = { startRPC, stopRPC}; module.exports = { startRPC, stopRPC};

View File

@@ -27,6 +27,7 @@ const { userThemesPath } = require("../server/helpers/path.helpers");
*/ */
const FEATURE_WHITELIST = new Set([ const FEATURE_WHITELIST = new Set([
"autoUpdate", "autoUpdate",
"systemTray",
"discordRPC", "discordRPC",
"customTopBar", "customTopBar",
"autoCheckInfo", "autoCheckInfo",

108
app/tray.js Normal file
View File

@@ -0,0 +1,108 @@
const {app, Menu, Tray, nativeImage} = require("electron");
const path = require("path");
const {logger} = require("../server/logger"); // Ajuste le chemin si besoin
const fs = require("fs");
/**
* Global reference to the Tray instance to prevent garbage collection.
* @type {Tray | null}
*/
let tray = null;
/**
* Creates and configures the cross-platform System Tray.
*
* Responsibilities:
* - Loads the appropriate icon format based on the operating system.
* - Builds the right-click context menu.
* - Handles left-click behavior to toggle main window visibility.
*
* @param {import('electron').BrowserWindow} mainWindow - The main application window.
* @param {boolean} devMode - Indicates whether the application is running in development mode.
* @returns {Tray} The created Tray instance.
*/
function createSystemTray(mainWindow, devMode) {
// Prevent creating multiple tray instances
if (tray) return tray;
const isWin = process.platform === "win32";
const iconName = isWin ? "app-icon.ico" : "app-icon-64x64.png";
// Attempt to load the icon from the local build directory first
let iconPath = path.join(__dirname, "..", "build", iconName);
// Fallback to the extracted resources path if running in production
if (!devMode && !fs.existsSync(iconPath)) {
iconPath = path.join(process.resourcesPath, iconName);
}
if (!fs.existsSync(iconPath)) {
logger.error(`System Tray icon not found at resolved path: ${iconPath}`);
} else {
logger.info("System Tray icon resolved successfully.");
}
const icon = nativeImage.createFromPath(iconPath);
if (icon.isEmpty()) {
logger.error("System Tray icon file was found but could not be decoded by Electron (image is empty).");
}
tray = new Tray(icon);
tray.setToolTip("Freedom Loader");
/**
* System Tray Context Menu (Right-click action).
*/
const contextMenu = Menu.buildFromTemplate([
{
label: "Show Freedom Loader",
click: () => {
mainWindow.show();
},
},
{ type: "separator" },
{
label: "Quit",
click: () => {
/**
* Flags the application for a deliberate exit,
* bypassing the default "minimize to tray" behavior.
*/
app.isQuitting = true;
app.quit();
},
},
]);
tray.setContextMenu(contextMenu);
/**
* Left-click behavior: Toggles main window visibility.
* but it remains standard practice for Windows and Linux environments.
*/
tray.on("click", () => {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
mainWindow.focus();
}
});
return tray;
}
/**
* Safely destroys the tray icon to prevent OS-level crashes (especially on Linux/Wayland).
* Should be called right before the application quits.
*/
function destroyTray() {
if (tray && !tray.isDestroyed()) {
tray.destroy();
tray = null;
logger.info("System Tray destroyed cleanly.");
}
}
module.exports = {createSystemTray, destroyTray};

View File

@@ -81,6 +81,19 @@ async function createMainWindow() {
throw err; throw err;
} }
/**
* Intercepts the window close event.
* If the app is not explicitly quitting (via Tray menu),
* cancels the destruction and hides the window instead.
*/
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault();
mainWindow.hide();
return false;
}
});
/** /**
* Cleans up window reference when the main window is closed. * Cleans up window reference when the main window is closed.
* Prevents memory leaks and allows recreation if needed. * Prevents memory leaks and allows recreation if needed.

BIN
build/app-icon-64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,5 +1,6 @@
{ {
"autoUpdate": true, "autoUpdate": true,
"systemTray": true,
"discordRPC": true, "discordRPC": true,
"customTopBar": true, "customTopBar": true,
"autoCheckInfo": true, "autoCheckInfo": true,

17
main.js
View File

@@ -80,7 +80,9 @@ const { createMainWindow, getMainWindow } = require("./app/windowManager");
const { registerIpcHandlers } = require("./app/ipcHandlers"); const { registerIpcHandlers } = require("./app/ipcHandlers");
const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager"); const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager");
const { userThemesPath, initUserThemes, isWindows, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers"); const { userThemesPath, initUserThemes, isWindows, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers");
const {createSystemTray, destroyTray} = require("./app/tray");
app.isQuitting = false;
/** /**
* If another instance want to run * If another instance want to run
@@ -127,6 +129,11 @@ app.whenReady().then(async () => {
closeSplashWindow(); closeSplashWindow();
getMainWindow().show(); getMainWindow().show();
if (configFeatures.systemTray) {
createSystemTray(getMainWindow(), config.devMode);
}
if (configFeatures.discordRPC) startRPC(); if (configFeatures.discordRPC) startRPC();
if (configFeatures.autoUpdate) initAutoUpdater(getMainWindow()); if (configFeatures.autoUpdate) initAutoUpdater(getMainWindow());
@@ -138,11 +145,17 @@ app.whenReady().then(async () => {
}); });
app.on("window-all-closed", () => { app.on("window-all-closed", () => {
logger.info("Shutting down..."); if (app.isQuitting) {
app.quit(); logger.info("Shutting down...");
app.quit();
} else if (process.platform !== "darwin") {
logger.info("Main window closed, app running in background (Tray).");
}
}); });
app.on("before-quit", async () => { app.on("before-quit", async () => {
app.isQuitting = true;
destroyTray();
await stopRPC(); await stopRPC();
logger.info("All services stopped. Have a nice day!"); logger.info("All services stopped. Have a nice day!");
logSessionEnd(); logSessionEnd();

246
package-lock.json generated
View File

@@ -27,6 +27,7 @@
"electron": "^43.3.0", "electron": "^43.3.0",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"jest": "^30.4.2", "jest": "^30.4.2",
"nodemon": "^3.1.14",
"playwright": "^1.62.1" "playwright": "^1.62.1"
} }
}, },
@@ -2939,6 +2940,19 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bindings": { "node_modules/bindings": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@@ -3015,6 +3029,19 @@
"node": "20 || >=22" "node": "20 || >=22"
} }
}, },
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.28.7", "version": "4.28.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz",
@@ -3265,6 +3292,31 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/chownr": { "node_modules/chownr": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
@@ -4465,6 +4517,19 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
@@ -4741,6 +4806,19 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/glob/node_modules/balanced-match": { "node_modules/glob/node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -5031,6 +5109,13 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true,
"license": "ISC"
},
"node_modules/immediate": { "node_modules/immediate": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
@@ -5110,6 +5195,29 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": { "node_modules/is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -5130,6 +5238,29 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-promise": { "node_modules/is-promise": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -6653,6 +6784,58 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/nodemon": {
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
"integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
"ignore-by-default": "^1.0.1",
"minimatch": "^10.2.1",
"pstree.remy": "^1.1.8",
"semver": "^7.5.3",
"simple-update-notifier": "^2.0.0",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"bin": {
"nodemon": "bin/nodemon.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/nodemon/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/nodemon/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/nopt": { "node_modules/nopt": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz",
@@ -7250,6 +7433,13 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true,
"license": "MIT"
},
"node_modules/pump": { "node_modules/pump": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
@@ -7467,6 +7657,32 @@
"util-deprecate": "~1.0.1" "util-deprecate": "~1.0.1"
} }
}, },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/register-scheme": { "node_modules/register-scheme": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17", "resolved": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17",
@@ -8324,6 +8540,19 @@
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/toidentifier": { "node_modules/toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -8333,6 +8562,16 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
"dev": true,
"license": "ISC",
"bin": {
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/tr46": { "node_modules/tr46": {
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -8419,6 +8658,13 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true,
"license": "MIT"
},
"node_modules/undici": { "node_modules/undici": {
"version": "7.29.0", "version": "7.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",

View File

@@ -1,7 +1,7 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.6.1", "version": "1.6.2-preview",
"author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>", "author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>",
"description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms", "description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms",
"homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/", "homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/",
@@ -15,7 +15,12 @@
}, },
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron --trace-warnings .", "start": "electron --no-warnings .",
"start:warn": "electron --trace-warnings .",
"start:debug": "WAYLAND_DEBUG=1 ELECTRON_ENABLE_LOGGING=1 ELECTRON_ENABLE_STACK_DUMPING=1 electron --enable-logging --v=1 .",
"dev": "nodemon --watch server --watch app --watch main.js --watch config.js --ext js,json --exec \"electron --no-warnings .\"",
"dev:warn": "nodemon --watch server --watch app --watch main.js --watch config.js --ext js,json --exec \"electron --trace-warnings .\"",
"dev:debug": "nodemon --watch server --watch app --watch main.js --watch config.js --ext js,json --exec \"WAYLAND_DEBUG=1 ELECTRON_ENABLE_LOGGING=1 ELECTRON_ENABLE_STACK_DUMPING=1 electron --enable-logging --v=1 .\"",
"build": "electron-builder", "build": "electron-builder",
"build:win": "electron-builder --win", "build:win": "electron-builder --win",
@@ -50,6 +55,7 @@
"electron": "^43.3.0", "electron": "^43.3.0",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"jest": "^30.4.2", "jest": "^30.4.2",
"nodemon": "^3.1.14",
"playwright": "^1.62.1" "playwright": "^1.62.1"
}, },
"build": { "build": {
@@ -78,6 +84,14 @@
"from": "theme", "from": "theme",
"to": "theme" "to": "theme"
}, },
{
"from": "build/app-icon.ico",
"to": "app-icon.ico"
},
{
"from": "build/app-icon-64x64.png",
"to": "app-icon-64x64.png"
},
{ {
"from": "build/confirm-icon.png", "from": "build/confirm-icon.png",
"to": "confirm-icon.png" "to": "confirm-icon.png"

View File

@@ -127,13 +127,21 @@
</div> </div>
</div> </div>
<!-- <div class="setting-item"> <div class="setting-item">
<input type="checkbox" data-key="outputTitleCheck" id="outputTitleCheck"> <input data-key="systemTray" id="systemTray" type="checkbox">
<div class="setting-info"> <div class="setting-info">
<span class="setting-title">Output title (special chars)</span> <span class="setting-title">System Tray</span>
<small>Check for unusual characters in output titles.</small> <small>Minimize app on close, in the System Tray.</small>
</div>
</div> </div>
</div> -->
<!-- <div class="setting-item">
<input type="checkbox" data-key="outputTitleCheck" id="outputTitleCheck">
<div class="setting-info">
<span class="setting-title">Output title (special chars)</span>
<small>Check for unusual characters in output titles.</small>
</div>
</div> -->
<div class="setting-item"> <div class="setting-item">
<input data-key="autoCheckInfo" id="autoCheckInfo" type="checkbox"> <input data-key="autoCheckInfo" id="autoCheckInfo" type="checkbox">

View File

@@ -1,21 +1,54 @@
/** /**
* Retrieves the application version from the Electron main process * Retrieves the application version and build state from the backend.
* and displays it in the UI version badge (bottom-right corner). * Displays a formatted string indicating packaging state and preview status.
* Hides the "Packaged" state for production builds to keep the UI clean.
* *
* This is mainly used for debugging and build identification. * Example outputs: "Not Packaged Preview v1.6.2" or "Preview v1.6.2"
*
* @returns {Promise<void>}
*/ */
async function versionLabel() { async function versionLabel() {
/**
* Raw version string from the backend (e.g., "dev-1.6.2-preview" or "v1.6.2")
* @type {string}
*/
const appVersion = await window.electronAPI.getVersion(); const appVersion = await window.electronAPI.getVersion();
/** /**
* UI element displaying the current application version. * UI element displaying the current application version.
* Updated at runtime after IPC call resolves. * @type {HTMLElement | null}
*/ */
const versionBadge = document.getElementById("version-badge"); const versionBadge = document.getElementById("version-badge");
if (!versionBadge) return; if (!versionBadge) return;
versionBadge.textContent = `${appVersion}`; /**
* Extracts the numeric semantic version (e.g., "1.6.2").
* @type {RegExpMatchArray | null}
*/
const versionMatch = appVersion.match(/(\d+\.\d+\.\d+)/);
const baseVersion = versionMatch ? versionMatch[0] : "Unknown";
/**
* Determines the packaging state. The backend prepends "dev-" if unpackaged.
* @type {boolean}
*/
const isPackaged = !appVersion.includes("dev-");
/**
* Determines if the current build is a preview version.
* @type {boolean}
*/
const isPreview = appVersion.includes("preview");
/**
* Construct the final display string.
* If packaged, we don't mention it. If not, we explicitly say "Not Packaged ".
*/
const packageStateStr = isPackaged ? "" : "Not Packaged ";
const previewStr = isPreview ? "Preview " : "";
versionBadge.textContent = `${packageStateStr}${previewStr}v${baseVersion}`;
} }
versionLabel(); versionLabel();

View File

@@ -4,7 +4,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION=$(node -p "require('$ROOT_DIR/package.json').version") VERSION=$(node -p "require('$ROOT_DIR/package.json').version")
TAG="v$VERSION" TAG="$VERSION"
# Parse arguments # Parse arguments
PUBLISH_SNAP=false PUBLISH_SNAP=false

View File

@@ -22,6 +22,31 @@ const logFormat = format.combine(
format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}`) format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}`)
); );
/**
* Format used by the saved file
* @type {Format}
*/
const fileFormat = format.combine(
format.timestamp({format: "YYYY-MM-DD HH:mm:ss"}),
format.errors({stack: true}), // Capture la stack trace complète des erreurs
format.printf(({timestamp, level, message, stack}) => {
return `${timestamp} | ${level.toUpperCase()} | ${stack || message}`;
})
);
/**
* Formated used by the IDE/Console
* @type {Format}
*/
const consoleFormat = format.combine(
format.colorize({all: true}),
format.timestamp({format: "HH:mm:ss"}),
format.errors({stack: true}),
format.printf(({timestamp, level, message, stack}) => {
return `[${timestamp}] ${level}: ${stack || message}`;
})
);
/** /**
* Logger Instance with differents types * Logger Instance with differents types
* - info * - info
@@ -33,7 +58,6 @@ const logFormat = format.combine(
*/ */
const logger = createLogger({ const logger = createLogger({
level: "info", level: "info",
format: logFormat,
transports: [ transports: [
new DailyRotateFile({ new DailyRotateFile({
dirname: logDir, dirname: logDir,
@@ -41,11 +65,11 @@ const logger = createLogger({
datePattern: "YYYY-MM-DD", datePattern: "YYYY-MM-DD",
zippedArchive: false, zippedArchive: false,
maxFiles: "7d", maxFiles: "7d",
format: logFormat, format: fileFormat,
options: { flags: "a" }, options: { flags: "a" },
}), }),
new transports.Console({ new transports.Console({
format: logFormat, format: consoleFormat,
}), }),
], ],
}); });
@@ -57,7 +81,7 @@ function logSessionStart(logDir, downloadPath) {
logger.info(`--- Starting session: ${new Date().toISOString()} ---`); logger.info(`--- Starting session: ${new Date().toISOString()} ---`);
logger.info("============================================================") logger.info("============================================================")
logger.info(`Application Version: ${config.version}`) logger.info(`Application Version: ${config.version}`)
logSystemInfo(logger, logDir, downloadPath) if (typeof logSystemInfo === 'function') logSystemInfo(logger, logDir, downloadPath);
} }
/** /**