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) => {