Files
AutistMask/src/popup/index.js
user 0ab202e998
All checks were successful
check / check (push) Successful in 13s
fix: implement proper view navigation stack
The popup UI had no navigation stack — all back buttons were hardcoded
to specific destinations. This meant navigating Main → Address →
Transaction → Settings → Back would go to Main instead of Transaction.

Changes:
- Add viewStack array to persisted state (survives popup close/reopen)
- Add pushCurrentView(), goBack(), clearViewStack() helpers in helpers.js
- goBack() pops the stack and shows the previous view; falls back to
  Main if the stack is empty; re-renders the wallet list when returning
  to the Main view
- All ctx.show*() wrappers in index.js now push the current view before
  navigating forward
- All back buttons across every view now use goBack() instead of
  hardcoded destinations
- Direct showView() calls for forward navigation (Send, Export Private
  Key) push before switching
- Stack is cleared on reset-to-root actions: adding a wallet, deleting
  the last wallet
- After wallet deletion with remaining wallets, stack is reset to
  [main] and settings is re-shown
- After transaction completion (Done button), stack is reset to the
  appropriate address context

Closes #134
2026-03-01 13:23:15 -08:00

290 lines
7.4 KiB
JavaScript

// AutistMask popup entry point.
// Loads state, initializes views, triggers first render.
const { DEBUG } = require("../shared/constants");
const {
state,
saveState,
loadState,
currentNetwork,
} = require("../shared/state");
const { refreshPrices } = require("../shared/prices");
const { refreshBalances } = require("../shared/balances");
const {
$,
showView,
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",
"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 "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);
const net = currentNetwork();
if (DEBUG || net.isTestnet) {
const banner = document.createElement("div");
banner.id = "debug-banner";
if (DEBUG && net.isTestnet) {
banner.textContent = "DEBUG / INSECURE [TESTNET]";
} else if (net.isTestnet) {
banner.textContent = "[TESTNET]";
} else {
banner.textContent = "DEBUG / INSECURE";
}
banner.style.cssText =
"background:#c00;color:#fff;text-align:center;font-size:10px;padding:1px 0;font-family:monospace;position:sticky;top:0;z-index:9999;";
document.body.prepend(banner);
}
// 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);