fix: settle a site approval on the port that carries its teardown (closes #275)
This commit was merged in pull request #289.
This commit is contained in:
@@ -494,13 +494,53 @@ function requestSignApproval(origin, hostname, signParams, approvedFrom) {
|
||||
});
|
||||
}
|
||||
|
||||
// Detect when an approval popup (browser-action) closes without a response.
|
||||
// TX and sign approvals now use windows.create() and are handled by the
|
||||
// windows.onRemoved listener below, but we still handle site-connection
|
||||
// approval disconnects here.
|
||||
// Anything only the extension's own pages may say. A content script speaks
|
||||
// with the page's URL, so this is what separates the popup from the site the
|
||||
// popup is being asked about.
|
||||
function isExtensionSender(sender) {
|
||||
const extUrl = runtime.getURL("");
|
||||
return !!(sender && sender.url && sender.url.startsWith(extUrl));
|
||||
}
|
||||
|
||||
// The approval popup's port: it carries the user's decision on a
|
||||
// site-connection approval, and its disconnect is how that approval learns the
|
||||
// popup closed without one.
|
||||
//
|
||||
// The decision travels this port rather than a one-off runtime.sendMessage()
|
||||
// for exactly one reason: the port is also what the popup's window.close()
|
||||
// disconnects. A message posted on a port is delivered before that port's
|
||||
// disconnect, so approve-then-close settles as an approval no matter how fast
|
||||
// the teardown is. Sent as a one-off message the two crossed on independent
|
||||
// channels with nothing ordering them, and the teardown won every time when
|
||||
// the prompt was driven in a tab: the user approved and the dApp was told they
|
||||
// had refused.
|
||||
//
|
||||
// TX and sign approvals do not decide here. They stay pending across a
|
||||
// disconnect — the user can reopen the toolbar popup — and are rejected by the
|
||||
// windows.onRemoved listener below.
|
||||
runtime.onConnect.addListener((port) => {
|
||||
if (port.name.startsWith("approval:")) {
|
||||
const id = port.name.split(":")[1];
|
||||
if (pendingApprovals[id] && isExtensionSender(port.sender)) {
|
||||
// The extension's own popup is on the other end, so its disconnect
|
||||
// is a trustworthy "closed" and onRemoved below stands down. The
|
||||
// sender check is what keeps that from being an off switch: a
|
||||
// content script that guessed the id and held its port open would
|
||||
// otherwise disable the only settlement path a prompt whose popup
|
||||
// never connected has left, and the dApp would wait forever.
|
||||
pendingApprovals[id].portConnected = true;
|
||||
}
|
||||
port.onMessage.addListener((msg) => {
|
||||
if (!msg || msg.type !== "AUTISTMASK_APPROVAL_DECISION") return;
|
||||
if (!isExtensionSender(port.sender)) return;
|
||||
const approval = pendingApprovals[id];
|
||||
if (!approval || approval.type === "tx" || approval.type === "sign")
|
||||
return;
|
||||
settleApproval(id, {
|
||||
approved: !!msg.approved,
|
||||
remember: !!msg.remember,
|
||||
});
|
||||
});
|
||||
port.onDisconnect.addListener(() => {
|
||||
const approval = pendingApprovals[id];
|
||||
if (approval) {
|
||||
@@ -510,7 +550,6 @@ runtime.onConnect.addListener((port) => {
|
||||
}
|
||||
settleApproval(id, { approved: false, remember: false });
|
||||
}
|
||||
resetPopupUrl();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1074,10 +1113,21 @@ startBackgroundJobs();
|
||||
// outcome to the page — and the window is recorded as gone, so that an attempt
|
||||
// which then fails retryably settles instead of waiting in a window that no
|
||||
// longer exists.
|
||||
//
|
||||
// A site-connection approval whose popup connected its port is not decided
|
||||
// here. That popup approves and closes in the same breath, and this event
|
||||
// races the decision on a channel of its own — the same race the port exists
|
||||
// to end. Its port disconnect says the same thing this event does, in an order
|
||||
// that is defined, so the disconnect is left to say it. The window closing
|
||||
// before any port connected is the one case with nothing else to speak for it,
|
||||
// and is rejected here so the dApp is not left waiting on a window that is
|
||||
// gone.
|
||||
if (windowsNs && windowsNs.onRemoved) {
|
||||
windowsNs.onRemoved.addListener((windowId) => {
|
||||
for (const [id, approval] of Object.entries(pendingApprovals)) {
|
||||
if (approval.windowId !== windowId) continue;
|
||||
const isSite = approval.type !== "tx" && approval.type !== "sign";
|
||||
if (isSite && approval.portConnected) continue;
|
||||
const rejection = abandonedResult(
|
||||
approval,
|
||||
APPROVAL_REJECTED_CODE,
|
||||
@@ -1127,18 +1177,16 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
}
|
||||
|
||||
// Validate that popup-only messages originate from the extension itself.
|
||||
// The site-connection decision is not here: it is a port message, and it
|
||||
// is checked the same way where the port is served.
|
||||
const POPUP_ONLY_TYPES = [
|
||||
"AUTISTMASK_GET_APPROVAL",
|
||||
"AUTISTMASK_APPROVAL_RESPONSE",
|
||||
"AUTISTMASK_TX_RESPONSE",
|
||||
"AUTISTMASK_SIGN_RESPONSE",
|
||||
];
|
||||
if (POPUP_ONLY_TYPES.includes(msg.type)) {
|
||||
const extUrl = runtime.getURL("");
|
||||
if (!sender.url || !sender.url.startsWith(extUrl)) {
|
||||
sendResponse({ error: "Unauthorized sender" });
|
||||
return false;
|
||||
}
|
||||
if (POPUP_ONLY_TYPES.includes(msg.type) && !isExtensionSender(sender)) {
|
||||
sendResponse({ error: "Unauthorized sender" });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.type === "AUTISTMASK_GET_APPROVAL") {
|
||||
@@ -1170,15 +1218,6 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.type === "AUTISTMASK_APPROVAL_RESPONSE") {
|
||||
settleApproval(msg.id, {
|
||||
approved: msg.approved,
|
||||
remember: msg.remember,
|
||||
});
|
||||
resetPopupUrl();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.type === "AUTISTMASK_TX_RESPONSE") {
|
||||
const approval = pendingApprovals[msg.id];
|
||||
if (!approval) return false;
|
||||
|
||||
Reference in New Issue
Block a user