refactor(cleanup): completar fase 5 con renombrados semánticos

This commit is contained in:
Pandipipas
2026-02-21 18:55:30 +01:00
parent 8047c99946
commit 710fea38c0
7 changed files with 114 additions and 113 deletions
+4 -4
View File
@@ -4,8 +4,8 @@ export type AppRuntimeConfig = {
iconPathOverride?: string;
nodecgPort: string;
bundleName: string;
dashboardRoute: string;
loadingRoute: string;
mainDashboardRoute: string;
loadingDashboardRoute: string;
loadDelayMs: number;
startupTimeoutMs: number;
nodecgKillTimeoutMs: number;
@@ -21,8 +21,8 @@ export function getRuntimeConfig(): AppRuntimeConfig {
iconPathOverride: getOptionalEnv("SCOREKO_APP_ICON_PATH"),
nodecgPort: parseEnvPort("NODECG_PORT", "9090"),
bundleName: getEnv("NODECG_BUNDLE_NAME", "scoreko-dev"),
dashboardRoute: getEnv("SCOREKO_DASHBOARD_ROUTE", "dashboard/scoreko-dev/main.html?standalone=true"),
loadingRoute: getEnv("SCOREKO_LOADING_ROUTE", "dashboard/loading/main.html?standalone=true"),
mainDashboardRoute: getEnv("SCOREKO_DASHBOARD_ROUTE", "dashboard/scoreko-dev/main.html?standalone=true"),
loadingDashboardRoute: getEnv("SCOREKO_LOADING_ROUTE", "dashboard/loading/main.html?standalone=true"),
loadDelayMs: parseEnvIntInRange("ELECTRON_LOAD_DELAY_MS", 10000, 0, 600000),
startupTimeoutMs: parseEnvIntInRange("NODECG_STARTUP_TIMEOUT_MS", 30000, 1000, 600000),
nodecgKillTimeoutMs: parseEnvIntInRange("NODECG_KILL_TIMEOUT_MS", 2500, 0, 120000),
+23 -23
View File
@@ -7,20 +7,20 @@ import { createNodecgProcessManager } from "./nodecg/process-manager";
import { getRemainingDelayMs } from "./utils/timing";
import { createLoadingWindow, createMainWindow } from "./windows/window-factory";
const runtimeConfig = getRuntimeConfig();
const appConfig = getRuntimeConfig();
const isDev = !app.isPackaged;
const rootPath = isDev ? path.resolve(__dirname, "../..") : process.resourcesPath;
const nodecgPath = path.resolve(rootPath, "lib", "nodecg");
const dashboardUrl = `http://localhost:${runtimeConfig.nodecgPort}/bundles/${runtimeConfig.bundleName}/${runtimeConfig.dashboardRoute}`;
const loadingUrl = `http://localhost:${runtimeConfig.nodecgPort}/bundles/${runtimeConfig.bundleName}/${runtimeConfig.loadingRoute}`;
const baseUrl = `http://127.0.0.1:${runtimeConfig.nodecgPort}`;
const nodecgRootPath = path.resolve(rootPath, "lib", "nodecg");
const mainDashboardUrl = `http://localhost:${appConfig.nodecgPort}/bundles/${appConfig.bundleName}/${appConfig.mainDashboardRoute}`;
const loadingDashboardUrl = `http://localhost:${appConfig.nodecgPort}/bundles/${appConfig.bundleName}/${appConfig.loadingDashboardRoute}`;
const nodecgBaseUrl = `http://127.0.0.1:${appConfig.nodecgPort}`;
const nodecgManager = createNodecgProcessManager({
isDev,
nodecgPath,
baseUrl,
runtimeConfig,
nodecgRootPath,
nodecgBaseUrl,
appConfig,
log,
});
@@ -30,19 +30,19 @@ let mainWindow: BrowserWindow | null = null;
let loadingWindow: BrowserWindow | null = null;
let shutdownState: AppShutdownState = "running";
async function launch(): Promise<void> {
mainWindow = createMainWindow({ runtimeConfig, rootPath, dashboardUrl });
loadingWindow = createLoadingWindow({ runtimeConfig, rootPath });
async function launchApplication(): Promise<void> {
mainWindow = createMainWindow({ appConfig, rootPath, mainDashboardUrl });
loadingWindow = createLoadingWindow({ appConfig, rootPath });
nodecgManager.startNodeCG();
nodecgManager.startNodecgProcess();
await nodecgManager.waitForNodeCGReady(Date.now());
await nodecgManager.waitForNodecgReady(Date.now());
if (!loadingWindow || loadingWindow.isDestroyed()) {
return;
}
await loadingWindow.loadURL(loadingUrl);
await loadingWindow.loadURL(loadingDashboardUrl);
loadingWindow.show();
const loadingShownAt = Date.now();
@@ -51,9 +51,9 @@ async function launch(): Promise<void> {
return;
}
await mainWindow.loadURL(dashboardUrl);
await mainWindow.loadURL(mainDashboardUrl);
const remainingLoadingDelay = getRemainingDelayMs(runtimeConfig.loadDelayMs, loadingShownAt);
const remainingLoadingDelay = getRemainingDelayMs(appConfig.loadDelayMs, loadingShownAt);
if (remainingLoadingDelay > 0) {
await sleep(remainingLoadingDelay);
}
@@ -83,24 +83,24 @@ function stopNodecgGracefully(): Promise<void> {
}
if (shutdownState === "stopping") {
return nodecgManager.stopNodeCG();
return nodecgManager.stopNodecgProcessGracefully();
}
shutdownState = "stopping";
return nodecgManager.stopNodeCG().finally(() => {
return nodecgManager.stopNodecgProcessGracefully().finally(() => {
shutdownState = "stopped";
});
}
app.on("ready", () => {
app.setName(runtimeConfig.title);
app.setName(appConfig.title);
if (process.platform === "win32") {
app.setAppUserModelId(runtimeConfig.userModelId);
app.setAppUserModelId(appConfig.userModelId);
}
launch().catch((error: unknown) => {
launchApplication().catch((error: unknown) => {
showFatalError("No se pudo iniciar Scoreko.", error);
closeLoadingWindow();
app.exit(1);
@@ -109,8 +109,8 @@ app.on("ready", () => {
app.on("activate", async () => {
if (BrowserWindow.getAllWindows().length === 0) {
mainWindow = createMainWindow({ runtimeConfig, rootPath, dashboardUrl });
await mainWindow.loadURL(dashboardUrl);
mainWindow = createMainWindow({ appConfig, rootPath, mainDashboardUrl });
await mainWindow.loadURL(mainDashboardUrl);
mainWindow.show();
}
});
+39 -38
View File
@@ -7,9 +7,9 @@ import { NODE_RUNTIME_NAME } from "../constants";
type NodecgProcessManagerConfig = {
isDev: boolean;
nodecgPath: string;
baseUrl: string;
runtimeConfig: AppRuntimeConfig;
nodecgRootPath: string;
nodecgBaseUrl: string;
appConfig: AppRuntimeConfig;
log: (...args: unknown[]) => void;
deps?: Partial<NodecgProcessManagerDeps>;
};
@@ -28,35 +28,35 @@ type NodecgProcessManagerDeps = {
};
export type NodecgProcessManager = {
startNodeCG: () => ChildProcess;
waitForNodeCGReady: (startTime: number) => Promise<void>;
stopNodeCG: () => Promise<void>;
startNodecgProcess: () => ChildProcess;
waitForNodecgReady: (startTime: number) => Promise<void>;
stopNodecgProcessGracefully: () => Promise<void>;
getProcess: () => ChildProcess | null;
};
export function createNodecgProcessManager({
isDev,
nodecgPath,
baseUrl,
runtimeConfig,
nodecgRootPath,
nodecgBaseUrl,
appConfig,
log,
deps,
}: NodecgProcessManagerConfig): NodecgProcessManager {
const resolvedDeps = resolveDeps(deps);
let nodecgProcess: ChildProcess | null = null;
let stopNodeCGPromise: Promise<void> | null = null;
let stopNodecgPromise: Promise<void> | null = null;
const startNodeCG = (): ChildProcess => {
validateNodeCGInstall(nodecgPath, runtimeConfig.bundleName, resolvedDeps.pathExists);
const startNodecgProcess = (): ChildProcess => {
validateNodecgInstall(nodecgRootPath, appConfig.bundleName, resolvedDeps.pathExists);
const indexPath = path.join(nodecgPath, "index.js");
const indexPath = path.join(nodecgRootPath, "index.js");
const child = resolvedDeps.spawnProcess(resolvedDeps.execPath, [indexPath], {
cwd: nodecgPath,
cwd: nodecgRootPath,
env: {
...resolvedDeps.env,
NODE_ENV: isDev ? "development" : "production",
NODECG_PORT: runtimeConfig.nodecgPort,
NODECG_PORT: appConfig.nodecgPort,
ELECTRON_RUN_AS_NODE: "1",
},
stdio: ["ignore", "pipe", "pipe"],
@@ -83,14 +83,14 @@ export function createNodecgProcessManager({
return child;
};
const waitForNodeCGReady = async (startTime: number): Promise<void> => {
while (Date.now() - startTime < runtimeConfig.startupTimeoutMs) {
const waitForNodecgReady = async (startTime: number): Promise<void> => {
while (Date.now() - startTime < appConfig.startupTimeoutMs) {
if (!nodecgProcess) {
throw new Error("NodeCG terminó antes de estar listo.");
}
try {
const response = await resolvedDeps.fetchUrl(baseUrl, { method: "GET" });
const response = await resolvedDeps.fetchUrl(nodecgBaseUrl, { method: "GET" });
if (response.ok || response.status === 404) {
return;
}
@@ -101,12 +101,12 @@ export function createNodecgProcessManager({
await sleep(500, resolvedDeps.setTimer);
}
throw new Error(`Timeout esperando NodeCG en ${baseUrl} (${runtimeConfig.startupTimeoutMs}ms).`);
throw new Error(`Timeout esperando NodeCG en ${nodecgBaseUrl} (${appConfig.startupTimeoutMs}ms).`);
};
const stopNodeCG = (): Promise<void> => {
if (stopNodeCGPromise) {
return stopNodeCGPromise;
const stopNodecgProcessGracefully = (): Promise<void> => {
if (stopNodecgPromise) {
return stopNodecgPromise;
}
if (!nodecgProcess || nodecgProcess.killed) {
@@ -122,14 +122,15 @@ export function createNodecgProcessManager({
}
log(`Stopping NodeCG pid=${pid}`);
killNodeCGProcessTree(pid, "SIGTERM", log, resolvedDeps);
killNodecgProcessTree(pid, "SIGTERM", log, resolvedDeps);
stopNodeCGPromise = new Promise((resolve) => {
stopNodecgPromise = new Promise((resolve) => {
const complete = () => {
if (nodecgProcess === processToStop) {
nodecgProcess = null;
}
stopNodeCGPromise = null;
stopNodecgPromise = null;
resolve();
};
@@ -140,18 +141,18 @@ export function createNodecgProcessManager({
resolvedDeps.setTimer(() => {
if (processToStop.exitCode === null && processToStop.signalCode === null) {
log(`NodeCG did not exit after SIGTERM, forcing SIGKILL pid=${pid}`);
killNodeCGProcessTree(pid, "SIGKILL", log, resolvedDeps);
killNodecgProcessTree(pid, "SIGKILL", log, resolvedDeps);
}
}, Math.max(0, runtimeConfig.nodecgKillTimeoutMs));
}, Math.max(0, appConfig.nodecgKillTimeoutMs));
});
return stopNodeCGPromise;
return stopNodecgPromise;
};
return {
startNodeCG,
waitForNodeCGReady,
stopNodeCG,
startNodecgProcess,
waitForNodecgReady,
stopNodecgProcessGracefully,
getProcess: () => nodecgProcess,
};
}
@@ -171,13 +172,13 @@ function resolveDeps(deps?: Partial<NodecgProcessManagerDeps>): NodecgProcessMan
};
}
function validateNodeCGInstall(nodecgPath: string, bundleName: string, pathExists: (candidatePath: string) => boolean): void {
const indexPath = path.join(nodecgPath, "index.js");
const nodecgBootstrapPath = path.join(nodecgPath, "node_modules", "nodecg", "dist", "server", "bootstrap.js");
const bundlePath = path.join(nodecgPath, "bundles", bundleName);
function validateNodecgInstall(nodecgRootPath: string, bundleName: string, pathExists: (candidatePath: string) => boolean): void {
const indexPath = path.join(nodecgRootPath, "index.js");
const nodecgBootstrapPath = path.join(nodecgRootPath, "node_modules", "nodecg", "dist", "server", "bootstrap.js");
const bundlePath = path.join(nodecgRootPath, "bundles", bundleName);
if (!pathExists(nodecgPath)) {
throw new Error(`No existe la carpeta NodeCG: ${nodecgPath}`);
if (!pathExists(nodecgRootPath)) {
throw new Error(`No existe la carpeta NodeCG: ${nodecgRootPath}`);
}
if (!pathExists(indexPath)) {
@@ -206,7 +207,7 @@ function validateNodeCGInstall(nodecgPath: string, bundleName: string, pathExist
}
}
function killNodeCGProcessTree(
function killNodecgProcessTree(
pid: number,
signal: NodeJS.Signals,
log: (...args: unknown[]) => void,
+2 -2
View File
@@ -4,12 +4,12 @@ import path from "node:path";
import { AppRuntimeConfig } from "../config/runtime-config";
export function resolveAppIconPath(
runtimeConfig: AppRuntimeConfig,
appConfig: AppRuntimeConfig,
rootPath: string,
pathExists: (candidatePath: string) => boolean = fs.existsSync,
): string | undefined {
const iconCandidates = [
runtimeConfig.iconPathOverride,
appConfig.iconPathOverride,
path.join(rootPath, "static", "icons", "icon.ico"),
path.join(rootPath, "static", "icons", "icon.png"),
path.join(rootPath, "static", "icon.ico"),
+11 -11
View File
@@ -5,13 +5,13 @@ import { resolveAppIconPath } from "./icon-path";
import { shouldAllowInternalNavigation, shouldOpenExternalNavigation } from "./navigation-security";
type WindowFactoryDependencies = {
runtimeConfig: AppRuntimeConfig;
appConfig: AppRuntimeConfig;
rootPath: string;
dashboardUrl: string;
mainDashboardUrl: string;
};
export function createMainWindow({ runtimeConfig, rootPath, dashboardUrl }: WindowFactoryDependencies): BrowserWindow {
const windowOptions = createWindowOptions({ runtimeConfig, rootPath, isLoadingWindow: false });
export function createMainWindow({ appConfig, rootPath, mainDashboardUrl }: WindowFactoryDependencies): BrowserWindow {
const windowOptions = createWindowOptions({ appConfig, rootPath, isLoadingWindow: false });
const window = new BrowserWindow(windowOptions);
window.setMenuBarVisibility(false);
@@ -25,7 +25,7 @@ export function createMainWindow({ runtimeConfig, rootPath, dashboardUrl }: Wind
});
window.webContents.on("will-navigate", (event, url) => {
if (shouldAllowInternalNavigation(url, dashboardUrl)) {
if (shouldAllowInternalNavigation(url, mainDashboardUrl)) {
return;
}
@@ -43,8 +43,8 @@ export function createMainWindow({ runtimeConfig, rootPath, dashboardUrl }: Wind
return window;
}
export function createLoadingWindow({ runtimeConfig, rootPath }: Omit<WindowFactoryDependencies, "dashboardUrl">): BrowserWindow {
const window = new BrowserWindow(createWindowOptions({ runtimeConfig, rootPath, isLoadingWindow: true }));
export function createLoadingWindow({ appConfig, rootPath }: Omit<WindowFactoryDependencies, "mainDashboardUrl">): BrowserWindow {
const window = new BrowserWindow(createWindowOptions({ appConfig, rootPath, isLoadingWindow: true }));
window.on("page-title-updated", (event) => {
event.preventDefault();
@@ -54,19 +54,19 @@ export function createLoadingWindow({ runtimeConfig, rootPath }: Omit<WindowFact
}
function createWindowOptions({
runtimeConfig,
appConfig,
rootPath,
isLoadingWindow,
}: {
runtimeConfig: AppRuntimeConfig;
appConfig: AppRuntimeConfig;
rootPath: string;
isLoadingWindow: boolean;
}): BrowserWindowConstructorOptions {
const iconPath = resolveAppIconPath(runtimeConfig, rootPath);
const iconPath = resolveAppIconPath(appConfig, rootPath);
const baseOptions: BrowserWindowConstructorOptions = {
show: false,
title: runtimeConfig.title,
title: appConfig.title,
...(iconPath ? { icon: iconPath } : {}),
backgroundColor: DEFAULT_WINDOW_BACKGROUND,
webPreferences: {