fix: the private key export screen opens again in the same session (closes #460)
show() found the address line through the element inside it, then replaced the line's contents with renderAddressHtml(), which deleted that element, so the next show() in the same popup session threw before it navigated. The line now carries the export-privkey-address id itself and is looked up by it. No other view that renders an address finds its container through a child. The jest DOM stub now takes an element out of the document when its parent's contents are replaced, and a new test opens the screen twice. The #253 e2e case no longer reopens the popup before its second open. Model: opus-5-5
This commit is contained in:
@@ -45,6 +45,15 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-06: The private key export screen opens again in the same popup
|
||||||
|
session ([#460](https://git.eeqj.de/sneak/AutistMask/issues/460)). `show()`
|
||||||
|
found the address line through the element inside it, which its own rendering
|
||||||
|
replaced, so the second open threw before it navigated. The line now carries
|
||||||
|
the `export-privkey-address` id itself. `tests/exportPrivkey.test.js` opens
|
||||||
|
the screen twice, its DOM stub now takes an element out of the document when
|
||||||
|
its parent's contents are replaced, and the `#253` e2e case no longer reopens
|
||||||
|
the popup before its second open.
|
||||||
|
|
||||||
- 2026-10-06: The canonical files are re-vendored from `sneak/prompts` at
|
- 2026-10-06: The canonical files are re-vendored from `sneak/prompts` at
|
||||||
`dd4027b` ([#472](https://git.eeqj.de/sneak/AutistMask/issues/472)). The
|
`dd4027b` ([#472](https://git.eeqj.de/sneak/AutistMask/issues/472)). The
|
||||||
`Dockerfile` has separate `lint` and `test` phases, and its last stage depends
|
`Dockerfile` has separate `lint` and `test` phases, and its last stage depends
|
||||||
|
|||||||
@@ -391,14 +391,7 @@
|
|||||||
></div>
|
></div>
|
||||||
<h2 class="font-bold mb-1">Export Private Key</h2>
|
<h2 class="font-bold mb-1">Export Private Key</h2>
|
||||||
<p class="text-xs mb-1" id="export-privkey-title"></p>
|
<p class="text-xs mb-1" id="export-privkey-title"></p>
|
||||||
<div class="text-xs mb-3">
|
<div id="export-privkey-address" class="text-xs mb-3"></div>
|
||||||
<span id="export-privkey-dot"></span>
|
|
||||||
<span
|
|
||||||
id="export-privkey-address"
|
|
||||||
class="cursor-pointer"
|
|
||||||
title="Click to copy"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs mb-3 text-muted">
|
<p class="text-xs mb-3 text-muted">
|
||||||
Warning: anyone with this private key can access and
|
Warning: anyone with this private key can access and
|
||||||
transfer all funds from this address. Never share it.
|
transfer all funds from this address. Never share it.
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ function show(walletIdx, addrIdx) {
|
|||||||
|
|
||||||
$("export-privkey-title").textContent =
|
$("export-privkey-title").textContent =
|
||||||
wallet.name + " — Address " + (addrIdx + 1);
|
wallet.name + " — Address " + (addrIdx + 1);
|
||||||
const addrContainer = $("export-privkey-dot").parentElement;
|
const addrContainer = $("export-privkey-address");
|
||||||
addrContainer.innerHTML = renderAddressHtml(addr.address);
|
addrContainer.innerHTML = renderAddressHtml(addr.address);
|
||||||
attachCopyHandlers(addrContainer);
|
attachCopyHandlers(addrContainer);
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -1104,10 +1104,8 @@ test("leaving by the settings gear wipes the private key (#253)", async (env) =>
|
|||||||
test("leaving while the decrypt is in flight reveals no private key (#253)", async (env) => {
|
test("leaving while the decrypt is in flight reveals no private key (#253)", async (env) => {
|
||||||
try {
|
try {
|
||||||
await leavePrivkeyScreen(env.page);
|
await leavePrivkeyScreen(env.page);
|
||||||
// The export screen cannot yet be opened twice in one popup session
|
// The second open in this popup session
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/460), so this second
|
// (https://git.eeqj.de/sneak/AutistMask/issues/460).
|
||||||
// open gets a fresh one.
|
|
||||||
await reopenPopup(env, "main");
|
|
||||||
await openPrivkeyScreen(env.page);
|
await openPrivkeyScreen(env.page);
|
||||||
await env.page.fill("#export-privkey-password", PASSWORD);
|
await env.page.fill("#export-privkey-password", PASSWORD);
|
||||||
const inFlight = await env.page.evaluate(() => {
|
const inFlight = await env.page.evaluate(() => {
|
||||||
|
|||||||
@@ -58,7 +58,20 @@ function makeElement(id, withParent) {
|
|||||||
remove: () => {},
|
remove: () => {},
|
||||||
querySelectorAll: () => [],
|
querySelectorAll: () => [],
|
||||||
};
|
};
|
||||||
el.parentElement = withParent ? makeElement(id + "-parent", false) : null;
|
el.parentElement = null;
|
||||||
|
if (withParent) {
|
||||||
|
// As in a browser, replacing the parent's contents takes this
|
||||||
|
// element out of the document: getElementById no longer finds it.
|
||||||
|
el.parentElement = makeElement(id + "-parent", false);
|
||||||
|
let html = "";
|
||||||
|
Object.defineProperty(el.parentElement, "innerHTML", {
|
||||||
|
get: () => html,
|
||||||
|
set: (value) => {
|
||||||
|
html = value;
|
||||||
|
el.removed = true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +83,8 @@ function makeDocument() {
|
|||||||
// is the state a non-debug, non-testnet popup is in.
|
// is the state a non-debug, non-testnet popup is in.
|
||||||
if (id === "debug-banner") return null;
|
if (id === "debug-banner") return null;
|
||||||
if (!els.has(id)) els.set(id, makeElement(id, true));
|
if (!els.has(id)) els.set(id, makeElement(id, true));
|
||||||
return els.get(id);
|
const el = els.get(id);
|
||||||
|
return el.removed ? null : el;
|
||||||
},
|
},
|
||||||
createElement: () => makeElement("created", false),
|
createElement: () => makeElement("created", false),
|
||||||
addEventListener: () => {},
|
addEventListener: () => {},
|
||||||
@@ -300,6 +314,28 @@ describe("leaving the screen after the key is on it", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("opening the screen again in the same popup session", () => {
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/460: show() found the
|
||||||
|
// address line through an element inside it, which its own rendering
|
||||||
|
// deleted, so the second open threw before it navigated.
|
||||||
|
test("shows it for the address chosen the second time", async () => {
|
||||||
|
const { state, exportPrivkey } = load();
|
||||||
|
exportPrivkey.show(0, 0);
|
||||||
|
await click("btn-export-privkey-back");
|
||||||
|
expect(state.currentView).toBe("address");
|
||||||
|
|
||||||
|
exportPrivkey.show(0, 1);
|
||||||
|
|
||||||
|
expect(state.currentView).toBe(VIEW);
|
||||||
|
expect(node("export-privkey-title").textContent).toBe(
|
||||||
|
"Wallet 1 — Address 2",
|
||||||
|
);
|
||||||
|
expect(node("export-privkey-address").innerHTML).toContain(
|
||||||
|
"0x" + "22".repeat(20),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("views the popup may reopen onto", () => {
|
describe("views the popup may reopen onto", () => {
|
||||||
// Restoring onto this screen would put a private key on display with no
|
// Restoring onto this screen would put a private key on display with no
|
||||||
// password prompt in front of it, on a popup reopened by accident.
|
// password prompt in front of it, on a popup reopened by accident.
|
||||||
|
|||||||
Reference in New Issue
Block a user