From f820e40ad61d8acf2af1d2e54c0f681a14d3cdb7 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Mon, 15 Jun 2026 21:05:35 +0200 Subject: [PATCH 01/19] chore(packet-sender): remove --- pnpm-lock.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eb9ad9ae..d6663a02f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -329,8 +329,6 @@ importers: specifier: ^7.3.1 version: 7.3.1(@types/node@25.2.0)(jiti@2.6.1)(lightningcss@1.30.2) - packet-sender: {} - packages: 7zip-bin@5.2.0: From 6aefa814c71250942eae0906e3eb0c6c3655e25b Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 00:23:29 +0200 Subject: [PATCH 02/19] yay: new deprecated package with critical issuess --- electron-app/package.json | 4 +++- pnpm-lock.yaml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/electron-app/package.json b/electron-app/package.json index 1f1270cec..7805fe7c7 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -112,7 +112,9 @@ "icon": "icons/512x512.png", "category": "Utility", "artifactName": "${productName}-${version}-linux-${arch}.${ext}", - "executableArgs": ["--no-sandbox"] + "executableArgs": [ + "--no-sandbox" + ] } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6663a02f..06b11e8bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2107,6 +2107,7 @@ packages: '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version abbrev@3.0.1: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} @@ -2323,6 +2324,7 @@ packages: basic-ftp@5.1.0: resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} From 0f47e6e82d27eaecd89642cb87e1ec597b4190d6 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 00:43:52 +0200 Subject: [PATCH 03/19] feat: selector mode --- electron-app/main.js | 148 +++++++---------------- electron-app/preload.js | 5 + electron-app/src/app/cleanup.js | 33 +++++ electron-app/src/app/index.js | 11 ++ electron-app/src/app/initialization.js | 31 +++++ electron-app/src/app/lifecycle.js | 49 ++++++++ electron-app/src/app/modeSelector.js | 161 +++++++++++++++++++++++++ electron-app/src/app/updater.js | 49 ++++++++ electron-app/src/ipc/handlers.js | 16 +-- electron-app/src/windows/index.js | 8 ++ electron-app/src/windows/mainWindow.js | 13 +- 11 files changed, 407 insertions(+), 117 deletions(-) create mode 100644 electron-app/src/app/cleanup.js create mode 100644 electron-app/src/app/index.js create mode 100644 electron-app/src/app/initialization.js create mode 100644 electron-app/src/app/lifecycle.js create mode 100644 electron-app/src/app/modeSelector.js create mode 100644 electron-app/src/app/updater.js create mode 100644 electron-app/src/windows/index.js diff --git a/electron-app/main.js b/electron-app/main.js index b5067bd32..638d0e2e1 100644 --- a/electron-app/main.js +++ b/electron-app/main.js @@ -1,121 +1,55 @@ /** * @module main * @description Main entry point for the Electron application. - * Handles application lifecycle, initialization, and cleanup of processes and windows. + * + * Orchestrates application lifecycle and initialization through modular components: + * - initialization: Config, IPC, and process cleanup + * - modeSelector: Mode selection UI and main window creation + * - updater: Auto-update functionality + * - lifecycle: App lifecycle event handling */ -import { app, BrowserWindow, dialog, screen } from "electron"; -import pkg from "electron-updater"; -import { getConfigManager } from "./src/config/configInstance.js"; -import { setupIpcHandlers } from "./src/ipc/handlers.js"; -import { startBackend, stopBackend } from "./src/processes/backend.js"; -import { startBlcuProgramming, stopBlcuProgramming } from "./src/processes/blcuProgramming.js"; +import { app, screen } from "electron"; +import { + handleSelectorFallback, + initializeApp, + setupLifecycleHandlers, + setupUpdater, + showModeSelector, +} from "./src/app/index.js"; import { logger } from "./src/utils/logger.js"; -import { createLogWindow } from "./src/windows/logWindow.js"; -import { createWindow } from "./src/windows/mainWindow.js"; -const { autoUpdater } = pkg; - -// Setup IPC handlers for renderer process communication -setupIpcHandlers(); - -// App lifecycle: wait for Electron to be ready +/** + * Initializes the application when Electron is ready. + * Orchestrates startup sequence: + * 1. Initialize config and IPC + * 2. Show mode selector + * 3. Setup auto-updater + * 4. Setup lifecycle handlers + */ app.whenReady().then(async () => { - // Get the screen width and height - // Only can be used inside app.whenReady() - const { width: screenWidth, height: screenHeight } = - screen.getPrimaryDisplay().workAreaSize; - - // Initialize ConfigManager and ensure config exists BEFORE starting backend - logger.electron.header("Initializing configuration..."); - // Get ConfigManager instance (creates config from template if needed) - await getConfigManager(); - logger.electron.header("Configuration ready"); - - const logWindow = createLogWindow(screenWidth, screenHeight); - - // Start backend process try { - await startBackend(logWindow); - logger.electron.header("Backend process spawned"); - } catch (error) { - // Start backend already shows these errors - } - - try { - await startBlcuProgramming(logWindow); - logger.electron.header("BLCU programming process spawned"); - } catch (error) { - logger.electron.error("Failed to start BLCU programming:", error); - } - - // Create main application window - const mainWindow = createWindow(screenWidth, screenHeight); - mainWindow.maximize(); - - logger.electron.header("Main application window created"); - - // Updater setup - if (!app.isPackaged) { - autoUpdater.forceDevUpdateConfig = true; - } - - autoUpdater.logger = { - info: (message) => logger.electron.info(message), - error: (message) => logger.electron.error(message), - warn: (message) => logger.electron.warning(message), - debug: (message) => logger.electron.debug(message), - }; - - // Check for updates - autoUpdater.checkForUpdates(); - - // Handle update downloaded event - autoUpdater.on("update-downloaded", (info) => { - dialog - .showMessageBox({ - type: "info", - title: "Update Ready", - message: `Version ${info.version} has been downloaded. Restart now to install?`, - buttons: ["Restart", "Later"], - }) - .then((result) => { - if (result.response === 0) { - autoUpdater.quitAndInstall(); - } - }); - }); - - // Handle macOS app activation (reopen window when dock icon clicked) - app.on("activate", () => { - // Only create window if no windows exist - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); + // Initialize configuration, IPC, and cleanup + await initializeApp(); + + // Get screen dimensions for window creation + const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; + + // Show mode selector and get user choice + try { + await showModeSelector(screenWidth, screenHeight); + } catch (error) { + logger.electron.error("Mode selector failed:", error); + await handleSelectorFallback(screenWidth, screenHeight); } - }); -}); -// Handle window close behavior -app.on("window-all-closed", () => { - // On macOS, keep app running even when all windows are closed - if (process.platform !== "darwin") { - // Quit app on other platforms when all windows are closed + // Setup auto-updater + setupUpdater(); + + // Setup application lifecycle handlers (window-all-closed, before-quit, activate, exceptions) + setupLifecycleHandlers(); + } catch (error) { + logger.electron.error("Failed to initialize application:", error); app.quit(); } }); - -// Cleanup before app quits -app.on("before-quit", (e) => { - e.preventDefault(); - Promise.all([stopBackend(), stopBlcuProgramming()]) - .catch((error) => logger.electron.error("Error during shutdown:", error)) - .finally(() => app.exit()); -}); - -// Handle uncaught exceptions globally -process.on("uncaughtException", (error) => { - // Log error to console - logger.electron.error("Uncaught exception:", error); - // Show error dialog to user - dialog.showErrorBox("Error", error.message); -}); diff --git a/electron-app/preload.js b/electron-app/preload.js index 5fda40215..05947b2de 100644 --- a/electron-app/preload.js +++ b/electron-app/preload.js @@ -38,6 +38,11 @@ contextBridge.exposeInMainWorld("electronAPI", { selectFolder: () => ipcRenderer.invoke("select-folder"), // Open a folder path in the OS file explorer openFolder: (path) => ipcRenderer.invoke("open-folder", path), + // Set initial mode (used by mode selector renderer) + setInitialMode: (mode) => { + ipcRenderer.send("mode-selected", mode); + return Promise.resolve(); + }, // Receive log message from backend onLog: (callback) => { const listener = (_event, value) => callback(value); diff --git a/electron-app/src/app/cleanup.js b/electron-app/src/app/cleanup.js new file mode 100644 index 000000000..3cc8c8ffc --- /dev/null +++ b/electron-app/src/app/cleanup.js @@ -0,0 +1,33 @@ +/** + * @module app/cleanup + * @description Cleanup utilities for terminating leftover processes from previous sessions. + */ + +import { execSync } from "child_process"; +import { logger } from "../utils/logger.js"; + +/** + * Terminates any leftover backend processes from previous sessions. + * Prevents backend/log windows from appearing before user selects a mode. + * @returns {Promise} + */ +async function cleanupLeftoverBackendProcesses() { + try { + const out = execSync("pgrep -f backend-linux-amd64 || true").toString().trim(); + if (out) { + const pids = out.split(/\s+/).filter(Boolean); + for (const pid of pids) { + try { + process.kill(Number(pid), "SIGTERM"); + logger.electron.info(`Terminated leftover backend pid=${pid}`); + } catch (e) { + logger.electron.debug(`Failed to terminate pid ${pid}:`, e); + } + } + } + } catch (e) { + logger.electron.debug("Error checking/killing leftover backends:", e); + } +} + +export { cleanupLeftoverBackendProcesses }; diff --git a/electron-app/src/app/index.js b/electron-app/src/app/index.js new file mode 100644 index 000000000..cd212fa15 --- /dev/null +++ b/electron-app/src/app/index.js @@ -0,0 +1,11 @@ +/** + * @module app + * @description Application lifecycle and initialization exports. + */ + +export { cleanupLeftoverBackendProcesses } from "./cleanup.js"; +export { initializeApp } from "./initialization.js"; +export { setupLifecycleHandlers } from "./lifecycle.js"; +export { handleSelectorFallback, showModeSelector } from "./modeSelector.js"; +export { setupUpdater } from "./updater.js"; + diff --git a/electron-app/src/app/initialization.js b/electron-app/src/app/initialization.js new file mode 100644 index 000000000..3e6195ca0 --- /dev/null +++ b/electron-app/src/app/initialization.js @@ -0,0 +1,31 @@ +/** + * @module app/initialization + * @description Application initialization: config setup and cleanup. + */ + +import { getConfigManager } from "../config/configInstance.js"; +import { setupIpcHandlers } from "../ipc/handlers.js"; +import { logger } from "../utils/logger.js"; +import { cleanupLeftoverBackendProcesses } from "./cleanup.js"; + +/** + * Initializes the application: + * - Sets up IPC handlers + * - Initializes configuration + * - Cleans up leftover processes + * @returns {Promise} + */ +async function initializeApp() { + // Setup IPC handlers for renderer process communication + setupIpcHandlers(); + + // Initialize ConfigManager and ensure config exists + logger.electron.header("Initializing configuration..."); + await getConfigManager(); + logger.electron.header("Configuration ready"); + + // Clean up leftover processes from previous sessions + await cleanupLeftoverBackendProcesses(); +} + +export { initializeApp }; diff --git a/electron-app/src/app/lifecycle.js b/electron-app/src/app/lifecycle.js new file mode 100644 index 000000000..b0ccdd84c --- /dev/null +++ b/electron-app/src/app/lifecycle.js @@ -0,0 +1,49 @@ +/** + * @module app/lifecycle + * @description Application lifecycle event handlers. + */ + +import { app, BrowserWindow, dialog } from "electron"; +import { stopBackend } from "../processes/backend.js"; +import { stopBlcuProgramming } from "../processes/blcuProgramming.js"; +import { logger } from "../utils/logger.js"; +import { createWindow } from "../windows/index.js"; + +/** + * Sets up all application lifecycle event handlers. + * @returns {void} + */ +function setupLifecycleHandlers() { + // Handle window close behavior + app.on("window-all-closed", () => { + // On macOS, keep app running even when all windows are closed + if (process.platform !== "darwin") { + // Quit app on other platforms when all windows are closed + app.quit(); + } + }); + + // Cleanup before app quits + app.on("before-quit", (e) => { + e.preventDefault(); + Promise.all([stopBackend(), stopBlcuProgramming()]) + .catch((error) => logger.electron.error("Error during shutdown:", error)) + .finally(() => app.exit()); + }); + + // Handle macOS app activation (reopen window when dock icon clicked) + app.on("activate", () => { + // Only create window if no windows exist + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); + + // Handle uncaught exceptions globally + process.on("uncaughtException", (error) => { + logger.electron.error("Uncaught exception:", error); + dialog.showErrorBox("Error", error.message); + }); +} + +export { setupLifecycleHandlers }; diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js new file mode 100644 index 000000000..a3780d6fc --- /dev/null +++ b/electron-app/src/app/modeSelector.js @@ -0,0 +1,161 @@ +/** + * @module app/modeSelector + * @description Mode selector window and logic for initial app mode selection. + */ + +import { BrowserWindow, ipcMain } from "electron"; +import fs from "fs"; +import path from "path"; +import { startBackend } from "../processes/backend.js"; +import { startBlcuProgramming } from "../processes/blcuProgramming.js"; +import { logger } from "../utils/logger.js"; +import { getAppPath } from "../utils/paths.js"; +import { createLogWindow, createWindow } from "../windows/index.js"; + +const VALID_MODES = { + testing: "testing-view", + flashing: "flashing-view", + default: "testing-view", +}; + +/** + * Creates and displays the mode selector window. + * Returns a Promise that resolves when user selects a mode. + * @param {number} screenWidth + * @param {number} screenHeight + * @returns {Promise<{mode: string, view: string, mainWindow: BrowserWindow}>} + */ +async function showModeSelector(screenWidth, screenHeight) { + return new Promise(async (resolve, reject) => { + let mainWindow = null; + + const selectorWindow = new BrowserWindow({ + width: 480, + height: 320, + resizable: false, + modal: true, + parent: mainWindow, + show: true, + webPreferences: { + preload: path.join(getAppPath(), "preload.js"), + contextIsolation: true, + nodeIntegration: false, + }, + title: "Select Mode", + }); + + const selectorPath = path.join(getAppPath(), "renderer", "mode-selector", "index.html"); + + if (!fs.existsSync(selectorPath)) { + logger.electron.warning("Mode selector UI not found, using default testing-view"); + resolve({ mode: "default", view: VALID_MODES.default, mainWindow: null }); + return; + } + + logger.electron.info(`Mode selector found: ${selectorPath}`); + + try { + await selectorWindow.loadFile(selectorPath); + selectorWindow.show(); + selectorWindow.focus(); + } catch (err) { + logger.electron.error("Failed to load selector UI:", err); + resolve({ mode: "default", view: VALID_MODES.default, mainWindow: null }); + return; + } + + // Listen for mode selection from renderer + ipcMain.once("mode-selected", async (_event, mode) => { + try { + const view = VALID_MODES[mode] || VALID_MODES.default; + + // Create the main window with selected view + mainWindow = createWindow(screenWidth, screenHeight, view); + try { + mainWindow.maximize(); + } catch (e) {} + logger.electron.header("Main application window created"); + + // Start backend and logging only for testing view + if (view === "testing-view" || view === "flashing-view") { + await startServices(screenWidth, screenHeight, view); + } + + // Show and focus main window + try { + mainWindow.show(); + mainWindow.focus(); + } catch (e) {} + + resolve({ mode, view, mainWindow }); + } catch (error) { + logger.electron.error("Error handling mode selection:", error); + reject(error); + } finally { + try { + selectorWindow.close(); + } catch (e) {} + } + }); + }); +} + +/** + * Starts services based on the selected view. + * - testing-view: Backend + BLCU Programming + * - flashing-view: BLCU Programming only + * @param {number} screenWidth + * @param {number} screenHeight + * @param {string} view - The selected view mode + * @returns {Promise} + */ +async function startServices(screenWidth, screenHeight, view) { + const logWindow = createLogWindow(screenWidth, screenHeight); + + // Start backend only for testing view + if (view === "testing-view") { + try { + await startBackend(logWindow); + logger.electron.header("Backend process spawned"); + } catch (err) { + logger.electron.error("Failed to start backend:", err); + } + } + + // Start BLCU Programming for both testing and flashing views + try { + await startBlcuProgramming(logWindow); + logger.electron.header("BLCU programming process spawned"); + } catch (err) { + logger.electron.error("Failed to start BLCU programming:", err); + } +} + +/** + * Handles fallback when selector is not available or fails. + * @param {number} screenWidth + * @param {number} screenHeight + * @returns {Promise<{mode: string, view: string, mainWindow: BrowserWindow}>} + */ +async function handleSelectorFallback(screenWidth, screenHeight) { + const view = VALID_MODES.default; + const mainWindow = createWindow(screenWidth, screenHeight, view); + + try { + mainWindow.maximize(); + } catch (e) {} + + logger.electron.header("Main application window created"); + + try { + mainWindow.show(); + } catch (e) {} + + // Start services by default (testing view) + await startServices(screenWidth, screenHeight, view); + + return { mode: "default", view, mainWindow }; +} + +export { handleSelectorFallback, showModeSelector }; + diff --git a/electron-app/src/app/updater.js b/electron-app/src/app/updater.js new file mode 100644 index 000000000..c5a9c5dba --- /dev/null +++ b/electron-app/src/app/updater.js @@ -0,0 +1,49 @@ +/** + * @module app/updater + * @description Auto-updater configuration and event handling. + */ + +import { app, dialog } from "electron"; +import pkg from "electron-updater"; +import { logger } from "../utils/logger.js"; + +const { autoUpdater } = pkg; + +/** + * Initializes the auto-updater with appropriate logging and event handlers. + * @returns {void} + */ +function setupUpdater() { + if (!app.isPackaged) { + autoUpdater.forceDevUpdateConfig = true; + } + + // Configure auto-updater logging + autoUpdater.logger = { + info: (message) => logger.electron.info(message), + error: (message) => logger.electron.error(message), + warn: (message) => logger.electron.warning(message), + debug: (message) => logger.electron.debug(message), + }; + + // Handle update downloaded event + autoUpdater.on("update-downloaded", (info) => { + dialog + .showMessageBox({ + type: "info", + title: "Update Ready", + message: `Version ${info.version} has been downloaded. Restart now to install?`, + buttons: ["Restart", "Later"], + }) + .then((result) => { + if (result.response === 0) { + autoUpdater.quitAndInstall(); + } + }); + }); + + // Check for updates + autoUpdater.checkForUpdates(); +} + +export { setupUpdater }; diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index 269ea5235..e9b47f5e6 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -11,17 +11,17 @@ import { dialog, ipcMain, shell } from "electron"; import fs from "fs"; import { isAbsolute, join } from "path"; import { - importConfig, - readConfig, - writeConfig, + importConfig, + readConfig, + writeConfig, } from "../config/configInstance.js"; import { getBackendWorkingDir, restartBackend } from "../processes/backend.js"; import { logger } from "../utils/logger.js"; import { - getCurrentView, - getMainWindow, - loadView, - reloadWindow, + getCurrentView, + getMainWindow, + loadView, + reloadWindow, } from "../windows/mainWindow.js"; /** @@ -52,6 +52,8 @@ function setupIpcHandlers() { return view; }); + // Mode selection is handled in main process via 'mode-selected' event. + /** * @event save-config * @async diff --git a/electron-app/src/windows/index.js b/electron-app/src/windows/index.js new file mode 100644 index 000000000..06cd375b2 --- /dev/null +++ b/electron-app/src/windows/index.js @@ -0,0 +1,8 @@ +/** + * @module windows + * @description Window creation and management exports. + */ + +export { createLogWindow } from "./logWindow.js"; +export { createWindow } from "./mainWindow.js"; + diff --git a/electron-app/src/windows/mainWindow.js b/electron-app/src/windows/mainWindow.js index 0bf125b85..3fc755f64 100644 --- a/electron-app/src/windows/mainWindow.js +++ b/electron-app/src/windows/mainWindow.js @@ -24,7 +24,7 @@ let currentView = "testing-view"; * @example * createWindow(); */ -function createWindow(screenWidth, screenHeight) { +function createWindow(screenWidth, screenHeight, initialView) { // Create new browser window with configuration mainWindow = new BrowserWindow({ x: 0, @@ -47,8 +47,15 @@ function createWindow(screenWidth, screenHeight) { backgroundColor: "#1a1a1a", }); - // Load ethernet view by default - loadView(currentView); + // If an initial view string is provided, load it. + // If `initialView` is explicitly null, skip loading so caller can decide later. + if (typeof initialView === "string") { + loadView(initialView); + } else if (initialView === null) { + // skip loading any view for now + } else { + loadView(currentView); + } // Create application menu const menu = createMenu(mainWindow); From 02c6a4bf74b6c710302b223d237c4a9270b86b0d Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 00:47:21 +0200 Subject: [PATCH 04/19] feat: not show log window at blcu --- electron-app/src/app/modeSelector.js | 9 +++++++-- electron-app/src/menu/menu.js | 25 +++++-------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index a3780d6fc..5d3843e68 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -110,7 +110,12 @@ async function showModeSelector(screenWidth, screenHeight) { * @returns {Promise} */ async function startServices(screenWidth, screenHeight, view) { - const logWindow = createLogWindow(screenWidth, screenHeight); + let logWindow = null; + + // Create the backend log window only for testing view + if (view === "testing-view") { + logWindow = createLogWindow(screenWidth, screenHeight); + } // Start backend only for testing view if (view === "testing-view") { @@ -124,7 +129,7 @@ async function startServices(screenWidth, screenHeight, view) { // Start BLCU Programming for both testing and flashing views try { - await startBlcuProgramming(logWindow); + await startBlcuProgramming(); logger.electron.header("BLCU programming process spawned"); } catch (err) { logger.electron.error("Failed to start BLCU programming:", err); diff --git a/electron-app/src/menu/menu.js b/electron-app/src/menu/menu.js index c64b64ff4..82499b48a 100644 --- a/electron-app/src/menu/menu.js +++ b/electron-app/src/menu/menu.js @@ -1,15 +1,15 @@ /** * @module menu * @description Application menu creation and management for the Electron application. - * Defines menu structure with File, View, Tools, and Help sections with keyboard shortcuts and actions. + * Defines menu structure with File, Tools, and Help sections with keyboard shortcuts and actions. */ import { Menu, app, dialog } from "electron"; -import { loadView } from "../windows/mainWindow.js"; /** - * Creates and sets the application menu with File, View, Tools, and Help sections. - * Includes menu items for reloading, exiting, switching views, toggling DevTools, and managing packet sender. + * Creates and sets the application menu with File, Tools, and Help sections. + * Includes menu items for reloading, exiting, toggling DevTools, and app information. + * View switching is no longer available since the mode is selected at startup. * @param {import("electron").BrowserWindow} mainWindow - The main browser window instance to attach menu actions to. * @returns {void} * @example @@ -38,23 +38,8 @@ function createMenu(mainWindow) { ], }, { - label: "View", + label: "Tools", submenu: [ - { - label: "Competition View", - accelerator: "CmdOrCtrl+1", - click: () => { - loadView("competition-view"); - }, - }, - { - label: "Testing View", - accelerator: "CmdOrCtrl+2", - click: () => { - loadView("testing-view"); - }, - }, - { type: "separator" }, { label: "Toggle DevTools", accelerator: "F12", From a39284ff5467f864a13d15631b8bbf41a78ea776 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 01:18:16 +0200 Subject: [PATCH 05/19] feat: enhance selection window --- electron-app/.gitignore | 5 + electron-app/package.json | 2 +- electron-app/preload.js | 2 + .../renderer/mode-selector/index.html | 245 ++++++++++++++++++ .../renderer/mode-selector/src/Subsystem.png | Bin 0 -> 20750 bytes .../renderer/mode-selector/src/logo.svg | 28 ++ .../renderer/mode-selector/src/sign.png | Bin 0 -> 16217 bytes electron-app/src/app/modeSelector.js | 7 +- electron-app/src/ipc/handlers.js | 18 +- 9 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 electron-app/renderer/mode-selector/index.html create mode 100644 electron-app/renderer/mode-selector/src/Subsystem.png create mode 100644 electron-app/renderer/mode-selector/src/logo.svg create mode 100644 electron-app/renderer/mode-selector/src/sign.png diff --git a/electron-app/.gitignore b/electron-app/.gitignore index ce4204b7b..2f4d5e1f1 100644 --- a/electron-app/.gitignore +++ b/electron-app/.gitignore @@ -7,6 +7,11 @@ dist-ssr build binaries renderer +!renderer/ +renderer/testing-view/ +renderer/flashing-view/ +!renderer/mode-selector/ +!renderer/mode-selector/** out *.local diff --git a/electron-app/package.json b/electron-app/package.json index 7805fe7c7..abe159444 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -1,6 +1,6 @@ { "name": "hyperloop-control-station", - "version": "1.0.0", + "version": "11.1.0", "description": "Hyperloop UPV Control Station", "main": "main.js", "type": "module", diff --git a/electron-app/preload.js b/electron-app/preload.js index 05947b2de..e9fcb690d 100644 --- a/electron-app/preload.js +++ b/electron-app/preload.js @@ -38,6 +38,8 @@ contextBridge.exposeInMainWorld("electronAPI", { selectFolder: () => ipcRenderer.invoke("select-folder"), // Open a folder path in the OS file explorer openFolder: (path) => ipcRenderer.invoke("open-folder", path), + // Get the application version from the main process + getAppVersion: () => ipcRenderer.invoke("get-app-version"), // Set initial mode (used by mode selector renderer) setInitialMode: (mode) => { ipcRenderer.send("mode-selected", mode); diff --git a/electron-app/renderer/mode-selector/index.html b/electron-app/renderer/mode-selector/index.html new file mode 100644 index 000000000..e554d12fc --- /dev/null +++ b/electron-app/renderer/mode-selector/index.html @@ -0,0 +1,245 @@ + + + + + + + Seleccione Modo + + + + +
+ Departamento + +
+

Control Station

+
+
+ + +
+
+ Logo auxiliar + +
+
+ + + + \ No newline at end of file diff --git a/electron-app/renderer/mode-selector/src/Subsystem.png b/electron-app/renderer/mode-selector/src/Subsystem.png new file mode 100644 index 0000000000000000000000000000000000000000..aed1aee9117e9d60f2a1709a6352190198b61859 GIT binary patch literal 20750 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 RJb~$#!PC{xWt~$(69AzE1~C8t literal 0 HcmV?d00001 diff --git a/electron-app/renderer/mode-selector/src/logo.svg b/electron-app/renderer/mode-selector/src/logo.svg new file mode 100644 index 000000000..20d20319d --- /dev/null +++ b/electron-app/renderer/mode-selector/src/logo.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/electron-app/renderer/mode-selector/src/sign.png b/electron-app/renderer/mode-selector/src/sign.png new file mode 100644 index 0000000000000000000000000000000000000000..5771951ddb746d8e3a57e5118436172ba488a07b GIT binary patch literal 16217 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 literal 0 HcmV?d00001 diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index 5d3843e68..8e4fee3a3 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -30,9 +30,10 @@ async function showModeSelector(screenWidth, screenHeight) { let mainWindow = null; const selectorWindow = new BrowserWindow({ - width: 480, - height: 320, - resizable: false, + width: 920, + height: 680, + useContentSize: true, + resizable: true, modal: true, parent: mainWindow, show: true, diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index e9b47f5e6..e36604f54 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -7,21 +7,21 @@ * - Folder selection dialogs */ -import { dialog, ipcMain, shell } from "electron"; +import { app, dialog, ipcMain, shell } from "electron"; import fs from "fs"; import { isAbsolute, join } from "path"; import { - importConfig, - readConfig, - writeConfig, + importConfig, + readConfig, + writeConfig, } from "../config/configInstance.js"; import { getBackendWorkingDir, restartBackend } from "../processes/backend.js"; import { logger } from "../utils/logger.js"; import { - getCurrentView, - getMainWindow, - loadView, - reloadWindow, + getCurrentView, + getMainWindow, + loadView, + reloadWindow, } from "../windows/mainWindow.js"; /** @@ -40,6 +40,8 @@ function setupIpcHandlers() { */ ipcMain.handle("get-current-view", () => getCurrentView()); + ipcMain.handle("get-app-version", () => app.getVersion()); + /** * @event switch-view * @description Switches the main window to the specified view. From a6043f863d5225ba0c1d99a3153d44c316ce9613 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 01:25:07 +0200 Subject: [PATCH 06/19] fix: wait before opening testing view --- electron-app/src/app/modeSelector.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index 8e4fee3a3..9be4da7af 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -11,6 +11,7 @@ import { startBlcuProgramming } from "../processes/blcuProgramming.js"; import { logger } from "../utils/logger.js"; import { getAppPath } from "../utils/paths.js"; import { createLogWindow, createWindow } from "../windows/index.js"; +import { loadView } from "../windows/mainWindow.js"; const VALID_MODES = { testing: "testing-view", @@ -70,18 +71,21 @@ async function showModeSelector(screenWidth, screenHeight) { try { const view = VALID_MODES[mode] || VALID_MODES.default; - // Create the main window with selected view - mainWindow = createWindow(screenWidth, screenHeight, view); + // Create the main window without loading the view yet. + mainWindow = createWindow(screenWidth, screenHeight, null); try { mainWindow.maximize(); } catch (e) {} logger.electron.header("Main application window created"); - // Start backend and logging only for testing view + // Start services and only then load the selected view. if (view === "testing-view" || view === "flashing-view") { await startServices(screenWidth, screenHeight, view); + await new Promise((resolve) => setTimeout(resolve, 1000)); } + loadView(view); + // Show and focus main window try { mainWindow.show(); From c7d61155acf5b66763e81eb5753e6da3117640f0 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 01:31:57 +0200 Subject: [PATCH 07/19] feat: return to selector button --- electron-app/main.js | 26 +++++++++++++++++++++++++- electron-app/src/menu/menu.js | 5 +++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/electron-app/main.js b/electron-app/main.js index 638d0e2e1..d154b9106 100644 --- a/electron-app/main.js +++ b/electron-app/main.js @@ -9,7 +9,7 @@ * - lifecycle: App lifecycle event handling */ -import { app, screen } from "electron"; +import { app, BrowserWindow, screen } from "electron"; import { handleSelectorFallback, initializeApp, @@ -17,6 +17,8 @@ import { setupUpdater, showModeSelector, } from "./src/app/index.js"; +import { stopBackend } from "./src/processes/backend.js"; +import { stopBlcuProgramming } from "./src/processes/blcuProgramming.js"; import { logger } from "./src/utils/logger.js"; /** @@ -35,6 +37,28 @@ app.whenReady().then(async () => { // Get screen dimensions for window creation const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; + // Register return-to-selector handler before starting the app. + app.on("return-to-selector", async () => { + try { + logger.electron.info("Returning to selector mode..."); + await Promise.all([stopBackend(), stopBlcuProgramming()]); + + const existingWindows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed()); + existingWindows.forEach((window) => window.hide()); + + const { width: selectorWidth, height: selectorHeight } = screen.getPrimaryDisplay().workAreaSize; + await showModeSelector(selectorWidth, selectorHeight); + + existingWindows.forEach((window) => { + if (!window.isDestroyed()) { + window.close(); + } + }); + } catch (error) { + logger.electron.error("Failed to return to selector:", error); + } + }); + // Show mode selector and get user choice try { await showModeSelector(screenWidth, screenHeight); diff --git a/electron-app/src/menu/menu.js b/electron-app/src/menu/menu.js index 82499b48a..a7cdc27e7 100644 --- a/electron-app/src/menu/menu.js +++ b/electron-app/src/menu/menu.js @@ -29,6 +29,11 @@ function createMenu(mainWindow) { } }, }, + { + label: "Return to Selector", + accelerator: "CmdOrCtrl+Shift+S", + click: () => app.emit("return-to-selector"), + }, { type: "separator" }, { label: "Exit", From 28b1eb55240432308fce0c516a4e606f2e9a535e Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 15:32:34 +0200 Subject: [PATCH 08/19] docs(electron): remove orphan comment --- electron-app/src/ipc/handlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index e36604f54..5a4198d23 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -54,7 +54,7 @@ function setupIpcHandlers() { return view; }); - // Mode selection is handled in main process via 'mode-selected' event. + /** * @event save-config From 4b90356158015dabd17a9a875d5fe6c965538991 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Mon, 15 Jun 2026 21:05:35 +0200 Subject: [PATCH 09/19] chore(packet-sender): remove --- pnpm-lock.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eb9ad9ae..d6663a02f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -329,8 +329,6 @@ importers: specifier: ^7.3.1 version: 7.3.1(@types/node@25.2.0)(jiti@2.6.1)(lightningcss@1.30.2) - packet-sender: {} - packages: 7zip-bin@5.2.0: From 3b453cced72f64918c4917d6f0a07c262978ec17 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 00:23:29 +0200 Subject: [PATCH 10/19] yay: new deprecated package with critical issuess --- pnpm-lock.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6663a02f..06b11e8bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2107,6 +2107,7 @@ packages: '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version abbrev@3.0.1: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} @@ -2323,6 +2324,7 @@ packages: basic-ftp@5.1.0: resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} From 143a00bf4cf4a966b4115163050b0ba501617b73 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 00:43:52 +0200 Subject: [PATCH 11/19] feat: selector mode --- electron-app/main.js | 148 +++++++---------------- electron-app/preload.js | 5 + electron-app/src/app/cleanup.js | 33 +++++ electron-app/src/app/index.js | 11 ++ electron-app/src/app/initialization.js | 31 +++++ electron-app/src/app/lifecycle.js | 49 ++++++++ electron-app/src/app/modeSelector.js | 161 +++++++++++++++++++++++++ electron-app/src/app/updater.js | 49 ++++++++ electron-app/src/ipc/handlers.js | 16 +-- electron-app/src/windows/index.js | 8 ++ electron-app/src/windows/mainWindow.js | 13 +- 11 files changed, 407 insertions(+), 117 deletions(-) create mode 100644 electron-app/src/app/cleanup.js create mode 100644 electron-app/src/app/index.js create mode 100644 electron-app/src/app/initialization.js create mode 100644 electron-app/src/app/lifecycle.js create mode 100644 electron-app/src/app/modeSelector.js create mode 100644 electron-app/src/app/updater.js create mode 100644 electron-app/src/windows/index.js diff --git a/electron-app/main.js b/electron-app/main.js index b5067bd32..638d0e2e1 100644 --- a/electron-app/main.js +++ b/electron-app/main.js @@ -1,121 +1,55 @@ /** * @module main * @description Main entry point for the Electron application. - * Handles application lifecycle, initialization, and cleanup of processes and windows. + * + * Orchestrates application lifecycle and initialization through modular components: + * - initialization: Config, IPC, and process cleanup + * - modeSelector: Mode selection UI and main window creation + * - updater: Auto-update functionality + * - lifecycle: App lifecycle event handling */ -import { app, BrowserWindow, dialog, screen } from "electron"; -import pkg from "electron-updater"; -import { getConfigManager } from "./src/config/configInstance.js"; -import { setupIpcHandlers } from "./src/ipc/handlers.js"; -import { startBackend, stopBackend } from "./src/processes/backend.js"; -import { startBlcuProgramming, stopBlcuProgramming } from "./src/processes/blcuProgramming.js"; +import { app, screen } from "electron"; +import { + handleSelectorFallback, + initializeApp, + setupLifecycleHandlers, + setupUpdater, + showModeSelector, +} from "./src/app/index.js"; import { logger } from "./src/utils/logger.js"; -import { createLogWindow } from "./src/windows/logWindow.js"; -import { createWindow } from "./src/windows/mainWindow.js"; -const { autoUpdater } = pkg; - -// Setup IPC handlers for renderer process communication -setupIpcHandlers(); - -// App lifecycle: wait for Electron to be ready +/** + * Initializes the application when Electron is ready. + * Orchestrates startup sequence: + * 1. Initialize config and IPC + * 2. Show mode selector + * 3. Setup auto-updater + * 4. Setup lifecycle handlers + */ app.whenReady().then(async () => { - // Get the screen width and height - // Only can be used inside app.whenReady() - const { width: screenWidth, height: screenHeight } = - screen.getPrimaryDisplay().workAreaSize; - - // Initialize ConfigManager and ensure config exists BEFORE starting backend - logger.electron.header("Initializing configuration..."); - // Get ConfigManager instance (creates config from template if needed) - await getConfigManager(); - logger.electron.header("Configuration ready"); - - const logWindow = createLogWindow(screenWidth, screenHeight); - - // Start backend process try { - await startBackend(logWindow); - logger.electron.header("Backend process spawned"); - } catch (error) { - // Start backend already shows these errors - } - - try { - await startBlcuProgramming(logWindow); - logger.electron.header("BLCU programming process spawned"); - } catch (error) { - logger.electron.error("Failed to start BLCU programming:", error); - } - - // Create main application window - const mainWindow = createWindow(screenWidth, screenHeight); - mainWindow.maximize(); - - logger.electron.header("Main application window created"); - - // Updater setup - if (!app.isPackaged) { - autoUpdater.forceDevUpdateConfig = true; - } - - autoUpdater.logger = { - info: (message) => logger.electron.info(message), - error: (message) => logger.electron.error(message), - warn: (message) => logger.electron.warning(message), - debug: (message) => logger.electron.debug(message), - }; - - // Check for updates - autoUpdater.checkForUpdates(); - - // Handle update downloaded event - autoUpdater.on("update-downloaded", (info) => { - dialog - .showMessageBox({ - type: "info", - title: "Update Ready", - message: `Version ${info.version} has been downloaded. Restart now to install?`, - buttons: ["Restart", "Later"], - }) - .then((result) => { - if (result.response === 0) { - autoUpdater.quitAndInstall(); - } - }); - }); - - // Handle macOS app activation (reopen window when dock icon clicked) - app.on("activate", () => { - // Only create window if no windows exist - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); + // Initialize configuration, IPC, and cleanup + await initializeApp(); + + // Get screen dimensions for window creation + const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; + + // Show mode selector and get user choice + try { + await showModeSelector(screenWidth, screenHeight); + } catch (error) { + logger.electron.error("Mode selector failed:", error); + await handleSelectorFallback(screenWidth, screenHeight); } - }); -}); -// Handle window close behavior -app.on("window-all-closed", () => { - // On macOS, keep app running even when all windows are closed - if (process.platform !== "darwin") { - // Quit app on other platforms when all windows are closed + // Setup auto-updater + setupUpdater(); + + // Setup application lifecycle handlers (window-all-closed, before-quit, activate, exceptions) + setupLifecycleHandlers(); + } catch (error) { + logger.electron.error("Failed to initialize application:", error); app.quit(); } }); - -// Cleanup before app quits -app.on("before-quit", (e) => { - e.preventDefault(); - Promise.all([stopBackend(), stopBlcuProgramming()]) - .catch((error) => logger.electron.error("Error during shutdown:", error)) - .finally(() => app.exit()); -}); - -// Handle uncaught exceptions globally -process.on("uncaughtException", (error) => { - // Log error to console - logger.electron.error("Uncaught exception:", error); - // Show error dialog to user - dialog.showErrorBox("Error", error.message); -}); diff --git a/electron-app/preload.js b/electron-app/preload.js index 5fda40215..05947b2de 100644 --- a/electron-app/preload.js +++ b/electron-app/preload.js @@ -38,6 +38,11 @@ contextBridge.exposeInMainWorld("electronAPI", { selectFolder: () => ipcRenderer.invoke("select-folder"), // Open a folder path in the OS file explorer openFolder: (path) => ipcRenderer.invoke("open-folder", path), + // Set initial mode (used by mode selector renderer) + setInitialMode: (mode) => { + ipcRenderer.send("mode-selected", mode); + return Promise.resolve(); + }, // Receive log message from backend onLog: (callback) => { const listener = (_event, value) => callback(value); diff --git a/electron-app/src/app/cleanup.js b/electron-app/src/app/cleanup.js new file mode 100644 index 000000000..3cc8c8ffc --- /dev/null +++ b/electron-app/src/app/cleanup.js @@ -0,0 +1,33 @@ +/** + * @module app/cleanup + * @description Cleanup utilities for terminating leftover processes from previous sessions. + */ + +import { execSync } from "child_process"; +import { logger } from "../utils/logger.js"; + +/** + * Terminates any leftover backend processes from previous sessions. + * Prevents backend/log windows from appearing before user selects a mode. + * @returns {Promise} + */ +async function cleanupLeftoverBackendProcesses() { + try { + const out = execSync("pgrep -f backend-linux-amd64 || true").toString().trim(); + if (out) { + const pids = out.split(/\s+/).filter(Boolean); + for (const pid of pids) { + try { + process.kill(Number(pid), "SIGTERM"); + logger.electron.info(`Terminated leftover backend pid=${pid}`); + } catch (e) { + logger.electron.debug(`Failed to terminate pid ${pid}:`, e); + } + } + } + } catch (e) { + logger.electron.debug("Error checking/killing leftover backends:", e); + } +} + +export { cleanupLeftoverBackendProcesses }; diff --git a/electron-app/src/app/index.js b/electron-app/src/app/index.js new file mode 100644 index 000000000..cd212fa15 --- /dev/null +++ b/electron-app/src/app/index.js @@ -0,0 +1,11 @@ +/** + * @module app + * @description Application lifecycle and initialization exports. + */ + +export { cleanupLeftoverBackendProcesses } from "./cleanup.js"; +export { initializeApp } from "./initialization.js"; +export { setupLifecycleHandlers } from "./lifecycle.js"; +export { handleSelectorFallback, showModeSelector } from "./modeSelector.js"; +export { setupUpdater } from "./updater.js"; + diff --git a/electron-app/src/app/initialization.js b/electron-app/src/app/initialization.js new file mode 100644 index 000000000..3e6195ca0 --- /dev/null +++ b/electron-app/src/app/initialization.js @@ -0,0 +1,31 @@ +/** + * @module app/initialization + * @description Application initialization: config setup and cleanup. + */ + +import { getConfigManager } from "../config/configInstance.js"; +import { setupIpcHandlers } from "../ipc/handlers.js"; +import { logger } from "../utils/logger.js"; +import { cleanupLeftoverBackendProcesses } from "./cleanup.js"; + +/** + * Initializes the application: + * - Sets up IPC handlers + * - Initializes configuration + * - Cleans up leftover processes + * @returns {Promise} + */ +async function initializeApp() { + // Setup IPC handlers for renderer process communication + setupIpcHandlers(); + + // Initialize ConfigManager and ensure config exists + logger.electron.header("Initializing configuration..."); + await getConfigManager(); + logger.electron.header("Configuration ready"); + + // Clean up leftover processes from previous sessions + await cleanupLeftoverBackendProcesses(); +} + +export { initializeApp }; diff --git a/electron-app/src/app/lifecycle.js b/electron-app/src/app/lifecycle.js new file mode 100644 index 000000000..b0ccdd84c --- /dev/null +++ b/electron-app/src/app/lifecycle.js @@ -0,0 +1,49 @@ +/** + * @module app/lifecycle + * @description Application lifecycle event handlers. + */ + +import { app, BrowserWindow, dialog } from "electron"; +import { stopBackend } from "../processes/backend.js"; +import { stopBlcuProgramming } from "../processes/blcuProgramming.js"; +import { logger } from "../utils/logger.js"; +import { createWindow } from "../windows/index.js"; + +/** + * Sets up all application lifecycle event handlers. + * @returns {void} + */ +function setupLifecycleHandlers() { + // Handle window close behavior + app.on("window-all-closed", () => { + // On macOS, keep app running even when all windows are closed + if (process.platform !== "darwin") { + // Quit app on other platforms when all windows are closed + app.quit(); + } + }); + + // Cleanup before app quits + app.on("before-quit", (e) => { + e.preventDefault(); + Promise.all([stopBackend(), stopBlcuProgramming()]) + .catch((error) => logger.electron.error("Error during shutdown:", error)) + .finally(() => app.exit()); + }); + + // Handle macOS app activation (reopen window when dock icon clicked) + app.on("activate", () => { + // Only create window if no windows exist + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); + + // Handle uncaught exceptions globally + process.on("uncaughtException", (error) => { + logger.electron.error("Uncaught exception:", error); + dialog.showErrorBox("Error", error.message); + }); +} + +export { setupLifecycleHandlers }; diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js new file mode 100644 index 000000000..a3780d6fc --- /dev/null +++ b/electron-app/src/app/modeSelector.js @@ -0,0 +1,161 @@ +/** + * @module app/modeSelector + * @description Mode selector window and logic for initial app mode selection. + */ + +import { BrowserWindow, ipcMain } from "electron"; +import fs from "fs"; +import path from "path"; +import { startBackend } from "../processes/backend.js"; +import { startBlcuProgramming } from "../processes/blcuProgramming.js"; +import { logger } from "../utils/logger.js"; +import { getAppPath } from "../utils/paths.js"; +import { createLogWindow, createWindow } from "../windows/index.js"; + +const VALID_MODES = { + testing: "testing-view", + flashing: "flashing-view", + default: "testing-view", +}; + +/** + * Creates and displays the mode selector window. + * Returns a Promise that resolves when user selects a mode. + * @param {number} screenWidth + * @param {number} screenHeight + * @returns {Promise<{mode: string, view: string, mainWindow: BrowserWindow}>} + */ +async function showModeSelector(screenWidth, screenHeight) { + return new Promise(async (resolve, reject) => { + let mainWindow = null; + + const selectorWindow = new BrowserWindow({ + width: 480, + height: 320, + resizable: false, + modal: true, + parent: mainWindow, + show: true, + webPreferences: { + preload: path.join(getAppPath(), "preload.js"), + contextIsolation: true, + nodeIntegration: false, + }, + title: "Select Mode", + }); + + const selectorPath = path.join(getAppPath(), "renderer", "mode-selector", "index.html"); + + if (!fs.existsSync(selectorPath)) { + logger.electron.warning("Mode selector UI not found, using default testing-view"); + resolve({ mode: "default", view: VALID_MODES.default, mainWindow: null }); + return; + } + + logger.electron.info(`Mode selector found: ${selectorPath}`); + + try { + await selectorWindow.loadFile(selectorPath); + selectorWindow.show(); + selectorWindow.focus(); + } catch (err) { + logger.electron.error("Failed to load selector UI:", err); + resolve({ mode: "default", view: VALID_MODES.default, mainWindow: null }); + return; + } + + // Listen for mode selection from renderer + ipcMain.once("mode-selected", async (_event, mode) => { + try { + const view = VALID_MODES[mode] || VALID_MODES.default; + + // Create the main window with selected view + mainWindow = createWindow(screenWidth, screenHeight, view); + try { + mainWindow.maximize(); + } catch (e) {} + logger.electron.header("Main application window created"); + + // Start backend and logging only for testing view + if (view === "testing-view" || view === "flashing-view") { + await startServices(screenWidth, screenHeight, view); + } + + // Show and focus main window + try { + mainWindow.show(); + mainWindow.focus(); + } catch (e) {} + + resolve({ mode, view, mainWindow }); + } catch (error) { + logger.electron.error("Error handling mode selection:", error); + reject(error); + } finally { + try { + selectorWindow.close(); + } catch (e) {} + } + }); + }); +} + +/** + * Starts services based on the selected view. + * - testing-view: Backend + BLCU Programming + * - flashing-view: BLCU Programming only + * @param {number} screenWidth + * @param {number} screenHeight + * @param {string} view - The selected view mode + * @returns {Promise} + */ +async function startServices(screenWidth, screenHeight, view) { + const logWindow = createLogWindow(screenWidth, screenHeight); + + // Start backend only for testing view + if (view === "testing-view") { + try { + await startBackend(logWindow); + logger.electron.header("Backend process spawned"); + } catch (err) { + logger.electron.error("Failed to start backend:", err); + } + } + + // Start BLCU Programming for both testing and flashing views + try { + await startBlcuProgramming(logWindow); + logger.electron.header("BLCU programming process spawned"); + } catch (err) { + logger.electron.error("Failed to start BLCU programming:", err); + } +} + +/** + * Handles fallback when selector is not available or fails. + * @param {number} screenWidth + * @param {number} screenHeight + * @returns {Promise<{mode: string, view: string, mainWindow: BrowserWindow}>} + */ +async function handleSelectorFallback(screenWidth, screenHeight) { + const view = VALID_MODES.default; + const mainWindow = createWindow(screenWidth, screenHeight, view); + + try { + mainWindow.maximize(); + } catch (e) {} + + logger.electron.header("Main application window created"); + + try { + mainWindow.show(); + } catch (e) {} + + // Start services by default (testing view) + await startServices(screenWidth, screenHeight, view); + + return { mode: "default", view, mainWindow }; +} + +export { handleSelectorFallback, showModeSelector }; + diff --git a/electron-app/src/app/updater.js b/electron-app/src/app/updater.js new file mode 100644 index 000000000..c5a9c5dba --- /dev/null +++ b/electron-app/src/app/updater.js @@ -0,0 +1,49 @@ +/** + * @module app/updater + * @description Auto-updater configuration and event handling. + */ + +import { app, dialog } from "electron"; +import pkg from "electron-updater"; +import { logger } from "../utils/logger.js"; + +const { autoUpdater } = pkg; + +/** + * Initializes the auto-updater with appropriate logging and event handlers. + * @returns {void} + */ +function setupUpdater() { + if (!app.isPackaged) { + autoUpdater.forceDevUpdateConfig = true; + } + + // Configure auto-updater logging + autoUpdater.logger = { + info: (message) => logger.electron.info(message), + error: (message) => logger.electron.error(message), + warn: (message) => logger.electron.warning(message), + debug: (message) => logger.electron.debug(message), + }; + + // Handle update downloaded event + autoUpdater.on("update-downloaded", (info) => { + dialog + .showMessageBox({ + type: "info", + title: "Update Ready", + message: `Version ${info.version} has been downloaded. Restart now to install?`, + buttons: ["Restart", "Later"], + }) + .then((result) => { + if (result.response === 0) { + autoUpdater.quitAndInstall(); + } + }); + }); + + // Check for updates + autoUpdater.checkForUpdates(); +} + +export { setupUpdater }; diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index 269ea5235..e9b47f5e6 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -11,17 +11,17 @@ import { dialog, ipcMain, shell } from "electron"; import fs from "fs"; import { isAbsolute, join } from "path"; import { - importConfig, - readConfig, - writeConfig, + importConfig, + readConfig, + writeConfig, } from "../config/configInstance.js"; import { getBackendWorkingDir, restartBackend } from "../processes/backend.js"; import { logger } from "../utils/logger.js"; import { - getCurrentView, - getMainWindow, - loadView, - reloadWindow, + getCurrentView, + getMainWindow, + loadView, + reloadWindow, } from "../windows/mainWindow.js"; /** @@ -52,6 +52,8 @@ function setupIpcHandlers() { return view; }); + // Mode selection is handled in main process via 'mode-selected' event. + /** * @event save-config * @async diff --git a/electron-app/src/windows/index.js b/electron-app/src/windows/index.js new file mode 100644 index 000000000..06cd375b2 --- /dev/null +++ b/electron-app/src/windows/index.js @@ -0,0 +1,8 @@ +/** + * @module windows + * @description Window creation and management exports. + */ + +export { createLogWindow } from "./logWindow.js"; +export { createWindow } from "./mainWindow.js"; + diff --git a/electron-app/src/windows/mainWindow.js b/electron-app/src/windows/mainWindow.js index 0bf125b85..3fc755f64 100644 --- a/electron-app/src/windows/mainWindow.js +++ b/electron-app/src/windows/mainWindow.js @@ -24,7 +24,7 @@ let currentView = "testing-view"; * @example * createWindow(); */ -function createWindow(screenWidth, screenHeight) { +function createWindow(screenWidth, screenHeight, initialView) { // Create new browser window with configuration mainWindow = new BrowserWindow({ x: 0, @@ -47,8 +47,15 @@ function createWindow(screenWidth, screenHeight) { backgroundColor: "#1a1a1a", }); - // Load ethernet view by default - loadView(currentView); + // If an initial view string is provided, load it. + // If `initialView` is explicitly null, skip loading so caller can decide later. + if (typeof initialView === "string") { + loadView(initialView); + } else if (initialView === null) { + // skip loading any view for now + } else { + loadView(currentView); + } // Create application menu const menu = createMenu(mainWindow); From 5b6200abe8eef6ab5c1aeb3b8f249d96c3bd3480 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 00:47:21 +0200 Subject: [PATCH 12/19] feat: not show log window at blcu --- electron-app/src/app/modeSelector.js | 9 +++++++-- electron-app/src/menu/menu.js | 25 +++++-------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index a3780d6fc..5d3843e68 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -110,7 +110,12 @@ async function showModeSelector(screenWidth, screenHeight) { * @returns {Promise} */ async function startServices(screenWidth, screenHeight, view) { - const logWindow = createLogWindow(screenWidth, screenHeight); + let logWindow = null; + + // Create the backend log window only for testing view + if (view === "testing-view") { + logWindow = createLogWindow(screenWidth, screenHeight); + } // Start backend only for testing view if (view === "testing-view") { @@ -124,7 +129,7 @@ async function startServices(screenWidth, screenHeight, view) { // Start BLCU Programming for both testing and flashing views try { - await startBlcuProgramming(logWindow); + await startBlcuProgramming(); logger.electron.header("BLCU programming process spawned"); } catch (err) { logger.electron.error("Failed to start BLCU programming:", err); diff --git a/electron-app/src/menu/menu.js b/electron-app/src/menu/menu.js index c64b64ff4..82499b48a 100644 --- a/electron-app/src/menu/menu.js +++ b/electron-app/src/menu/menu.js @@ -1,15 +1,15 @@ /** * @module menu * @description Application menu creation and management for the Electron application. - * Defines menu structure with File, View, Tools, and Help sections with keyboard shortcuts and actions. + * Defines menu structure with File, Tools, and Help sections with keyboard shortcuts and actions. */ import { Menu, app, dialog } from "electron"; -import { loadView } from "../windows/mainWindow.js"; /** - * Creates and sets the application menu with File, View, Tools, and Help sections. - * Includes menu items for reloading, exiting, switching views, toggling DevTools, and managing packet sender. + * Creates and sets the application menu with File, Tools, and Help sections. + * Includes menu items for reloading, exiting, toggling DevTools, and app information. + * View switching is no longer available since the mode is selected at startup. * @param {import("electron").BrowserWindow} mainWindow - The main browser window instance to attach menu actions to. * @returns {void} * @example @@ -38,23 +38,8 @@ function createMenu(mainWindow) { ], }, { - label: "View", + label: "Tools", submenu: [ - { - label: "Competition View", - accelerator: "CmdOrCtrl+1", - click: () => { - loadView("competition-view"); - }, - }, - { - label: "Testing View", - accelerator: "CmdOrCtrl+2", - click: () => { - loadView("testing-view"); - }, - }, - { type: "separator" }, { label: "Toggle DevTools", accelerator: "F12", From 13281090ea10287883e7af3102a23141b38d3979 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 01:18:16 +0200 Subject: [PATCH 13/19] feat: enhance selection window --- electron-app/.gitignore | 5 + electron-app/package.json | 2 +- electron-app/preload.js | 2 + .../renderer/mode-selector/index.html | 245 ++++++++++++++++++ .../renderer/mode-selector/src/Subsystem.png | Bin 0 -> 20750 bytes .../renderer/mode-selector/src/logo.svg | 28 ++ .../renderer/mode-selector/src/sign.png | Bin 0 -> 16217 bytes electron-app/src/app/modeSelector.js | 7 +- electron-app/src/ipc/handlers.js | 18 +- 9 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 electron-app/renderer/mode-selector/index.html create mode 100644 electron-app/renderer/mode-selector/src/Subsystem.png create mode 100644 electron-app/renderer/mode-selector/src/logo.svg create mode 100644 electron-app/renderer/mode-selector/src/sign.png diff --git a/electron-app/.gitignore b/electron-app/.gitignore index ce4204b7b..2f4d5e1f1 100644 --- a/electron-app/.gitignore +++ b/electron-app/.gitignore @@ -7,6 +7,11 @@ dist-ssr build binaries renderer +!renderer/ +renderer/testing-view/ +renderer/flashing-view/ +!renderer/mode-selector/ +!renderer/mode-selector/** out *.local diff --git a/electron-app/package.json b/electron-app/package.json index 083a5bd56..58265f789 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -1,6 +1,6 @@ { "name": "hyperloop-control-station", - "version": "1.0.0", + "version": "11.1.0", "description": "Hyperloop UPV Control Station", "main": "main.js", "type": "module", diff --git a/electron-app/preload.js b/electron-app/preload.js index 05947b2de..e9fcb690d 100644 --- a/electron-app/preload.js +++ b/electron-app/preload.js @@ -38,6 +38,8 @@ contextBridge.exposeInMainWorld("electronAPI", { selectFolder: () => ipcRenderer.invoke("select-folder"), // Open a folder path in the OS file explorer openFolder: (path) => ipcRenderer.invoke("open-folder", path), + // Get the application version from the main process + getAppVersion: () => ipcRenderer.invoke("get-app-version"), // Set initial mode (used by mode selector renderer) setInitialMode: (mode) => { ipcRenderer.send("mode-selected", mode); diff --git a/electron-app/renderer/mode-selector/index.html b/electron-app/renderer/mode-selector/index.html new file mode 100644 index 000000000..e554d12fc --- /dev/null +++ b/electron-app/renderer/mode-selector/index.html @@ -0,0 +1,245 @@ + + + + + + + Seleccione Modo + + + + +
+ Departamento + +
+

Control Station

+
+
+ + +
+
+ Logo auxiliar + +
+
+ + + + \ No newline at end of file diff --git a/electron-app/renderer/mode-selector/src/Subsystem.png b/electron-app/renderer/mode-selector/src/Subsystem.png new file mode 100644 index 0000000000000000000000000000000000000000..aed1aee9117e9d60f2a1709a6352190198b61859 GIT binary patch literal 20750 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 RJb~$#!PC{xWt~$(69AzE1~C8t literal 0 HcmV?d00001 diff --git a/electron-app/renderer/mode-selector/src/logo.svg b/electron-app/renderer/mode-selector/src/logo.svg new file mode 100644 index 000000000..20d20319d --- /dev/null +++ b/electron-app/renderer/mode-selector/src/logo.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/electron-app/renderer/mode-selector/src/sign.png b/electron-app/renderer/mode-selector/src/sign.png new file mode 100644 index 0000000000000000000000000000000000000000..5771951ddb746d8e3a57e5118436172ba488a07b GIT binary patch literal 16217 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 literal 0 HcmV?d00001 diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index 5d3843e68..8e4fee3a3 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -30,9 +30,10 @@ async function showModeSelector(screenWidth, screenHeight) { let mainWindow = null; const selectorWindow = new BrowserWindow({ - width: 480, - height: 320, - resizable: false, + width: 920, + height: 680, + useContentSize: true, + resizable: true, modal: true, parent: mainWindow, show: true, diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index e9b47f5e6..e36604f54 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -7,21 +7,21 @@ * - Folder selection dialogs */ -import { dialog, ipcMain, shell } from "electron"; +import { app, dialog, ipcMain, shell } from "electron"; import fs from "fs"; import { isAbsolute, join } from "path"; import { - importConfig, - readConfig, - writeConfig, + importConfig, + readConfig, + writeConfig, } from "../config/configInstance.js"; import { getBackendWorkingDir, restartBackend } from "../processes/backend.js"; import { logger } from "../utils/logger.js"; import { - getCurrentView, - getMainWindow, - loadView, - reloadWindow, + getCurrentView, + getMainWindow, + loadView, + reloadWindow, } from "../windows/mainWindow.js"; /** @@ -40,6 +40,8 @@ function setupIpcHandlers() { */ ipcMain.handle("get-current-view", () => getCurrentView()); + ipcMain.handle("get-app-version", () => app.getVersion()); + /** * @event switch-view * @description Switches the main window to the specified view. From 976155dca74df8ad1f7c73a527ffdb0b93473b8a Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 01:25:07 +0200 Subject: [PATCH 14/19] fix: wait before opening testing view --- electron-app/src/app/modeSelector.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index 8e4fee3a3..9be4da7af 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -11,6 +11,7 @@ import { startBlcuProgramming } from "../processes/blcuProgramming.js"; import { logger } from "../utils/logger.js"; import { getAppPath } from "../utils/paths.js"; import { createLogWindow, createWindow } from "../windows/index.js"; +import { loadView } from "../windows/mainWindow.js"; const VALID_MODES = { testing: "testing-view", @@ -70,18 +71,21 @@ async function showModeSelector(screenWidth, screenHeight) { try { const view = VALID_MODES[mode] || VALID_MODES.default; - // Create the main window with selected view - mainWindow = createWindow(screenWidth, screenHeight, view); + // Create the main window without loading the view yet. + mainWindow = createWindow(screenWidth, screenHeight, null); try { mainWindow.maximize(); } catch (e) {} logger.electron.header("Main application window created"); - // Start backend and logging only for testing view + // Start services and only then load the selected view. if (view === "testing-view" || view === "flashing-view") { await startServices(screenWidth, screenHeight, view); + await new Promise((resolve) => setTimeout(resolve, 1000)); } + loadView(view); + // Show and focus main window try { mainWindow.show(); From 75acd70eff061906de7bc3942fcd9878a47decd7 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 01:31:57 +0200 Subject: [PATCH 15/19] feat: return to selector button --- electron-app/main.js | 26 +++++++++++++++++++++++++- electron-app/src/menu/menu.js | 5 +++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/electron-app/main.js b/electron-app/main.js index 638d0e2e1..d154b9106 100644 --- a/electron-app/main.js +++ b/electron-app/main.js @@ -9,7 +9,7 @@ * - lifecycle: App lifecycle event handling */ -import { app, screen } from "electron"; +import { app, BrowserWindow, screen } from "electron"; import { handleSelectorFallback, initializeApp, @@ -17,6 +17,8 @@ import { setupUpdater, showModeSelector, } from "./src/app/index.js"; +import { stopBackend } from "./src/processes/backend.js"; +import { stopBlcuProgramming } from "./src/processes/blcuProgramming.js"; import { logger } from "./src/utils/logger.js"; /** @@ -35,6 +37,28 @@ app.whenReady().then(async () => { // Get screen dimensions for window creation const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize; + // Register return-to-selector handler before starting the app. + app.on("return-to-selector", async () => { + try { + logger.electron.info("Returning to selector mode..."); + await Promise.all([stopBackend(), stopBlcuProgramming()]); + + const existingWindows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed()); + existingWindows.forEach((window) => window.hide()); + + const { width: selectorWidth, height: selectorHeight } = screen.getPrimaryDisplay().workAreaSize; + await showModeSelector(selectorWidth, selectorHeight); + + existingWindows.forEach((window) => { + if (!window.isDestroyed()) { + window.close(); + } + }); + } catch (error) { + logger.electron.error("Failed to return to selector:", error); + } + }); + // Show mode selector and get user choice try { await showModeSelector(screenWidth, screenHeight); diff --git a/electron-app/src/menu/menu.js b/electron-app/src/menu/menu.js index 82499b48a..a7cdc27e7 100644 --- a/electron-app/src/menu/menu.js +++ b/electron-app/src/menu/menu.js @@ -29,6 +29,11 @@ function createMenu(mainWindow) { } }, }, + { + label: "Return to Selector", + accelerator: "CmdOrCtrl+Shift+S", + click: () => app.emit("return-to-selector"), + }, { type: "separator" }, { label: "Exit", From 8f536ed068c815375ad297c7c4a3854343929748 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Tue, 16 Jun 2026 15:32:34 +0200 Subject: [PATCH 16/19] docs(electron): remove orphan comment --- electron-app/src/ipc/handlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index e36604f54..5a4198d23 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -54,7 +54,7 @@ function setupIpcHandlers() { return view; }); - // Mode selection is handled in main process via 'mode-selected' event. + /** * @event save-config From e47561f774fa9ff148e7d1d058a3e283e214b200 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 17 Jun 2026 00:08:42 +0200 Subject: [PATCH 17/19] fix(electron): solve close testing view and open again bug --- electron-app/main.js | 12 +++ electron-app/src/app/index.js | 2 +- electron-app/src/app/lifecycle.js | 20 ++++- electron-app/src/app/modeSelector.js | 29 +++---- electron-app/src/processes/backend.js | 80 ++++++++++++++----- electron-app/src/processes/blcuProgramming.js | 25 ++++-- 6 files changed, 127 insertions(+), 41 deletions(-) diff --git a/electron-app/main.js b/electron-app/main.js index d154b9106..6ce31fd17 100644 --- a/electron-app/main.js +++ b/electron-app/main.js @@ -13,6 +13,7 @@ import { app, BrowserWindow, screen } from "electron"; import { handleSelectorFallback, initializeApp, + setTransitionMode, setupLifecycleHandlers, setupUpdater, showModeSelector, @@ -40,8 +41,14 @@ app.whenReady().then(async () => { // Register return-to-selector handler before starting the app. app.on("return-to-selector", async () => { try { + // Set transition mode to prevent window-all-closed from quitting + setTransitionMode(true); + logger.electron.info("Returning to selector mode..."); await Promise.all([stopBackend(), stopBlcuProgramming()]); + + // Give ports time to be released (increased to 3s for TCP TIME_WAIT state) + await new Promise((resolve) => setTimeout(resolve, 3000)); const existingWindows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed()); existingWindows.forEach((window) => window.hide()); @@ -51,11 +58,16 @@ app.whenReady().then(async () => { existingWindows.forEach((window) => { if (!window.isDestroyed()) { + window.removeAllListeners("close"); window.close(); } }); + + // Exit transition mode once selector is shown + setTransitionMode(false); } catch (error) { logger.electron.error("Failed to return to selector:", error); + setTransitionMode(false); } }); diff --git a/electron-app/src/app/index.js b/electron-app/src/app/index.js index cd212fa15..5ae193df7 100644 --- a/electron-app/src/app/index.js +++ b/electron-app/src/app/index.js @@ -5,7 +5,7 @@ export { cleanupLeftoverBackendProcesses } from "./cleanup.js"; export { initializeApp } from "./initialization.js"; -export { setupLifecycleHandlers } from "./lifecycle.js"; +export { setupLifecycleHandlers, setTransitionMode } from "./lifecycle.js"; export { handleSelectorFallback, showModeSelector } from "./modeSelector.js"; export { setupUpdater } from "./updater.js"; diff --git a/electron-app/src/app/lifecycle.js b/electron-app/src/app/lifecycle.js index b0ccdd84c..acad03e7b 100644 --- a/electron-app/src/app/lifecycle.js +++ b/electron-app/src/app/lifecycle.js @@ -9,6 +9,17 @@ import { stopBlcuProgramming } from "../processes/blcuProgramming.js"; import { logger } from "../utils/logger.js"; import { createWindow } from "../windows/index.js"; +// Flag to indicate we're in a transition (e.g., returning to selector) +let isInTransition = false; + +/** + * Sets the transition flag + * @param {boolean} inTransition + */ +function setTransitionMode(inTransition) { + isInTransition = inTransition; +} + /** * Sets up all application lifecycle event handlers. * @returns {void} @@ -16,6 +27,12 @@ import { createWindow } from "../windows/index.js"; function setupLifecycleHandlers() { // Handle window close behavior app.on("window-all-closed", () => { + // Don't quit if we're in a transition (returning to selector) + if (isInTransition) { + logger.electron.debug("In transition mode - skipping app quit on window-all-closed"); + return; + } + // On macOS, keep app running even when all windows are closed if (process.platform !== "darwin") { // Quit app on other platforms when all windows are closed @@ -46,4 +63,5 @@ function setupLifecycleHandlers() { }); } -export { setupLifecycleHandlers }; +export { setTransitionMode, setupLifecycleHandlers }; + diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index 9be4da7af..078f40a2d 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -107,7 +107,7 @@ async function showModeSelector(screenWidth, screenHeight) { /** * Starts services based on the selected view. - * - testing-view: Backend + BLCU Programming + * - testing-view: Backend only * - flashing-view: BLCU Programming only * @param {number} screenWidth * @param {number} screenHeight @@ -115,29 +115,30 @@ async function showModeSelector(screenWidth, screenHeight) { * @returns {Promise} */ async function startServices(screenWidth, screenHeight, view) { - let logWindow = null; - - // Create the backend log window only for testing view - if (view === "testing-view") { - logWindow = createLogWindow(screenWidth, screenHeight); - } - // Start backend only for testing view if (view === "testing-view") { + const logWindow = createLogWindow(screenWidth, screenHeight); + logWindow.show(); // Show the log window + try { await startBackend(logWindow); logger.electron.header("Backend process spawned"); } catch (err) { logger.electron.error("Failed to start backend:", err); + if (logWindow && !logWindow.isDestroyed()) { + logWindow.close(); + } } } - // Start BLCU Programming for both testing and flashing views - try { - await startBlcuProgramming(); - logger.electron.header("BLCU programming process spawned"); - } catch (err) { - logger.electron.error("Failed to start BLCU programming:", err); + // Start BLCU Programming only for flashing view + if (view === "flashing-view") { + try { + await startBlcuProgramming(); + logger.electron.header("BLCU programming process spawned"); + } catch (err) { + logger.electron.error("Failed to start BLCU programming:", err); + } } } diff --git a/electron-app/src/processes/backend.js b/electron-app/src/processes/backend.js index b021edfdc..64b51c838 100644 --- a/electron-app/src/processes/backend.js +++ b/electron-app/src/processes/backend.js @@ -32,6 +32,18 @@ let storedLogWindow = null; // Store error messages accumulated from the current process run let lastBackendError = null; +/** + * Clears the stored log window reference and closes it + */ +function clearLogWindow() { + if (storedLogWindow && !storedLogWindow.isDestroyed()) { + try { + storedLogWindow.close(); + } catch (e) {} + } + storedLogWindow = null; +} + /** * Starts the backend process by spawning the backend binary with the user configuration. * @returns {void} @@ -46,6 +58,8 @@ async function startBackend(logWindow = null) { const currentLogWindow = logWindow || storedLogWindow; return new Promise((resolve, reject) => { + let resolved = false; + // Get paths for binary and config const backendBin = getBinaryPath("backend"); const configPath = getUserConfigPath(); @@ -91,9 +105,10 @@ async function startBackend(logWindow = null) { // Resolve as soon as the HTTP server confirms it is listening. // Matches: "INF ... > http server listening localAddr=..." - if (text.includes("http server listening")) { + if (text.includes("http server listening") && !resolved) { logger.backend.info("Backend ready (HTTP server listening)"); clearTimeout(startupTimer); + resolved = true; resolve(backendProcess); } }); @@ -118,7 +133,10 @@ async function startBackend(logWindow = null) { "Backend Error", `Failed to start backend: ${error.message}`, ); - return reject(new Error(`Failed to start backend: ${error.message}`)); + if (!resolved) { + resolved = true; + return reject(new Error(`Failed to start backend: ${error.message}`)); + } }); // Handle process exit @@ -126,21 +144,32 @@ async function startBackend(logWindow = null) { logger.backend.info(`Backend process exited with code ${code}`); clearTimeout(startupTimer); - if (code !== 0 && code !== null) { - let errorMessage = `Backend exited with code ${code}`; - - if (lastBackendError) { - const stripped = lastBackendError.replace(/\x1b\[[0-9;]*m/g, ""); - const formatted = formatBackendError(stripped); - errorMessage += `\n\n${getHint(stripped, formatted)}`; - } else { - errorMessage += "\n\n(No error output captured)"; + // If the process closed without success, reject the promise + if (!resolved) { + if (code !== 0 && code !== null) { + let errorMessage = `Backend exited with code ${code}`; + + if (lastBackendError) { + const stripped = lastBackendError.replace(/\x1b\[[0-9;]*m/g, ""); + const formatted = formatBackendError(stripped); + errorMessage += `\n\n${getHint(stripped, formatted)}`; + } else { + errorMessage += "\n\n(No error output captured)"; + } + + dialog.showErrorBox("Backend Crashed", errorMessage); + lastBackendError = null; + backendProcess = null; + resolved = true; + return reject(new Error(errorMessage)); } - dialog.showErrorBox("Backend Crashed", errorMessage); - lastBackendError = null; - backendProcess = null; - return reject(new Error(errorMessage)); + if (code === null || code === 0) { + logger.backend.warning("Backend closed before ready signal - likely port conflict or initialization error"); + backendProcess = null; + resolved = true; + return reject(new Error("Backend process closed before initialization completed")); + } } backendProcess = null; @@ -148,10 +177,13 @@ async function startBackend(logWindow = null) { // Fallback: if the ready message never appears, resolve anyway after timeout const startupTimer = setTimeout(() => { - logger.backend.warning( - "Backend ready signal not received - resolving after timeout", - ); - resolve(backendProcess); + if (!resolved) { + logger.backend.warning( + "Backend ready signal not received - resolving after timeout", + ); + resolved = true; + resolve(backendProcess); + } }, 5000); }); } @@ -176,6 +208,8 @@ async function stopBackend() { if (localBackendProcess === backendProcess) { backendProcess = null; } + // Clean up log window + clearLogWindow(); resolve(); }); @@ -194,6 +228,8 @@ async function stopBackend() { fallbackTimer.unref(); } else { logger.backend.warning("Backend process not found, skipping stop..."); + // Clean up log window even if process doesn't exist + clearLogWindow(); resolve(); } }); @@ -209,6 +245,10 @@ async function restartBackend() { // Stop current process first await stopBackend(); + if (localBackendProcess.stdin) { + localBackendProcess.stdin.end(); + } + // Start a new process try { await startBackend(); @@ -225,4 +265,4 @@ function getBackendWorkingDir() { : path.dirname(getUserConfigPath()); } -export { getBackendWorkingDir, restartBackend, startBackend, stopBackend }; +export { clearLogWindow, getBackendWorkingDir, restartBackend, startBackend, stopBackend }; diff --git a/electron-app/src/processes/blcuProgramming.js b/electron-app/src/processes/blcuProgramming.js index aebd2a3a6..80b7d7701 100644 --- a/electron-app/src/processes/blcuProgramming.js +++ b/electron-app/src/processes/blcuProgramming.js @@ -139,12 +139,27 @@ async function startBlcuProgramming() { } async function stopBlcuProgramming() { - if (!blcuProgrammingProcess || blcuProgrammingProcess.killed) { - return; - } + return new Promise((resolve) => { + if (!blcuProgrammingProcess || blcuProgrammingProcess.killed) { + resolve(); + return; + } + + blcuProgrammingProcess.once("close", () => { + resolve(); + }); - blcuProgrammingProcess.kill("SIGTERM"); - blcuProgrammingProcess = null; + blcuProgrammingProcess.kill("SIGTERM"); + + const fallbackTimer = setTimeout(() => { + if (blcuProgrammingProcess && !blcuProgrammingProcess.killed) { + blcuProgrammingProcess.kill("SIGKILL"); + } + resolve(); + }, 3000); + + fallbackTimer.unref(); + }); } export { startBlcuProgramming, stopBlcuProgramming }; From c8e60d39c3bb9381fcab1bded80cc5775689d17a Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 17 Jun 2026 00:14:43 +0200 Subject: [PATCH 18/19] fix(electron): remove timer to start front --- electron-app/src/app/modeSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/electron-app/src/app/modeSelector.js b/electron-app/src/app/modeSelector.js index 078f40a2d..a64d54fcf 100644 --- a/electron-app/src/app/modeSelector.js +++ b/electron-app/src/app/modeSelector.js @@ -81,7 +81,6 @@ async function showModeSelector(screenWidth, screenHeight) { // Start services and only then load the selected view. if (view === "testing-view" || view === "flashing-view") { await startServices(screenWidth, screenHeight, view); - await new Promise((resolve) => setTimeout(resolve, 1000)); } loadView(view); From 5d9c285280b73c20e123bfe1156b31c5ac15c75f Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 17 Jun 2026 00:41:26 +0200 Subject: [PATCH 19/19] docs: update readme --- electron-app/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/electron-app/README.md b/electron-app/README.md index d28d055ad..03c363201 100644 --- a/electron-app/README.md +++ b/electron-app/README.md @@ -24,7 +24,7 @@ When running in development mode (unpackaged), the application creates temporary - `binaries/` - Directory containing compiled backend and BLCU programming executables for your platform. These are generated during the build process, when running `pnpm run build`. -- `renderer/` - Directory containing built frontend views (control-station, ethernet-view). These are generated during the build process, when running `pnpm run build`. +- `renderer/` - Directory containing built frontend views (control-station, ethernet-view). These are generated during the build process, when running `pnpm run build`. All its content is ignored, except `mode-selector` - `dist/` - Build output directory containing compiled and packaged application files. Generated during build and distribution processes, when running `pnpm run dist`. @@ -100,9 +100,10 @@ sudo ifconfig lo0 alias 127.0.0.9 up - **Backend Process**: Go backend for data processing - **BLCU Programming Process**: Packaged FastAPI/TFTP API for firmware transfers -- **Packet Sender**: Tool for sending test packets - **Configuration**: TOML-based config management -- **Views**: Multiple frontend interfaces (Competition/Testing) +- **Views**: Multiple frontend interfaces (Competition, Testing & Flashing) + +- **Mode Selector**: html5 file to chose the mode of the app: testing, competition or flashing. Placed at `renderer/mode-selector` ## Dependencies