chore(aegis): 0.8.6 — in-panel confirm/alert instead of native dialogs
window.confirm/alert render as chrome-owned, Theseus-branded OS boxes outside the sidebar, which breaks the illusion that Aegis is one coherent surface — and they can't carry an icon, a danger-styled button, or formatted copy. Adds aegisConfirm() / aegisAlert(): the same overlay shell the manage and import modals already use, resolving like confirm() so callers just await it. Escape cancels, Enter confirms, click-outside cancels. Swapped at all four confirm sites (remove wallet from the picker, remove wallet from Settings, remove PIN, sign out) and all seven alert sites.
This commit is contained in:
parent
f3fb31e651
commit
30fbe73d21
2 changed files with 98 additions and 12 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 = `
|
||||
<div style="width:min(94vw,360px);background:var(--panel,#12161e);border:1px solid var(--line,#2a2f38);border-radius:10px;padding:14px 14px 12px;box-shadow:0 10px 40px rgba(0,0,0,.4)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||||
<span style="font-size:17px;line-height:1">${o.icon || (o.danger ? "⚠" : "🛡")}</span>
|
||||
<div style="font-weight:600;flex:1">${esc(o.title || "Are you sure?")}</div>
|
||||
</div>
|
||||
${o.body ? `<div class="hint" style="margin-bottom:12px;line-height:1.5">${o.body}</div>` : ""}
|
||||
<div class="actions" style="justify-content:flex-end;gap:6px">
|
||||
${o.alertOnly ? "" : `<button class="btn" data-ac="no">${esc(o.cancelLabel || "Cancel")}</button>`}
|
||||
<button class="btn ${o.danger ? "danger" : "primary"}" data-ac="yes">${esc(o.confirmLabel || "Confirm")}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
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.<br><br>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.<br><br>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; }
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue