fix: closing a transaction or signature approval window leaves the request pending (closes #673)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

The browser can close the approval window when the user clicks away from it,
for example to copy the password from a password manager. Closing it used to
reject the request with 4001. Now the request stays pending and the site gets
no answer. Until it is answered, the toolbar popup opens the oldest such
approval. A worker that starts holds no approvals, so it points the toolbar
popup back at the wallet.

Reject now closes its window only after the background has the rejection. Sent
as the window closed, the rejection could be lost, which the old window-close
rejection hid.

Site-connection and network-switch windows still reject the request when
closed.

Model: opus-5-5
This commit is contained in:
2026-10-26 21:18:24 +00:00
parent bf8aa9f5dd
commit df0de00821
9 changed files with 592 additions and 162 deletions
+85
View File
@@ -12,6 +12,7 @@ const {
cleanupPopup,
settle,
unversionedValidProfile,
ADDRESS,
} = require("./support/popupBoot");
afterEach(cleanupPopup);
@@ -174,3 +175,87 @@ test("a connection prompt in the toolbar popup leaves the browser window alone",
expect(windows.updates).toEqual([]);
expect(env.pageErrors).toEqual([]);
});
// A closed transaction or signature window leaves its request pending
// (https://git.eeqj.de/sneak/AutistMask/issues/673). So Reject closes the
// window only once the background has the rejection. A message sent as the
// window closes can be lost, and the request would stay pending although the
// user rejected it.
test.each([
{
kind: "transaction",
view: "approve-tx",
reject: "btn-reject-tx",
type: "AUTISTMASK_TX_RESPONSE",
approval: {
type: "tx",
approvedFrom: ADDRESS,
approvedTx: {
chainId: 1,
to: ADDRESS,
value: "0",
data: "0x",
gasLimit: "21000",
maxFeePerGas: "1000000000",
maxPriorityFeePerGas: "1000000000",
nonce: 0,
},
},
},
{
kind: "signature",
view: "approve-sign",
reject: "btn-reject-sign",
type: "AUTISTMASK_SIGN_RESPONSE",
approval: {
type: "sign",
approvedFrom: ADDRESS,
signParams: {
method: "personal_sign",
message: "0x48656c6c6f",
from: ADDRESS,
},
},
},
])(
"Reject on a $kind approval closes the window only once the background has answered",
async ({ view, reject, type, approval }) => {
let describeApproval = null;
let answerReject = null;
const sent = [];
const env = await bootPopup(unversionedValidProfile(), {
search: "?approval=approval-1",
runtime: {
connect: () => ({ postMessage: () => {} }),
sendMessage: (msg, reply) => {
if (msg.type === "AUTISTMASK_GET_APPROVAL") {
describeApproval = reply;
}
if (msg.type === type) {
sent.push(msg);
answerReject = reply;
}
},
},
});
describeApproval({
...approval,
origin: "https://dapp.example",
isPhishingDomain: false,
});
await settle();
expect(env.visibleViews()).toEqual([view]);
const close = jest.fn();
globalThis.window.close = close;
const clicked = env.click(reject);
await settle();
expect(sent).toEqual([{ type, id: "approval-1", approved: false }]);
expect(close).not.toHaveBeenCalled();
answerReject(null);
await clicked;
expect(close).toHaveBeenCalledTimes(1);
expect(env.pageErrors).toEqual([]);
},
);