mirror of
https://github.com/Pandipipas/scoreko-electron-dev.git
synced 2026-06-06 05:32:06 +00:00
refactor startup flow and remove legacy error handling (#14)
This commit is contained in:
@@ -5,11 +5,6 @@ Wrapper de Electron para empaquetar una instalación de NodeCG que incluya el bu
|
|||||||
## Requisitos clave
|
## Requisitos clave
|
||||||
|
|
||||||
- Electron fijado en `39.5.1`.
|
- Electron fijado en `39.5.1`.
|
||||||
- Script requerido para recompilar `better-sqlite3` contra Electron 39.5.1:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"rebuild:better-sqlite3:electron": "npm --prefix ../.. rebuild better-sqlite3 --runtime=electron --target=39.5.1 --dist-url=https://electronjs.org/headers"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Qué hace
|
## Qué hace
|
||||||
|
|
||||||
@@ -40,37 +35,8 @@ scoreko-electron-dev/
|
|||||||
- `npm run pack`: genera app sin instalador.
|
- `npm run pack`: genera app sin instalador.
|
||||||
- `npm run dist`: genera instalador.
|
- `npm run dist`: genera instalador.
|
||||||
- `npm run rebuild:native`: rebuild nativo auxiliar en `lib/nodecg`.
|
- `npm run rebuild:native`: rebuild nativo auxiliar en `lib/nodecg`.
|
||||||
- `npm run rebuild:better-sqlite3:electron`: comando exigido para rebuild de `better-sqlite3`.
|
|
||||||
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Error: `Cannot find module 'bindings'`
|
|
||||||
|
|
||||||
Si aparece en `database-adapter-sqlite-legacy/node_modules/better-sqlite3/lib/database.js`, faltan dependencias del workspace sqlite legacy.
|
|
||||||
|
|
||||||
Ejecuta:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd lib/nodecg/workspaces/database-adapter-sqlite-legacy
|
|
||||||
npm install
|
|
||||||
npm install bindings --no-save
|
|
||||||
cd ../../../../
|
|
||||||
npm run rebuild:native
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error: `NODE_MODULE_VERSION`
|
|
||||||
|
|
||||||
Recompila nativos contra Electron 39.5.1:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run rebuild:native
|
|
||||||
npm run rebuild:better-sqlite3:electron
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Variables de entorno útiles
|
## Variables de entorno útiles
|
||||||
|
|
||||||
- `NODECG_BUNDLE_NAME` (default: `scoreko-dev`)
|
- `NODECG_BUNDLE_NAME` (default: `scoreko-dev`)
|
||||||
- `SCOREKO_DASHBOARD_ROUTE` (default: `dashboard/index.html`)
|
- `SCOREKO_DASHBOARD_ROUTE` (default: `dashboard/example/main.html?standalone=true`)
|
||||||
- `SCOREKO_LOADING_ROUTE` (default: `dashboard/loading.html`)
|
- `SCOREKO_LOADING_ROUTE` (default: `dashboard/loading/main.html?standalone=true`)
|
||||||
|
|||||||
Generated
+3
-3
@@ -8,9 +8,6 @@
|
|||||||
"name": "scoreko-electron-dev",
|
"name": "scoreko-electron-dev",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"source-map-support": "^0.5.21"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/rebuild": "^3.7.1",
|
"@electron/rebuild": "^3.7.1",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.5",
|
||||||
@@ -1495,6 +1492,7 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/builder-util": {
|
"node_modules/builder-util": {
|
||||||
@@ -5092,6 +5090,7 @@
|
|||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -5101,6 +5100,7 @@
|
|||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
|
|||||||
@@ -58,9 +58,6 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"source-map-support": "^0.5.21"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/rebuild": "^3.7.1",
|
"@electron/rebuild": "^3.7.1",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.5",
|
||||||
|
|||||||
+34
-80
@@ -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 { ChildProcess, spawn } from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
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_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_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 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 LOAD_DELAY_MS = parseEnvInt("ELECTRON_LOAD_DELAY_MS", 10000);
|
||||||
const STARTUP_TIMEOUT_MS = Number.parseInt(process.env.NODECG_STARTUP_TIMEOUT_MS ?? "30000", 10);
|
const STARTUP_TIMEOUT_MS = parseEnvInt("NODECG_STARTUP_TIMEOUT_MS", 30000);
|
||||||
const USE_SYSTEM_NODE = (process.env.NODECG_USE_SYSTEM_NODE ?? "false").toLowerCase() === "true";
|
const USE_SYSTEM_NODE = (process.env.NODECG_USE_SYSTEM_NODE ?? "false").toLowerCase() === "true";
|
||||||
const NODE_BINARY = process.env.NODECG_NODE_BINARY ?? "node";
|
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 isDev = !app.isPackaged;
|
||||||
const rootPath = isDev ? path.resolve(__dirname, "../..") : process.resourcesPath;
|
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 mainWindow: BrowserWindow | null = null;
|
||||||
let loadingWindow: BrowserWindow | null = null;
|
let loadingWindow: BrowserWindow | null = null;
|
||||||
let nodecgProcess: ChildProcess | null = null;
|
let nodecgProcess: ChildProcess | null = null;
|
||||||
let lastNodeCGOutput = "";
|
|
||||||
let stopNodeCGPromise: Promise<void> | null = null;
|
let stopNodeCGPromise: Promise<void> | null = null;
|
||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
|
|
||||||
@@ -47,9 +46,7 @@ function createMainWindow(): BrowserWindow {
|
|||||||
win.setMenuBarVisibility(false);
|
win.setMenuBarVisibility(false);
|
||||||
|
|
||||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
shell.openExternal(url).catch((error) => {
|
void shell.openExternal(url);
|
||||||
log("Error opening external url", url, error);
|
|
||||||
});
|
|
||||||
|
|
||||||
return { action: "deny" };
|
return { action: "deny" };
|
||||||
});
|
});
|
||||||
@@ -57,9 +54,7 @@ function createMainWindow(): BrowserWindow {
|
|||||||
win.webContents.on("will-navigate", (event, url) => {
|
win.webContents.on("will-navigate", (event, url) => {
|
||||||
if (url !== dashboardUrl) {
|
if (url !== dashboardUrl) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
shell.openExternal(url).catch((error) => {
|
void shell.openExternal(url);
|
||||||
log("Error opening navigation url", url, error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,42 +123,6 @@ function validateNodeCGInstall(): void {
|
|||||||
].join("\n"),
|
].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 {
|
function startNodeCG(): ChildProcess {
|
||||||
@@ -189,13 +148,11 @@ function startNodeCG(): ChildProcess {
|
|||||||
child.stdout?.on("data", (chunk) => {
|
child.stdout?.on("data", (chunk) => {
|
||||||
const text = String(chunk);
|
const text = String(chunk);
|
||||||
process.stdout.write(text);
|
process.stdout.write(text);
|
||||||
lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
child.stderr?.on("data", (chunk) => {
|
child.stderr?.on("data", (chunk) => {
|
||||||
const text = String(chunk);
|
const text = String(chunk);
|
||||||
process.stderr.write(text);
|
process.stderr.write(text);
|
||||||
lastNodeCGOutput = `${lastNodeCGOutput}${text}`.slice(-20000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log(`NodeCG started with pid=${child.pid} using ${runtimeName}`);
|
log(`NodeCG started with pid=${child.pid} using ${runtimeName}`);
|
||||||
@@ -239,7 +196,6 @@ async function launch(): Promise<void> {
|
|||||||
mainWindow = createMainWindow();
|
mainWindow = createMainWindow();
|
||||||
loadingWindow = createLoadingWindow();
|
loadingWindow = createLoadingWindow();
|
||||||
|
|
||||||
lastNodeCGOutput = "";
|
|
||||||
nodecgProcess = startNodeCG();
|
nodecgProcess = startNodeCG();
|
||||||
|
|
||||||
await waitForNodeCGReady(Date.now());
|
await waitForNodeCGReady(Date.now());
|
||||||
@@ -248,12 +204,8 @@ async function launch(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await loadingWindow.loadURL(loadingUrl);
|
||||||
await loadingWindow.loadURL(loadingUrl);
|
loadingWindow.show();
|
||||||
loadingWindow.show();
|
|
||||||
} catch (error) {
|
|
||||||
log("No se pudo cargar la ruta de loading del bundle", loadingUrl, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadingShownAt = Date.now();
|
const loadingShownAt = Date.now();
|
||||||
|
|
||||||
@@ -261,23 +213,15 @@ async function launch(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await mainWindow.loadURL(dashboardUrl);
|
||||||
await mainWindow.loadURL(dashboardUrl);
|
|
||||||
|
|
||||||
const remainingLoadingDelay = Math.max(0, LOAD_DELAY_MS - (Date.now() - loadingShownAt));
|
const remainingLoadingDelay = Math.max(0, LOAD_DELAY_MS - (Date.now() - loadingShownAt));
|
||||||
if (remainingLoadingDelay > 0) {
|
if (remainingLoadingDelay > 0) {
|
||||||
await sleep(remainingLoadingDelay);
|
await sleep(remainingLoadingDelay);
|
||||||
}
|
|
||||||
|
|
||||||
mainWindow.show();
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`No se pudo cargar el dashboard en ${dashboardUrl}. ${String(error)}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadingWindow && !loadingWindow.isDestroyed()) {
|
mainWindow.show();
|
||||||
loadingWindow.close();
|
closeLoadingWindow();
|
||||||
loadingWindow = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function killNodeCGProcessTree(pid: number, signal: NodeJS.Signals): boolean {
|
function killNodeCGProcessTree(pid: number, signal: NodeJS.Signals): boolean {
|
||||||
@@ -356,19 +300,29 @@ function log(...args: unknown[]): void {
|
|||||||
console.log("[scoreko-electron]", ...args);
|
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", () => {
|
app.on("ready", () => {
|
||||||
launch().catch(async (error: unknown) => {
|
launch().catch(async (error: unknown) => {
|
||||||
console.error("Failed to launch Scoreko wrapper", error);
|
console.error("Failed to launch Scoreko wrapper", error);
|
||||||
|
closeLoadingWindow();
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
app.exit(1);
|
app.exit(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user