diff --git a/bundled-addons/aegis/addon.json b/bundled-addons/aegis/addon.json index ac3ccce7..37c555c5 100644 --- a/bundled-addons/aegis/addon.json +++ b/bundled-addons/aegis/addon.json @@ -1,7 +1,7 @@ { "id": "aegis", "name": "Aegis Wallet", - "version": "0.8.5", + "version": "0.8.6", "category": "plugin", "description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.", "author": "Silent Mode", diff --git a/bundled-addons/aegis/panel.js b/bundled-addons/aegis/panel.js index 38b5b55e..8feed09c 100644 --- a/bundled-addons/aegis/panel.js +++ b/bundled-addons/aegis/panel.js @@ -831,6 +831,68 @@ function fillPicker() { if (impKs) impKs.addEventListener("click", (e) => { e.stopPropagation(); d.hidden = true; openKeystoreImportModal(); }); } +// 0.8.6: in-panel replacement for window.confirm(). The native dialog is +// chrome-owned, so it renders as a Theseus-branded OS box outside the +// sidebar โ€” jarring next to the wallet's own UI, and it can't carry an +// icon or a danger-styled button. Resolves true/false like confirm(), +// so callers just `await` it. +// opts: { title, body, confirmLabel, cancelLabel, danger, icon } +function aegisConfirm(opts) { + const o = opts || {}; + return new Promise((resolve) => { + const overlay = document.createElement("div"); + overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,.55);display:flex;align-items:flex-start;justify-content:center;z-index:100000;padding-top:24px"; + overlay.innerHTML = ` +
+
+ ${o.icon || (o.danger ? "โš " : "๐Ÿ›ก")} +
${esc(o.title || "Are you sure?")}
+
+ ${o.body ? `
${o.body}
` : ""} +
+ ${o.alertOnly ? "" : ``} + +
+
`; + document.body.appendChild(overlay); + let done = false; + const finish = (val) => { + if (done) return; + done = true; + document.removeEventListener("keydown", onKey, true); + try { overlay.remove(); } catch {} + resolve(val); + }; + const onKey = (e) => { + if (e.key === "Escape") { e.stopPropagation(); finish(false); } + else if (e.key === "Enter") { e.stopPropagation(); finish(true); } + }; + document.addEventListener("keydown", onKey, true); + overlay.addEventListener("click", (e) => { if (e.target === overlay) finish(false); }); + overlay.querySelectorAll("[data-ac]").forEach((b) => b.addEventListener("click", (e) => { + e.stopPropagation(); + finish(b.dataset.ac === "yes"); + })); + const yes = overlay.querySelector('[data-ac="yes"]'); + if (yes) yes.focus(); + }); +} + +// Single-button sibling of aegisConfirm, for the error notices that used +// window.alert(). Fire-and-forget: callers don't need the result, so it +// works from sync handlers too. +function aegisAlert(message, opts) { + const o = opts || {}; + return aegisConfirm({ + title: o.title || "Something went wrong", + icon: o.icon || "โš ", + body: esc(String(message == null ? "" : message)), + confirmLabel: o.confirmLabel || "OK", + cancelLabel: null, + alertOnly: true, + }); +} + // Import modal โ€” M.1 UX. Paste mnemonic + path OR WIF, choose network + label // + category. Backend derives cashaddr and stores signer material in // wallet-imports.enc (design ยง3.2). Modal is a plain overlay div injected @@ -893,7 +955,13 @@ function openWalletManageModal(w) { }); if (canRemove) overlay.querySelector("#mwRemove").addEventListener("click", async () => { const msg = overlay.querySelector("#mwMsg"); msg.hidden = true; - if (!confirm(`Remove "${w.label}" from Aegis?\n\nOn-chain funds stay where they are โ€” this only unlinks the wallet from Aegis. Add it back later on the same coin + network to derive the same addresses (${w.kind === "imported" ? "or re-import if this was imported" : "from your vault seed"}).`)) return; + const ok = await aegisConfirm({ + title: `Remove "${w.label}"?`, + danger: true, + confirmLabel: "Remove wallet", + body: `On-chain funds stay exactly where they are โ€” this only unlinks the wallet from Aegis.

You can add it back later on the same coin + network to derive the same addresses ${w.kind === "imported" ? "(or re-import it, since this one was imported)" : "from your vault seed"}.`, + }); + if (!ok) return; try { state = await S.invoke("removeWallet", { id: w.id }); close(); @@ -3360,14 +3428,20 @@ $("gsPinChange") && $("gsPinChange").addEventListener("click", async () => { await handlePinSet(true); }); $("gsPinRemove") && $("gsPinRemove").addEventListener("click", async () => { - if (!confirm("Remove the quick-access PIN? You'll have to type the master password on every unlock again.")) return; + const ok = await aegisConfirm({ + title: "Remove the quick-access PIN?", + danger: true, + confirmLabel: "Remove PIN", + body: "You'll have to type the master password on every unlock again.", + }); + if (!ok) return; try { await S.invoke("pinBlobClear"); // Also disable the send-time PIN policy โ€” it depends on having a PIN. await S.invoke("securitySet", { requirePinForSending: false }); await refreshSecurityState(); renderGeneralSecurity(); - } catch (e) { alert("Could not remove PIN: " + cleanErr(e)); } + } catch (e) { aegisAlert("Could not remove PIN: " + cleanErr(e)); } }); $("gsRequirePin") && $("gsRequirePin").addEventListener("change", async () => { const on = $("gsRequirePin").checked; @@ -3376,7 +3450,7 @@ $("gsRequirePin") && $("gsRequirePin").addEventListener("change", async () => { renderGeneralSecurity(); } catch (e) { $("gsRequirePin").checked = !on; - alert("Could not save setting: " + cleanErr(e)); + aegisAlert("Could not save setting: " + cleanErr(e)); } }); $("gsOpenPasswords") && $("gsOpenPasswords").addEventListener("click", () => { @@ -3408,7 +3482,7 @@ $("gsLockOnClose") && $("gsLockOnClose").addEventListener("change", async () => bindIdleAutoLock(); } catch (e) { $("gsLockOnClose").checked = !on; - alert("Could not save setting: " + cleanErr(e)); + aegisAlert("Could not save setting: " + cleanErr(e)); } }); $("gsIdleMinutes") && $("gsIdleMinutes").addEventListener("change", async () => { @@ -3417,10 +3491,16 @@ $("gsIdleMinutes") && $("gsIdleMinutes").addEventListener("change", async () => sessionState = await S.invoke("sessionConfigSet", { idleMinutes: mins }); renderSessionSettings(); bindIdleAutoLock(); - } catch (e) { alert("Could not save idle timeout: " + cleanErr(e)); } + } catch (e) { aegisAlert("Could not save idle timeout: " + cleanErr(e)); } }); $("gsSignOut") && $("gsSignOut").addEventListener("click", async () => { - if (!confirm("Sign out of Aegis? The vault will re-lock and you'll need the master password (or PIN) to open it again.")) return; + const ok = await aegisConfirm({ + title: "Sign out of Aegis?", + icon: "๐Ÿ”’", + confirmLabel: "Sign out", + body: "The vault will re-lock and you'll need the master password (or PIN) to open it again.", + }); + if (!ok) return; try { state = await S.invoke("vaultLock"); stripView = { mode: "coins", groupKey: null }; @@ -3428,7 +3508,7 @@ $("gsSignOut") && $("gsSignOut").addEventListener("click", async () => { // Session blob was cleared server-side; refresh our cached view. sessionState = await S.invoke("sessionStatus"); renderSessionSettings(); - } catch (e) { alert("Could not sign out: " + cleanErr(e)); } + } catch (e) { aegisAlert("Could not sign out: " + cleanErr(e)); } }); // Setting or changing a PIN needs the master password to encrypt against. @@ -3459,7 +3539,7 @@ async function handlePinSet(replacing) { await refreshSecurityState(); renderGeneralSecurity(); } catch (e) { - alert("Could not save PIN: " + cleanErr(e)); + aegisAlert("Could not save PIN: " + cleanErr(e)); } finally { // Drop the buffered password sooner rather than later โ€” we only kept // it around to enroll a PIN without a re-prompt. @@ -3514,7 +3594,7 @@ function promptMasterPassword({ title, subtitle }) { async function verifyPinInteractively(subtitle) { const remain = await pinLockoutRemainingMs(); if (remain > 0) { - alert(`PIN entry is locked for ${Math.ceil(remain / 60000)} min. Use "Remove" in Settings or wait it out.`); + aegisAlert(`PIN entry is locked for ${Math.ceil(remain / 60000)} min. Use "Remove" in Settings or wait it out.`); return false; } return new Promise((resolve) => { @@ -3731,7 +3811,13 @@ $("renameBtn").addEventListener("click", async () => { }); $("removeBtn").addEventListener("click", async () => { const s = sel(); if (!s || s.isLegacy) return; - if (!confirm(`Remove the wallet "${s.label}"?\n\nThe on-chain address stays; the wallet is unlinked from Aegis. You can add it back later by creating a new wallet on the same coin + network.`)) return; + const ok = await aegisConfirm({ + title: `Remove "${s.label}"?`, + danger: true, + confirmLabel: "Remove wallet", + body: "The on-chain address stays exactly where it is; the wallet is only unlinked from Aegis.

You can add it back later by creating a new wallet on the same coin + network.", + }); + if (!ok) return; try { state = await S.invoke("removeWallet", { id: state.selectedWalletId }); settingsFilled = false; render(); } catch (e) { $("settingsMsg").textContent = cleanErr(e); $("settingsMsg").hidden = false; } });