import { app, BrowserWindow, dialog } from 'electron'; import { spawn } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; import { get } from 'node:http'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const bundleRoot = resolve(__dirname, '..'); const nodecgRoot = process.env.NODECG_ROOT ? resolve(bundleRoot, process.env.NODECG_ROOT) : resolve(bundleRoot, '..', '..'); const startUrl = process.env.ELECTRON_START_URL ?? 'http://localhost:9090/bundles/scoreko-dev/dashboard/example/main.html?standalone=true'; const nodecgPort = Number(process.env.NODECG_PORT ?? 9090); const nodeBinary = process.env.NODE_BINARY ?? 'node'; const useElectronNodeForNodeCG = process.env.NODECG_USE_ELECTRON_NODE === '1'; /** @type {import('node:child_process').ChildProcess | undefined} */ let nodecgProcess; function waitForServer(url, timeoutMs = 30_000) { const started = Date.now(); return new Promise((resolvePromise, rejectPromise) => { const attempt = () => { const request = get(url, (response) => { response.resume(); if (response.statusCode && response.statusCode >= 200 && response.statusCode < 500) { resolvePromise(); return; } if (Date.now() - started > timeoutMs) { rejectPromise(new Error('NodeCG no respondió a tiempo.')); return; } setTimeout(attempt, 500); }); request.on('error', () => { if (Date.now() - started > timeoutMs) { rejectPromise(new Error('No fue posible conectar a NodeCG.')); return; } setTimeout(attempt, 500); }); }; attempt(); }); } function startNodeCG() { const runtimeBinary = useElectronNodeForNodeCG ? process.execPath : nodeBinary; nodecgProcess = spawn(runtimeBinary, ['index.js'], { cwd: nodecgRoot, stdio: 'inherit', env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? 'production', PORT: String(nodecgPort) } }); nodecgProcess.on('exit', (code) => { if (!app.isQuiting) { dialog.showErrorBox( 'NodeCG finalizado', `El proceso de NodeCG terminó inesperadamente con código ${code ?? 'desconocido'}.` ); app.quit(); } }); nodecgProcess.on('error', (error) => { dialog.showErrorBox( 'No se pudo iniciar NodeCG', `No se pudo ejecutar \"${runtimeBinary}\". ${useElectronNodeForNodeCG ? 'Desactivá NODECG_USE_ELECTRON_NODE o revisá Electron.' : 'Definí NODE_BINARY con la ruta de Node.js.'}\n\nDetalle: ${error.message}` ); app.quit(); }); } async function createWindow() { const win = new BrowserWindow({ width: 1920, height: 1080, autoHideMenuBar: true, backgroundColor: '#000000', webPreferences: { contextIsolation: true, sandbox: true } }); await win.loadURL(startUrl); } app.on('before-quit', () => { app.isQuiting = true; if (nodecgProcess && !nodecgProcess.killed) { nodecgProcess.kill('SIGTERM'); } }); app.whenReady().then(async () => { startNodeCG(); try { await waitForServer(`http://127.0.0.1:${nodecgPort}`); await createWindow(); } catch (error) { dialog.showErrorBox('No se pudo iniciar', error instanceof Error ? error.message : String(error)); app.quit(); } }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });