refactor: add full documentation and fix theme loading robustness

- Add JSDoc comments across frontend and backend modules
- Improve code readability and maintainability with consistent documentation style
- Document Electron IPC handlers, UI utilities, and theme system
- Add missing function/class documentation in theme loader and app services
- Minor structural improvements and cleanup across Electron main process modules
- Minor fixes on variable names
This commit is contained in:
MasterAcnolo
2026-05-31 20:38:42 +02:00
parent 6afc5e06d6
commit 0218664c5a
34 changed files with 1777 additions and 340 deletions

View File

@@ -5,6 +5,26 @@ const { ffmpegPath, denoPath} = require("./path.helpers.js");
const { configFeatures } = require("../../config.js");
const { logger } = require("../logger.js");
/**
* Validates whether the provided codec is supported by the application.
*
* If the codec is supported, it is returned unchanged.
* Otherwise, a warning is logged and the default codec ("h264")
* is returned as a fallback.
*
* Supported codecs:
* - av01
* - vp9.2
* - vp9
* - h265
* - h264
* - vp8
* - h263
* - theora
*
* @param {string} codec - Codec identifier to validate.
* @returns {string} A valid codec identifier.
*/
function validateCodec(codec){
const validCodec = [
@@ -20,6 +40,28 @@ function validateCodec(codec){
}
}
/**
* Builds the complete yt-dlp argument list based on the
* application configuration and download options.
*
* The generated arguments include:
* - Browser cookie extraction
* - FFmpeg integration
* - Deno runtime configuration
* - Metadata and thumbnail embedding
* - Playlist handling
* - Video/audio format selection
* - Quality filtering
* - Output file destination
*
* @param {Object} options - Download configuration.
* @param {string} options.url - Video or playlist URL.
* @param {boolean} options.audioOnly - Whether to extract audio only.
* @param {string} options.quality - Requested quality preset.
* @param {string} options.outputFolder - Download destination folder.
*
* @returns {string[]} Array of arguments ready to be passed to yt-dlp.
*/
function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
logger.info("--- CONFIGURATION ---");

View File

@@ -4,10 +4,33 @@ const path = require("path");
const notify = require("./notify.helpers")
const { logger } = require("../logger");
/**
* Detects the first supported browser installed on the system.
*
* The detected browser is used by yt-dlp to import authentication
* cookies when required by certain platforms.
*
* Currently, Firefox is the only browser officially supported
* by the application.
*
* If no supported browser is found:
* - A warning is logged
* - A notification is displayed to the user
* - "firefox" is returned as a fallback to allow yt-dlp to
* handle the error gracefully without crashing the application
*
* @returns {string} The detected browser identifier.
*/
function getUserBrowser() {
const userProfile = os.homedir();
// List of browsers supported by yt-dlp (Currently I can only guarantee Firefox, Sorry)
/**
* Browsers supported by yt-dlp cookie extraction.
*
* Only Firefox is currently enabled and tested.
* Additional browsers can be enabled in the future
* once compatibility has been verified.
*/
const browsers = [
{ name: "firefox", path: path.join(userProfile, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles") },
// { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") },
@@ -19,7 +42,7 @@ function getUserBrowser() {
// { name: "whale", path: path.join(userProfile, "AppData", "Local", "Naver", "Naver Whale", "User Data", "Default") }
];
// Search for an available browser
// Search for the first available browser profile.
for (const browser of browsers) {
if (fs.existsSync(browser.path)) {
logger.info(`Browser found: ${browser.name}`);
@@ -27,12 +50,12 @@ function getUserBrowser() {
}
}
// No browser found - notify user
// No supported browser found => Notify User
logger.warn("No supported browser found on the system");
notify.notifyFirefoxBrowserMissing();
// Fallback: return "Firefox" to let YT-DLP manage error
// this avoid app crash
// Fallback to Firefox and let yt-dlp handle the error gracefully.
// This prevents the application from crashing
return "firefox";
}

View File

@@ -1,8 +1,19 @@
const { Notification, shell } = require("electron");
const { iconPaths } = require("./path.helpers");
const { logger } = require("../logger");
/**
* Displays a system notification when a download completes successfully.
*
* If enabled, clicking the notification opens the download folder
* using the system file explorer.
*
* @param {string} folder - Path to the completed download folder.
* @param {boolean} notifyEnabled - Whether notifications are enabled in config.
*/
function notifyDownloadFinished(folder, notifyEnabled = true) {
if (!notifyEnabled) return;
if (!folder) return;
const notif = new Notification({
title: "Freedom Loader",
@@ -14,6 +25,15 @@ function notifyDownloadFinished(folder, notifyEnabled = true) {
notif.show();
}
/**
* Displays a notification when browser cookies cannot be extracted.
*
* This usually indicates:
* - User is not logged in to the browser
* - Browser profile is inaccessible
*
* Clicking the notification opens a tutorial link.
*/
function notifyCookiesBrowserError(){
const notif = new Notification({
title: "Cookies Error",
@@ -21,23 +41,41 @@ function notifyCookiesBrowserError(){
icon: iconPaths.error,
});
notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"));
notif.on("click", () =>
shell.openExternal(
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
)
);
notif.show();
}
function notifyFirefoxBrowserMissing(){
/**
* Displays a notification when Firefox is not detected
* on the user system.
*
* This is required for cookie extraction via yt-dlp.
*
* Clicking the notification opens an installation guide.
*/
function notifyFirefoxBrowserMissing() {
const notif = new Notification({
title: "Firefox Missing",
body: "Firefox was not found on your system. Click here to follow the installation guide",
icon: iconPaths.error,
});
notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"))
notif.on("click", () =>
shell.openExternal(
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
)
);
notif.show();
}
module.exports = {
notifyDownloadFinished,
notifyDownloadFinished,
notifyCookiesBrowserError,
notifyFirefoxBrowserMissing
notifyFirefoxBrowserMissing,
};

View File

@@ -1,27 +1,38 @@
/**
* Normalizes a video payload by attaching the resource type.
*
* @param {Object} data - Raw video metadata.
* @returns {Object} Normalized video object.
*/
function parseVideo(data) {
return {
type: "video",
...data
};
return {
type: "video",
...data
};
}
/**
* Transforms a raw playlist payload into a normalized structure
* containing playlist metadata and a simplified list of videos.
*
* @param {Object} data - Raw playlist metadata.
* @returns {Object} Normalized playlist object.
*/
function parsePlaylist(data) {
return {
type: "playlist",
title: data.title || data.id,
channel: data.uploader,
count: data.entries.length,
videos: (data.entries || []).map(v => ({
id: v.id,
title: v.title,
url: v.webpage_url,
duration: v.duration,
thumbnail: v.thumbnail,
uploader: v.uploader
}))
};
return {
type: "playlist",
title: data.title || data.id,
channel: data.uploader,
count: data.entries.length,
videos: (data.entries || []).map(v => ({
id: v.id,
title: v.title,
url: v.webpage_url,
duration: v.duration,
thumbnail: v.thumbnail,
uploader: v.uploader
}))
};
}
module.exports = { parseVideo, parsePlaylist };
module.exports = { parseVideo, parsePlaylist };

View File

@@ -14,13 +14,23 @@ const resourcesPath = config.devMode
// Default download folder (centralized)
const defaultDownloadFolder = path.join(os.homedir(), "Downloads", "Freedom Loader");
// Binary paths
/**
* Runtime-resolved binary paths.
* These values differ between development and production builds.
*/
let userYtDlp;
let ffmpegPath;
let denoPath;
/** Source binary bundled inside application resources (used for first-time copy) */
const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe");
/**
* Resolve binary locations depending on environment:
* - dev: local resources folder
* - prod: extracted userData + packaged resources
*/
if (config.devMode) {
userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe");
ffmpegPath = path.join(__dirname, "../../ressources/"); // <- has ffmpeg.exe and ffprobe.exe
@@ -35,14 +45,20 @@ if (config.devMode) {
}
}
// Notification icon paths
/**
* Centralized notification and UI icon paths.
* Used by Electron notifications and UI components.
*/
const iconPaths = {
confirm: path.join(resourcesPath, "confirm-icon.png"),
error: path.join(resourcesPath, "error.png"),
app: path.join(resourcesPath, "app-icon.ico")
};
// Binary paths for verification
/**
* Static reference paths to bundled binaries.
* Used mainly for verification and diagnostics.
*/
const binaryPaths = {
ytDlp: path.join(resourcesPath, "binaries", "yt-dlp.exe"),
ffmpeg: path.join(resourcesPath, "binaries", "ffmpeg.exe"),
@@ -50,10 +66,21 @@ const binaryPaths = {
deno: path.join(resourcesPath, "binaries", "deno.exe")
};
// Theme paths - themes stored in AppData to survive updates
/**
* Theme storage system:
* - default themes come from app resources
* - user themes are persisted in userData to survive updates
*/
const userThemesPath = path.join(app.getPath("userData"), "themes");
const defaultThemesSourcePath = path.join(resourcesPath, "theme");
/**
* Initializes user theme directory and ensures default themes exist.
*
* - Creates user themes folder if missing
* - Copies bundled themes from resources if not present
* - Applies permissive permissions for runtime modification
*/
function initUserThemes() {
try {
if (!fs.existsSync(userThemesPath)) {
@@ -94,6 +121,13 @@ function initUserThemes() {
}
}
/**
* Recursively copies a directory and its contents.
*
* @param {string} src - Source directory
* @param {string} dest - Destination directory
* @throws Error If copy operation fails
*/
function copyDirectory(src, dest) {
try {
if (!fs.existsSync(dest)) {
@@ -116,8 +150,9 @@ function copyDirectory(src, dest) {
}
}
if (!userYtDlp){ logger.error("Missing YT-DLP")}
if (!ffmpegPath){ logger.error("Missing FFMPEG")}
if (!denoPath){ logger.error("Missing DENO")}
// Runtime validation of critical binaries
if (!userYtDlp){ logger.error("Missing yt-dlp binary")}
if (!ffmpegPath){ logger.error("Missing ffmpeg binary")}
if (!denoPath){ logger.error("Missing deno binary")}
module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes };

View File

@@ -1,14 +1,24 @@
const RateLimit = require("express-rate-limit");
/* Rate Limite */
const rateLimite = RateLimit({
/**
* Rate limiter middleware used to protect public endpoints
* against abusive or excessive requests.
*
* Restrictions:
* - 5 requests per IP address
* - 15-minute sliding window
*
* When the limit is exceeded, a HTTP 429 (Too Many Requests)
* response is returned.
*/
const rateLimit = RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 100 requests per windowMs on the root path
max: 5,
message: "Too many requests, please try again later.",
statusCode: 429, // HTTP status code for "Too Many Requests"
statusCode: 429,
});
module.exports = {
rateLimite
rateLimit
}

View File

@@ -1,21 +1,43 @@
const path = require("path");
function isValidUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
/**
* Validates whether the provided string is a well-formed URL.
*
* The validation relies on the native URL constructor and
* returns `true` only if the value can be successfully parsed.
*
* @param {string} url - URL to validate.
* @returns {boolean} True if the URL is valid, otherwise false.
*/
function isValidUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}
/**
* Validates whether a folder path is considered safe for
* file operations.
*
* Security checks:
* - Rejects empty or very short paths.
* - Resolves the absolute path before validation.
* - Prevents access to sensitive Windows system directories.
*
* @param {string} folder - Folder path to validate.
* @returns {boolean} True if the path is considered safe, otherwise false.
*/
function isSafePath(folder) {
if (!folder || folder.length < 3) return false;
const unsafe = ["System32", "\\Windows"];
const resolved = path.resolve(folder);
return !unsafe.some(u => resolved.includes(u));
}
module.exports = { isValidUrl, isSafePath };
module.exports = { isValidUrl, isSafePath };