diff --git a/app/main.js b/app/main.js index 4497136..e06812d 100644 --- a/app/main.js +++ b/app/main.js @@ -149,7 +149,7 @@ async function startServer() { const target = `http://127.0.0.1:${port}/`; for (let i = 0; i < 100; i++) { if (exited !== null) throw new Error(`The server exited (${exited}). See ${LOG}.`); - if (await ping(target)) { url = target; return; } + if (await ping(target)) { url = target; serverStarted = Date.now(); return; } await new Promise((r) => setTimeout(r, 100)); } if (server === child) server = null; @@ -175,18 +175,27 @@ function stopServer() { if (server) endServer(server); } -function errorPage(message) { +function errorPage(message, title = "Frame Control couldn't start") { const esc = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" }[c])); const html = ` -

Frame Control couldn't start

-

${esc(message)}

Fix it, then choose Frame → Restart Server.

`; +

${esc(title)}

+

${esc(message)}

+

+

Frame → Restart Server does the same.

`; return "data:text/html;charset=utf-8," + encodeURIComponent(html); } +// A server that had been running starts again by itself (something stopped it: a +// signal, a crash). One that stops again within a minute shows the error instead, +// so a server that can't stay up doesn't restart forever. +let serverStarted = 0; function serverDied(why) { url = null; - if (win) win.loadURL(errorPage(`The server stopped unexpectedly (${why}). See ${LOG}.`)); + if (!win) return; + if (Date.now() - serverStarted > 60000) restartServer(); + else win.loadURL(errorPage(`Its server stopped unexpectedly (${why}). See ${LOG}.`, "Frame Control stopped")); } // Restarts that overlap share one: two could each start a server, and the one @@ -263,6 +272,8 @@ function fromUi(e) { } catch { return false; } } +// The error page's Try Again button (there is no server page while it shows). +ipcMain.handle("server:restart", (e) => { if (win && e.sender === win.webContents && !url) restartServer(); }); ipcMain.handle("clipboard:read", (e) => fromUi(e) ? clipboard.readText() : ""); ipcMain.handle("connection:setup", (e) => { if (fromUi(e)) setUpConnection(); }); ipcMain.on("keys:capture", (e, on) => { if (fromUi(e)) win.webContents.setIgnoreMenuShortcuts(on === true); }); diff --git a/app/preload.js b/app/preload.js index 8ab5b8f..05d3a6d 100644 --- a/app/preload.js +++ b/app/preload.js @@ -13,6 +13,7 @@ contextBridge.exposeInMainWorld("frameApp", { notify: (message, request) => ipcRenderer.invoke("comfort:notify", message, request), readClipboard: () => ipcRenderer.invoke("clipboard:read"), setUpConnection: () => ipcRenderer.invoke("connection:setup"), + restartServer: () => ipcRenderer.invoke("server:restart"), // the "couldn't start" page's Try Again // The Frame menu's headset switcher: the page tells it the headsets, and hears picks. devicesChanged: (list) => ipcRenderer.send("devices:changed", list), onUseDevice: (cb) => {