import { app, BrowserWindow } from 'electron'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawn } from 'node:child_process'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const BUNDLE_ROOT = app.isPackaged ? path.join(process.resourcesPath, 'bundle') : path.resolve(__dirname, '..'); const NODECG_PORT = Number.parseInt(process.env.NODECG_PORT ?? '9090', 10); const NODECG_HOST = process.env.NODECG_HOST ?? '127.0.0.1'; const NODECG_URL = `http://${NODECG_HOST}:${NODECG_PORT}`; let nodecgProcess; function getNodecgCliPath() { return app.isPackaged ? path.join(process.resourcesPath, 'app.asar.unpacked', 'node_modules', 'nodecg', 'bin', 'nodecg.js') : path.join(__dirname, 'node_modules', 'nodecg', 'bin', 'nodecg.js'); } function startNodecg() { const nodecgCli = getNodecgCliPath(); nodecgProcess = spawn(process.execPath, [nodecgCli, 'start'], { cwd: BUNDLE_ROOT, env: { ...process.env, ELECTRON_RUN_AS_NODE: '1', NODECG_PORT: String(NODECG_PORT), }, stdio: 'inherit', }); nodecgProcess.on('exit', (code) => { if (code !== 0) { console.error(`[electron] NodeCG process exited with code ${code}`); } if (!app.isQuitting) { app.quit(); } }); } function stopNodecg() { if (!nodecgProcess || nodecgProcess.killed) { return; } app.isQuitting = true; nodecgProcess.kill(); } async function waitForNodecg(retries = 80, delayMs = 250) { for (let attempt = 0; attempt < retries; attempt += 1) { try { const response = await fetch(NODECG_URL, { method: 'HEAD' }); if (response.ok || response.status >= 300) { return; } } catch { // Ignore connection errors while NodeCG boots. } await new Promise((resolve) => { setTimeout(resolve, delayMs); }); } throw new Error(`NodeCG did not start in time at ${NODECG_URL}`); } async function createWindow() { const mainWindow = new BrowserWindow({ width: 1366, height: 768, autoHideMenuBar: true, webPreferences: { contextIsolation: true, sandbox: true, }, }); await mainWindow.loadURL(NODECG_URL); } app.whenReady().then(async () => { startNodecg(); try { await waitForNodecg(); await createWindow(); } catch (error) { console.error('[electron] Failed to start wrapper:', error); app.quit(); } app.on('activate', async () => { if (BrowserWindow.getAllWindows().length === 0) { await createWindow(); } }); }); app.on('before-quit', () => { app.isQuitting = true; stopNodecg(); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });