mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
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:
@@ -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 ---");
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user