refactor startup flow and remove legacy error handling (#14)

This commit is contained in:
Pandipipas
2026-02-10 22:45:54 +01:00
committed by GitHub
parent db81138af1
commit f3de69decf
4 changed files with 39 additions and 122 deletions
+34 -80
View File
@@ -1,4 +1,4 @@
import { app, BrowserWindow, dialog, shell } from "electron";
import { app, BrowserWindow, shell } from "electron";
import { ChildProcess, spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
@@ -8,11 +8,11 @@ const DEFAULT_NODECG_PORT = process.env.NODECG_PORT ?? "9090";
const DEFAULT_BUNDLE_NAME = process.env.NODECG_BUNDLE_NAME ?? "scoreko-dev";
const DEFAULT_DASHBOARD_ROUTE = process.env.SCOREKO_DASHBOARD_ROUTE ?? "dashboard/example/main.html?standalone=true";
const DEFAULT_LOADING_ROUTE = process.env.SCOREKO_LOADING_ROUTE ?? "dashboard/loading/main.html?standalone=true";
const LOAD_DELAY_MS = Number.parseInt(process.env.ELECTRON_LOAD_DELAY_MS ?? "10000", 10);
const STARTUP_TIMEOUT_MS = Number.parseInt(process.env.NODECG_STARTUP_TIMEOUT_MS ?? "30000", 10);
const LOAD_DELAY_MS = parseEnvInt("ELECTRON_LOAD_DELAY_MS", 10000);
const STARTUP_TIMEOUT_MS = parseEnvInt("NODECG_STARTUP_TIMEOUT_MS", 30000);
const USE_SYSTEM_NODE = (process.env.NODECG_USE_SYSTEM_NODE ?? "false").toLowerCase() === "true";
const NODE_BINARY = process.env.NODECG_NODE_BINARY ?? "node";
const NODECG_KILL_TIMEOUT_MS = Number.parseInt(process.env.NODECG_KILL_TIMEOUT_MS ?? "2500", 10);
const NODECG_KILL_TIMEOUT_MS = parseEnvInt("NODECG_KILL_TIMEOUT_MS", 2500);
const isDev = !app.isPackaged;
const rootPath = isDev ? path.resolve(__dirname, "../..") : process.resourcesPath;
@@ -24,7 +24,6 @@ const baseUrl = `http://127.0.0.1:${DEFAULT_NODECG_PORT}`;
let mainWindow: BrowserWindow | null = null;
let loadingWindow: BrowserWindow | null = null;
let nodecgProcess: ChildProcess | null = null;
let lastNodeCGOutput = "";
let stopNodeCGPromise: Promise<void> | null = null;
let isQuitting = false;
@@ -47,9 +46,7 @@ function createMainWindow(): BrowserWindow {
win.setMenuBarVisibility(false);
win.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url).catch((error) => {
log("Error opening external url", url, error);
});
void shell.openExternal(url);
return { action: "deny" };
});
@@ -57,9 +54,7 @@ function createMainWindow(): BrowserWindow {
win.webContents.on("will-navigate", (event, url) => {
if (url !== dashboardUrl) {
event.preventDefault();
shell.openExternal(url).catch((error) => {
log("Error opening navigation url", url, error);
});
void shell.openExternal(url);
}
});
@@ -128,42 +123,6 @@ function validateNodeCGInstall(): void {
].join("\n"),
);
}
}
function enrichNodeCGFailureMessage(baseMessage: string): string {
if (lastNodeCGOutput.includes("Cannot find module 'bindings'")) {
return [
baseMessage,
"",
"Detectado error: falta el módulo 'bindings' en el workspace sqlite legacy.",
"Normalmente pasa cuando dependencias del workspace quedaron incompletas.",
"",
"Solución recomendada:",
" 1) cd lib/nodecg/workspaces/database-adapter-sqlite-legacy",
" 2) npm install",
" 3) npm install bindings --no-save",
" 4) cd ../../../../",
" 5) npm run rebuild:native",
].join("\n");
}
if (lastNodeCGOutput.includes("NODE_MODULE_VERSION")) {
return [
baseMessage,
"",
"Detectado error de módulos nativos compilados para otra versión de Node (NODE_MODULE_VERSION).",
USE_SYSTEM_NODE
? "Estás en modo Node del sistema: asegúrate de lanzar con Node 22 y recompilar dependencias nativas."
: "Estás en modo standalone (Node interno de Electron). Reinstala/rebuild de dependencias con esta versión de Electron.",
"",
"Solución recomendada:",
" npm run rebuild:native",
" npm run rebuild:better-sqlite3:electron",
].join("\n");
}
return baseMessage;
}
function startNodeCG(): ChildProcess {
@@ -189,13 +148,11 @@ function startNodeCG(): ChildProcess {
child.stdout?.on("data", (chunk) => {
const text = String(chunk);
process.stdout.write(text);
lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000);
});
child.stderr?.on("data", (chunk) => {
const text = String(chunk);
process.stderr.write(text);
lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000);
});
log(`NodeCG started with pid=${child.pid} using ${runtimeName}`);
@@ -239,7 +196,6 @@ async function launch(): Promise<void> {
mainWindow = createMainWindow();
loadingWindow = createLoadingWindow();
lastNodeCGOutput = "";
nodecgProcess = startNodeCG();
await waitForNodeCGReady(Date.now());
@@ -248,12 +204,8 @@ async function launch(): Promise<void> {
return;
}
try {
await loadingWindow.loadURL(loadingUrl);
loadingWindow.show();
} catch (error) {
log("No se pudo cargar la ruta de loading del bundle", loadingUrl, error);
}
await loadingWindow.loadURL(loadingUrl);
loadingWindow.show();
const loadingShownAt = Date.now();
@@ -261,23 +213,15 @@ async function launch(): Promise<void> {
return;
}
try {
await mainWindow.loadURL(dashboardUrl);
await mainWindow.loadURL(dashboardUrl);
const remainingLoadingDelay = Math.max(0, LOAD_DELAY_MS - (Date.now() - loadingShownAt));
if (remainingLoadingDelay > 0) {
await sleep(remainingLoadingDelay);
}
mainWindow.show();
} catch (error) {
throw new Error(`No se pudo cargar el dashboard en ${dashboardUrl}. ${String(error)}`);
const remainingLoadingDelay = Math.max(0, LOAD_DELAY_MS - (Date.now() - loadingShownAt));
if (remainingLoadingDelay > 0) {
await sleep(remainingLoadingDelay);
}
if (loadingWindow && !loadingWindow.isDestroyed()) {
loadingWindow.close();
loadingWindow = null;
}
mainWindow.show();
closeLoadingWindow();
}
function killNodeCGProcessTree(pid: number, signal: NodeJS.Signals): boolean {
@@ -356,19 +300,29 @@ function log(...args: unknown[]): void {
console.log("[scoreko-electron]", ...args);
}
function parseEnvInt(name: string, fallback: number): number {
const rawValue = process.env[name];
if (!rawValue) {
return fallback;
}
const parsedValue = Number.parseInt(rawValue, 10);
return Number.isFinite(parsedValue) ? parsedValue : fallback;
}
function closeLoadingWindow(): void {
if (!loadingWindow || loadingWindow.isDestroyed()) {
return;
}
loadingWindow.close();
loadingWindow = null;
}
app.on("ready", () => {
launch().catch(async (error: unknown) => {
console.error("Failed to launch Scoreko wrapper", error);
const detail = enrichNodeCGFailureMessage(error instanceof Error ? error.message : String(error));
await dialog.showMessageBox({
type: "error",
title: "No se pudo iniciar Scoreko",
message: "Fallo al iniciar NodeCG",
detail,
});
closeLoadingWindow();
app.exit(1);
});
});