Some checks failed
check / check (push) Has been cancelled
A poll tick that found a receipt called showSuccess() and then fell through to the elapsed check, so on the tick crossing the 60-second deadline the "Transaction Confirmed" screen was immediately replaced by "not confirmed within 60 seconds" — the user is told a confirmed transaction failed. The wait now has an explicit lifecycle. A wait id is bumped by endWait(), which is called on receipt, on timeout, when a new wait starts and when the user navigates away; every timer callback and every post-await continuation checks it, so exactly one outcome can be rendered per wait and no stale timer or in-flight receipt lookup can touch a view it no longer owns. A receipt lookup that throws is treated as "no answer this tick" rather than "no receipt": the poll returns before the deadline check and keeps running, so one transient RPC failure cannot declare a timeout. This matters most on a resumed wait, whose first poll is immediate and may already be past the deadline, where a single error would otherwise be terminal. The wait is also persisted (state.viewData.pendingWait) and "wait-tx" is now restorable: reopening the popup resumes the poll with the elapsed counter and the deadline still measured from the original broadcast, instead of silently abandoning the wait. restoreWait() validates the whole persisted payload — hash, txInfo and a finite numeric broadcastTime — and returns false otherwise, so a malformed payload falls back to the main view instead of throwing out of restoreView() or rendering an unexitable "NaNs" wait. Polling stays in the popup rather than moving to the background, which would depend on setInterval surviving in an MV3 service worker. The 60-second threshold and the timeout copy are unchanged.
283 lines
7.2 KiB
JavaScript
283 lines
7.2 KiB
JavaScript
// AutistMask popup entry point.
|
|
// Loads state, initializes views, triggers first render.
|
|
|
|
const { state, saveState, loadState } = require("../shared/state");
|
|
const { setRuntimeDebug } = require("../shared/log");
|
|
const { refreshPrices } = require("../shared/prices");
|
|
const { refreshBalances } = require("../shared/balances");
|
|
const {
|
|
$,
|
|
showView,
|
|
updateDebugBanner,
|
|
setRenderMain,
|
|
pushCurrentView,
|
|
goBack,
|
|
clearViewStack,
|
|
} = require("./views/helpers");
|
|
const { applyTheme } = require("./theme");
|
|
|
|
const home = require("./views/home");
|
|
const welcome = require("./views/welcome");
|
|
const addWallet = require("./views/addWallet");
|
|
const addressDetail = require("./views/addressDetail");
|
|
const addressToken = require("./views/addressToken");
|
|
const send = require("./views/send");
|
|
const confirmTx = require("./views/confirmTx");
|
|
const txStatus = require("./views/txStatus");
|
|
const transactionDetail = require("./views/transactionDetail");
|
|
const receive = require("./views/receive");
|
|
const addToken = require("./views/addToken");
|
|
const settings = require("./views/settings");
|
|
const settingsAddToken = require("./views/settingsAddToken");
|
|
const approval = require("./views/approval");
|
|
|
|
function renderWalletList() {
|
|
home.render(ctx);
|
|
}
|
|
|
|
let refreshInFlight = false;
|
|
|
|
async function doRefreshAndRender() {
|
|
if (refreshInFlight) return;
|
|
refreshInFlight = true;
|
|
try {
|
|
await Promise.all([
|
|
refreshPrices(),
|
|
refreshBalances(
|
|
state.wallets,
|
|
state.rpcUrl,
|
|
state.blockscoutUrl,
|
|
state.trackedTokens,
|
|
),
|
|
]);
|
|
state.lastBalanceRefresh = Date.now();
|
|
await saveState();
|
|
renderWalletList();
|
|
} finally {
|
|
refreshInFlight = false;
|
|
}
|
|
}
|
|
|
|
const ctx = {
|
|
renderWalletList,
|
|
doRefreshAndRender,
|
|
showAddWalletView: () => {
|
|
pushCurrentView();
|
|
addWallet.show();
|
|
},
|
|
showAddressDetail: () => {
|
|
pushCurrentView();
|
|
addressDetail.show();
|
|
},
|
|
showAddressToken: () => {
|
|
pushCurrentView();
|
|
addressToken.show();
|
|
},
|
|
showAddTokenView: () => {
|
|
pushCurrentView();
|
|
addToken.show();
|
|
},
|
|
showConfirmTx: (txInfo) => {
|
|
pushCurrentView();
|
|
confirmTx.show(txInfo);
|
|
},
|
|
showReceive: () => {
|
|
pushCurrentView();
|
|
receive.show();
|
|
},
|
|
showTransactionDetail: (tx) => {
|
|
pushCurrentView();
|
|
transactionDetail.show(tx);
|
|
},
|
|
showSettingsView: () => {
|
|
pushCurrentView();
|
|
settings.show();
|
|
},
|
|
showSettingsAddTokenView: () => {
|
|
pushCurrentView();
|
|
settingsAddToken.show();
|
|
},
|
|
};
|
|
|
|
// Views that can be fully re-rendered from persisted state.
|
|
// All others fall back to the nearest restorable parent.
|
|
const RESTORABLE_VIEWS = new Set([
|
|
"main",
|
|
"address",
|
|
"address-token",
|
|
"receive",
|
|
"settings",
|
|
"settings-addtoken",
|
|
"confirm-tx",
|
|
"transaction",
|
|
"wait-tx",
|
|
"success-tx",
|
|
"error-tx",
|
|
]);
|
|
|
|
function needsAddress(view) {
|
|
return (
|
|
view === "address" ||
|
|
view === "address-token" ||
|
|
view === "receive" ||
|
|
view === "transaction"
|
|
);
|
|
}
|
|
|
|
function hasValidAddress() {
|
|
return (
|
|
state.selectedWallet !== null &&
|
|
state.selectedAddress !== null &&
|
|
state.wallets[state.selectedWallet] &&
|
|
state.wallets[state.selectedWallet].addresses[state.selectedAddress]
|
|
);
|
|
}
|
|
|
|
function restoreView() {
|
|
const view = state.currentView;
|
|
if (!view || !RESTORABLE_VIEWS.has(view)) {
|
|
return fallbackView();
|
|
}
|
|
|
|
if (needsAddress(view) && !hasValidAddress()) {
|
|
return fallbackView();
|
|
}
|
|
|
|
if (view === "address-token" && !state.selectedToken) {
|
|
return fallbackView();
|
|
}
|
|
|
|
switch (view) {
|
|
case "address":
|
|
addressDetail.show();
|
|
break;
|
|
case "address-token":
|
|
addressToken.show();
|
|
break;
|
|
case "receive":
|
|
receive.show();
|
|
break;
|
|
case "settings":
|
|
settings.show();
|
|
break;
|
|
case "settings-addtoken":
|
|
settingsAddToken.show();
|
|
break;
|
|
case "confirm-tx":
|
|
if (state.viewData && state.viewData.pendingTx) {
|
|
confirmTx.restore();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
case "transaction":
|
|
if (state.viewData && state.viewData.tx) {
|
|
transactionDetail.render();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
case "wait-tx":
|
|
// Resumes the receipt poll from the persisted broadcast time.
|
|
if (!txStatus.restoreWait()) {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
case "success-tx":
|
|
if (state.viewData && state.viewData.hash) {
|
|
txStatus.renderSuccess();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
case "error-tx":
|
|
if (state.viewData && state.viewData.message) {
|
|
txStatus.renderError();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
default:
|
|
fallbackView();
|
|
break;
|
|
}
|
|
}
|
|
|
|
function fallbackView() {
|
|
renderWalletList();
|
|
showView("main");
|
|
}
|
|
|
|
async function init() {
|
|
await loadState();
|
|
applyTheme(state.theme);
|
|
|
|
// Sync runtime debug flag from persisted state before first render
|
|
setRuntimeDebug(state.debugMode);
|
|
|
|
// Create the debug/testnet banner if needed (uses runtime debug state)
|
|
updateDebugBanner();
|
|
|
|
// Auto-default active address
|
|
if (
|
|
state.activeAddress === null &&
|
|
state.wallets.length > 0 &&
|
|
state.wallets[0].addresses.length > 0
|
|
) {
|
|
state.activeAddress = state.wallets[0].addresses[0].address;
|
|
await saveState();
|
|
}
|
|
|
|
// Always init approval and txStatus — they may run in the approval popup window
|
|
approval.init(ctx);
|
|
txStatus.init(ctx);
|
|
|
|
// Check for approval mode
|
|
const params = new URLSearchParams(window.location.search);
|
|
const approvalId = params.get("approval");
|
|
if (approvalId) {
|
|
approval.show(approvalId);
|
|
showView("approve-site");
|
|
return;
|
|
}
|
|
|
|
$("btn-settings").addEventListener("click", () => {
|
|
if (
|
|
!document
|
|
.getElementById("view-settings")
|
|
.classList.contains("hidden")
|
|
) {
|
|
goBack();
|
|
return;
|
|
}
|
|
pushCurrentView();
|
|
settings.show();
|
|
});
|
|
|
|
setRenderMain(renderWalletList);
|
|
|
|
welcome.init(ctx);
|
|
addWallet.init(ctx);
|
|
home.init(ctx);
|
|
addressDetail.init(ctx);
|
|
addressToken.init(ctx);
|
|
send.init(ctx);
|
|
confirmTx.init(ctx);
|
|
transactionDetail.init(ctx);
|
|
receive.init(ctx);
|
|
addToken.init(ctx);
|
|
settings.init(ctx);
|
|
settingsAddToken.init(ctx);
|
|
|
|
if (!state.hasWallet) {
|
|
showView("welcome");
|
|
} else {
|
|
renderWalletList();
|
|
restoreView();
|
|
doRefreshAndRender();
|
|
setInterval(doRefreshAndRender, 10000);
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", init);
|