fix(theseus): an add-on cannot restart the browser on its own

Aegis relaunches Theseus after staging its own update; seen twice in a
dev instance, the whole browser restarted with no warning, mid-session.
The add-on API's restartApp now asks the user in a native dialog
("Aegis Wallet wants to restart Theseus" — Restart now / Later, Later
is the default) and resolves { restarted, deferred }. Declining loses
nothing: a staged update applies on the next normal launch. The guard
lives in the host, so it covers every Aegis version on the channel and
any future add-on.
This commit is contained in:
Local Dev 2026-09-22 21:59:44 +02:00
parent d625bd25a1
commit 870986de21
2 changed files with 23 additions and 5 deletions

View file

@ -537,12 +537,12 @@ class AddonHost {
staged: (full?.staged || []).find((s) => s.id === manifest.id) || null,
};
},
// Cleanly relaunch Theseus. Used by the plug-in card's "apply
// update" chip to activate a staged build without asking the user
// to hunt for the app menu.
// Ask Theseus to relaunch (the plug-in card's "apply update" chip).
// The host asks the user first and resolves { restarted, deferred }
// — an add-on cannot restart the browser on its own.
restartApp: () => {
if (!this._restartApp) throw new Error("restartApp unavailable (host not wired)");
this._restartApp();
return Promise.resolve(this._restartApp(manifest.name || manifest.id));
},
// Resolves once the browser chrome has painted (immediately if it
// already has). Put expensive dependency loading behind this so it

20
main.js
View file

@ -2063,7 +2063,25 @@ function initAddons() {
return { report: [], skipped: "unexpected-error", staged: [] };
}
},
restartApp: () => { console.log("[restart] requested by an add-on"); try { app.relaunch(); } catch {} app.quit(); },
// An add-on may ask for a relaunch (Aegis does after staging its own
// update) but never gets to perform one: the user is asked first, in a
// native dialog the panel cannot draw over. Declining costs nothing —
// a staged update applies on the next normal launch anyway.
restartApp: async (addonName) => {
const who = String(addonName || "An add-on").slice(0, 60);
console.log(`[restart] ${who} asked to relaunch Theseus`);
const parent = win && !win.isDestroyed() ? win : undefined;
const { response } = await dialog.showMessageBox(parent, {
type: "question", title: "Restart Theseus?",
message: `${who} wants to restart Theseus.`,
detail: "Usually to finish installing its own update. Open tabs are restored after the restart. If you choose Later, the update still applies the next time Theseus starts.",
buttons: ["Restart now", "Later"], defaultId: 1, cancelId: 1, noLink: true,
});
if (response !== 0) { console.log(`[restart] ${who}: user chose Later`); return { restarted: false, deferred: true }; }
try { app.relaunch(); } catch {}
app.quit();
return { restarted: true };
},
// open-tab (addon-file variant): open one of the add-on's OWN files in a
// full tab. The path is joined against the resolved add-on folder and
// rejected if the result escapes it — belt-and-braces with the sanity