const { app, BrowserWindow, dialog } = require("electron"); const { spawn } = require("node:child_process"); const fs = require("node:fs"); const http = require("node:http"); const net = require("node:net"); const path = require("node:path"); app.commandLine.appendSwitch("no-sandbox"); app.commandLine.appendSwitch("disable-dev-shm-usage"); let server; function freePort() { return new Promise((resolve, reject) => { const probe = net.createServer(); probe.listen(0, "127.0.0.1", () => { const address = probe.address(); const port = address && typeof address === "object" ? address.port : 0; probe.close(() => resolve(port)); }); probe.on("error", reject); }); } function waitFor(url, timeoutMs) { const started = Date.now(); return new Promise((resolve, reject) => { const tick = () => { const request = http.get(url, (response) => { response.resume(); if (response.statusCode && response.statusCode < 500) resolve(); else retry(); }); request.on("error", retry); }; const retry = () => { if (Date.now() - started > timeoutMs) reject(new Error(`Timed out waiting for ${url}`)); else setTimeout(tick, 250); }; tick(); }); } function stopServer() { if (server && !server.killed) server.kill("SIGTERM"); } async function open() { const resources = process.resourcesPath; const standalone = path.join(resources, "standalone"); const serverJs = path.join(standalone, "server.js"); if (!fs.existsSync(serverJs)) { dialog.showErrorBox( "Recorder fingering editor", "The bundled app is missing from this AppImage.", ); app.quit(); return; } const port = await freePort(); const logPath = path.join(app.getPath("temp"), "recorder-tab-converter.log"); const log = fs.createWriteStream(logPath, { flags: "a" }); server = spawn(process.execPath, [serverJs], { cwd: standalone, env: { ...process.env, ELECTRON_RUN_AS_NODE: "1", PORT: String(port), HOSTNAME: "127.0.0.1", NODE_ENV: "production", }, stdio: ["ignore", "pipe", "pipe"], }); server.stdout.pipe(log); server.stderr.pipe(log); server.on("exit", (code) => { log.write(`\nserver exit ${code}\n`); }); const url = `http://127.0.0.1:${port}/`; try { await waitFor(url, 45_000); } catch (error) { dialog.showErrorBox( "Recorder fingering editor", `${error instanceof Error ? error.message : "The app server did not start."}\nLog: ${logPath}`, ); stopServer(); app.quit(); return; } const window = new BrowserWindow({ width: 1280, height: 880, minWidth: 390, minHeight: 640, show: false, title: "Recorder fingering editor", backgroundColor: "#f3ecdf", autoHideMenuBar: true, webPreferences: { contextIsolation: true, nodeIntegration: false, // Renderer sandbox cannot create shared memory here, so the window // fails with ERR_FAILED. The page is the local UI and has no Node access. sandbox: false, }, }); window.once("ready-to-show", () => window.show()); await window.loadURL(url); } app.whenReady().then(open).catch((error) => { dialog.showErrorBox("Recorder fingering editor", error instanceof Error ? error.message : String(error)); app.quit(); }); app.on("before-quit", stopServer); app.on("window-all-closed", () => { stopServer(); app.quit(); });