fix: a slow answer to an approval still reaches the site (closes #540)
Chrome stopped the background worker about six minutes after a site's request even with the approval window open, and the approval and the site's waiting request went with it. Every approval is now added through addPendingApproval(), which starts a timer in the worker that calls runtime.getPlatformInfo() every 20 seconds while any approval is pending; Chrome counts each call as activity. tests/e2e/slowApproval.js, run by make test-e2e, leaves a transaction and a signature approval open for seven minutes in minimized windows and requires the site to receive both results. It drives Chromium over the DevTools pipe because Playwright keeps DevTools attached to the worker. Its loopback dApp server, shared with the Firefox suite, moves to tests/e2e/dapp.js. Model: opus-5-5
This commit was merged in pull request #553.
This commit is contained in:
+35
-7
@@ -44,6 +44,7 @@ const {
|
||||
|
||||
const {
|
||||
actionApi,
|
||||
getPlatformInfo,
|
||||
runtimeApi,
|
||||
tabsQuery,
|
||||
tabsSendMessage,
|
||||
@@ -67,6 +68,33 @@ const connectedSites = {};
|
||||
// Pending approval requests: { id: { origin, resolve } }
|
||||
const pendingApprovals = {};
|
||||
|
||||
// An approval, and the site's request waiting on it, exist only in this
|
||||
// worker's memory. Left alone, Chrome stopped the worker about six minutes
|
||||
// after the site's request while the user was still deciding, and the approval
|
||||
// was lost with it. Chrome counts an extension call as activity, so while any
|
||||
// approval is pending this worker makes one every 20 seconds, inside Chrome's
|
||||
// 30-second idle limit. It is the worker's own timer, so it runs whether or not
|
||||
// the approval window is visible, and it stops itself once nothing is pending.
|
||||
let keepRunningTimer = null;
|
||||
|
||||
// Add an approval waiting for the user, and start the timer above if it is not
|
||||
// running. This is the ONLY place an approval is added, as settleApproval() is
|
||||
// the only place one is removed, so every kind of approval keeps the worker
|
||||
// running while it waits.
|
||||
function addPendingApproval(approval) {
|
||||
pendingApprovals[approval.id] = approval;
|
||||
if (keepRunningTimer) return;
|
||||
keepRunningTimer = setInterval(() => {
|
||||
if (Object.keys(pendingApprovals).length === 0) {
|
||||
clearInterval(keepRunningTimer);
|
||||
keepRunningTimer = null;
|
||||
return;
|
||||
}
|
||||
// Only the call matters; its answer is not used.
|
||||
getPlatformInfo().catch(() => {});
|
||||
}, 20000);
|
||||
}
|
||||
|
||||
// One transaction approval at a time, wallet-wide.
|
||||
//
|
||||
// The transaction a site asks for is populated before its approval window
|
||||
@@ -514,7 +542,7 @@ async function openApprovalWindow(id) {
|
||||
function requestApproval(origin) {
|
||||
return new Promise((resolve) => {
|
||||
const id = crypto.randomUUID();
|
||||
pendingApprovals[id] = { id, origin, resolve, type: "site" };
|
||||
addPendingApproval({ id, origin, resolve, type: "site" });
|
||||
|
||||
if (actionNs && typeof actionNs.openPopup === "function") {
|
||||
showInToolbarPopup(id);
|
||||
@@ -557,14 +585,14 @@ function showInToolbarPopup(id) {
|
||||
function requestTxApproval(origin, approvedTx, approvedFrom, slot) {
|
||||
return new Promise((resolve) => {
|
||||
const id = crypto.randomUUID();
|
||||
pendingApprovals[id] = {
|
||||
addPendingApproval({
|
||||
id,
|
||||
origin,
|
||||
approvedTx,
|
||||
approvedFrom,
|
||||
resolve,
|
||||
type: "tx",
|
||||
};
|
||||
});
|
||||
if (slot) slot.approvalId = id;
|
||||
|
||||
openApprovalWindow(id);
|
||||
@@ -578,14 +606,14 @@ function requestTxApproval(origin, approvedTx, approvedFrom, slot) {
|
||||
function requestSignApproval(origin, signParams, approvedFrom) {
|
||||
return new Promise((resolve) => {
|
||||
const id = crypto.randomUUID();
|
||||
pendingApprovals[id] = {
|
||||
addPendingApproval({
|
||||
id,
|
||||
origin,
|
||||
signParams,
|
||||
approvedFrom,
|
||||
resolve,
|
||||
type: "sign",
|
||||
};
|
||||
});
|
||||
|
||||
openApprovalWindow(id);
|
||||
});
|
||||
@@ -598,14 +626,14 @@ function requestSignApproval(origin, signParams, approvedFrom) {
|
||||
function requestNetworkApproval(origin, currentNetworkId, requestedNetworkId) {
|
||||
return new Promise((resolve) => {
|
||||
const id = crypto.randomUUID();
|
||||
pendingApprovals[id] = {
|
||||
addPendingApproval({
|
||||
id,
|
||||
origin,
|
||||
currentNetworkId,
|
||||
requestedNetworkId,
|
||||
resolve,
|
||||
type: "network",
|
||||
};
|
||||
});
|
||||
|
||||
openApprovalWindow(id);
|
||||
});
|
||||
|
||||
@@ -145,6 +145,14 @@ function sendMessage(message) {
|
||||
return invoke(runtimeApi(), "sendMessage", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object>} the operating system and processor the browser
|
||||
* runs on.
|
||||
*/
|
||||
function getPlatformInfo() {
|
||||
return invoke(runtimeApi(), "getPlatformInfo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message nobody is expected to answer, and swallow the fact that
|
||||
* nobody did.
|
||||
@@ -262,6 +270,7 @@ module.exports = {
|
||||
actionApi,
|
||||
alarmsApi,
|
||||
extensionApi,
|
||||
getPlatformInfo,
|
||||
hasBrowserNamespace,
|
||||
notify,
|
||||
runtimeApi,
|
||||
|
||||
Reference in New Issue
Block a user