feat: add UI toast notification for missing Firefox browser

This commit is contained in:
MasterAcnolo
2026-10-27 20:19:39 +01:00
parent 7c5ccb1527
commit dc898e1235
5 changed files with 89 additions and 1 deletions

22
app/toastNotifier.js Normal file
View File

@@ -0,0 +1,22 @@
const {getMainWindow} = require("./windowManager");
/**
* Sends a toast notification to the renderer UI when the main window is ready.
*
* @param {string} message
* @param {"success"|"error"|"warning"|"info"} [type="info"]
* @param {number} [duration=4000]
* @returns {boolean} Whether the toast was sent.
*/
function sendToast(message, type = "info", duration = 4000) {
const mainWindow = getMainWindow();
if (!mainWindow || mainWindow.isDestroyed() || mainWindow.webContents.isDestroyed()) {
return false;
}
mainWindow.webContents.send("show-toast", {message, type, duration});
return true;
}
module.exports = {sendToast};

View File

@@ -159,6 +159,18 @@ contextBridge.exposeInMainWorld("electronAPI", {
ipcRenderer.on("update-downloaded", (_, info) => callback(info)),
openReleasePage: () => ipcRenderer.send("open-release-page"),
/**
* Subscribes to toast notifications emitted by the main process.
*
* @param {Function} callback - Function executed with toast data.
* @returns {Function} Removes the listener.
*/
onToast: (callback) => {
const listener = (_, toast) => callback(toast);
ipcRenderer.on("show-toast", listener);
return () => ipcRenderer.removeListener("show-toast", listener);
},
});
/**

View File

@@ -82,4 +82,13 @@ window.showWarning = (message, duration = 4000) =>
* Shortcut: info toast
*/
window.showInfo = (message, duration = 4000) =>
window.showToast(message, "info", duration);
window.showToast(message, "info", duration);
/**
* Listens for toast notifications from the main process and displays them in the UI.
*/
window.electronAPI?.onToast(({message, type = "info", duration = 4000} = {}) => {
if (typeof message === "string") {
window.showToast(message, type, duration);
}
});

View File

@@ -2,6 +2,7 @@ const fs = require("fs");
const os = require("os");
const notify = require("./notify.helpers");
const { logger } = require("../logger");
const {sendToast} = require("../../app/toastNotifier");
/**
* Detects the first supported browser installed on the system.
@@ -15,6 +16,7 @@ const { logger } = require("../logger");
* If no supported browser is found:
* - A warning is logged
* - A notification is displayed to the user
* - An in-app toast is displayed to the user
* - "firefox" is returned as a fallback to allow yt-dlp to
* handle the error gracefully without crashing the application
*
@@ -51,6 +53,11 @@ function getUserBrowser() {
// No supported browser found => Notify User
logger.warn("No supported browser found on the system");
notify.notifyFirefoxBrowserMissing();
sendToast(
"Firefox was not found on your system. Please install Firefox to import browser cookies.",
"error",
);
} else {
logger.info("Browser found: firefox");
}

View File

@@ -0,0 +1,38 @@
const mockSend = jest.fn();
const mockGetMainWindow = jest.fn();
jest.mock("../../app/windowManager", () => ({
getMainWindow: mockGetMainWindow,
}));
const {sendToast} = require("../../app/toastNotifier");
describe("toastNotifier", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test("sends a toast to the active renderer", () => {
mockGetMainWindow.mockReturnValue({
isDestroyed: () => false,
webContents: {
isDestroyed: () => false,
send: mockSend,
},
});
expect(sendToast("Firefox is missing", "error", 5000)).toBe(true);
expect(mockSend).toHaveBeenCalledWith("show-toast", {
message: "Firefox is missing",
type: "error",
duration: 5000,
});
});
test("does not throw when the renderer is unavailable", () => {
mockGetMainWindow.mockReturnValue(null);
expect(sendToast("Firefox is missing", "error")).toBe(false);
expect(mockSend).not.toHaveBeenCalled();
});
});