203 lines
7.8 KiB
JavaScript
203 lines
7.8 KiB
JavaScript
// State management and extension storage persistence.
|
|
|
|
const { DEFAULT_RPC_URL, DEFAULT_BLOCKSCOUT_URL } = require("./constants");
|
|
const { networkById } = require("./networks");
|
|
// Dependency-free constant module; safe to pull into a background bundle.
|
|
const { RESTORABLE_VIEWS } = require("../popup/restorableViews");
|
|
|
|
const { storageGet, storageSet } = require("./browserApi");
|
|
|
|
const DEFAULT_STATE = {
|
|
hasWallet: false,
|
|
wallets: [],
|
|
trackedTokens: [],
|
|
networkId: "mainnet",
|
|
rpcUrl: DEFAULT_RPC_URL,
|
|
blockscoutUrl: DEFAULT_BLOCKSCOUT_URL,
|
|
lastBalanceRefresh: 0,
|
|
activeAddress: null,
|
|
allowedSites: {},
|
|
deniedSites: {},
|
|
rememberSiteChoice: true,
|
|
showZeroBalanceTokens: true,
|
|
hideSpoofedSymbols: true,
|
|
hideLowHolderTokens: true,
|
|
hideFraudContracts: true,
|
|
hideDustTransactions: true,
|
|
dustThresholdGwei: 100000,
|
|
utcTimestamps: false,
|
|
fraudContracts: [],
|
|
tokenHolderCache: {},
|
|
theme: "system",
|
|
debugMode: false,
|
|
};
|
|
|
|
const state = {
|
|
...DEFAULT_STATE,
|
|
currentView: null,
|
|
selectedWallet: null,
|
|
selectedAddress: null,
|
|
selectedToken: null,
|
|
viewData: {},
|
|
viewStack: [],
|
|
};
|
|
|
|
// Keep only the leading run of stored views the popup is willing to render.
|
|
//
|
|
// restoreView() refuses to reopen ONTO a non-restorable view, but the stack
|
|
// behind it used to be restored verbatim, so Back could walk onto a screen
|
|
// whose content is deliberately never re-rendered — and "show-phrase" has no
|
|
// Back control to leave by. Truncating at the first such entry instead of
|
|
// splicing it out keeps the result a prefix of the stored stack, so every
|
|
// surviving entry's Back target is exactly the one it had; splicing would
|
|
// silently re-point the entry above the hole at a different screen.
|
|
//
|
|
// Filtering happens here on load rather than in saveState(): the live
|
|
// in-session stack is legitimate (the screen really is rendered while the
|
|
// popup is open), and only a load-side filter also repairs the stacks
|
|
// already in storage, including ones written before a view left the set.
|
|
function restorableStack(stored, currentView) {
|
|
// A stored stack that is missing or not an array keeps nothing, but it
|
|
// still goes through the never-empty rule below rather than returning
|
|
// early: otherwise a corrupt stack would depend on exactly the goBack()
|
|
// fallback that the explicit ["main"] exists in order not to depend on.
|
|
const source = Array.isArray(stored) ? stored : [];
|
|
const cut = source.findIndex((view) => !RESTORABLE_VIEWS.has(view));
|
|
const kept = cut === -1 ? source.slice() : source.slice(0, cut);
|
|
// A view restored below the root still needs somewhere for Back to go.
|
|
if (
|
|
kept.length === 0 &&
|
|
currentView !== "main" &&
|
|
RESTORABLE_VIEWS.has(currentView)
|
|
) {
|
|
return ["main"];
|
|
}
|
|
return kept;
|
|
}
|
|
|
|
// Return the network configuration for the currently selected network.
|
|
function currentNetwork() {
|
|
return networkById(state.networkId);
|
|
}
|
|
|
|
async function saveState() {
|
|
const persisted = {
|
|
hasWallet: state.hasWallet,
|
|
wallets: state.wallets,
|
|
trackedTokens: state.trackedTokens,
|
|
networkId: state.networkId,
|
|
rpcUrl: state.rpcUrl,
|
|
blockscoutUrl: state.blockscoutUrl,
|
|
lastBalanceRefresh: state.lastBalanceRefresh,
|
|
activeAddress: state.activeAddress,
|
|
allowedSites: state.allowedSites,
|
|
deniedSites: state.deniedSites,
|
|
rememberSiteChoice: state.rememberSiteChoice,
|
|
showZeroBalanceTokens: state.showZeroBalanceTokens,
|
|
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
|
hideLowHolderTokens: state.hideLowHolderTokens,
|
|
hideFraudContracts: state.hideFraudContracts,
|
|
hideDustTransactions: state.hideDustTransactions,
|
|
dustThresholdGwei: state.dustThresholdGwei,
|
|
utcTimestamps: state.utcTimestamps,
|
|
fraudContracts: state.fraudContracts,
|
|
tokenHolderCache: state.tokenHolderCache,
|
|
theme: state.theme,
|
|
debugMode: state.debugMode,
|
|
currentView: state.currentView,
|
|
selectedWallet: state.selectedWallet,
|
|
selectedAddress: state.selectedAddress,
|
|
selectedToken: state.selectedToken,
|
|
viewData: state.viewData,
|
|
viewStack: state.viewStack,
|
|
};
|
|
await storageSet({ autistmask: persisted });
|
|
}
|
|
|
|
async function loadState() {
|
|
const result = await storageGet("autistmask");
|
|
if (result.autistmask) {
|
|
const saved = result.autistmask;
|
|
state.wallets = saved.wallets || [];
|
|
// Derived, never read from storage: a profile persisted with the flag
|
|
// out of step with the wallet list would otherwise stay broken on
|
|
// every load. Nothing depends on the two disagreeing.
|
|
state.hasWallet = state.wallets.length > 0;
|
|
state.trackedTokens = saved.trackedTokens || [];
|
|
state.networkId = saved.networkId || DEFAULT_STATE.networkId;
|
|
state.rpcUrl = saved.rpcUrl || DEFAULT_STATE.rpcUrl;
|
|
state.blockscoutUrl =
|
|
saved.blockscoutUrl || DEFAULT_STATE.blockscoutUrl;
|
|
state.lastBalanceRefresh = saved.lastBalanceRefresh || 0;
|
|
state.activeAddress = saved.activeAddress || null;
|
|
state.allowedSites =
|
|
saved.allowedSites && !Array.isArray(saved.allowedSites)
|
|
? saved.allowedSites
|
|
: {};
|
|
state.deniedSites =
|
|
saved.deniedSites && !Array.isArray(saved.deniedSites)
|
|
? saved.deniedSites
|
|
: {};
|
|
state.rememberSiteChoice =
|
|
saved.rememberSiteChoice !== undefined
|
|
? saved.rememberSiteChoice
|
|
: true;
|
|
state.showZeroBalanceTokens =
|
|
saved.showZeroBalanceTokens !== undefined
|
|
? saved.showZeroBalanceTokens
|
|
: true;
|
|
// A profile written before this setting existed has no key for it.
|
|
// It is a safety filter, so absent must load as on, not as undefined.
|
|
state.hideSpoofedSymbols =
|
|
saved.hideSpoofedSymbols !== undefined
|
|
? saved.hideSpoofedSymbols
|
|
: true;
|
|
state.hideLowHolderTokens =
|
|
saved.hideLowHolderTokens !== undefined
|
|
? saved.hideLowHolderTokens
|
|
: true;
|
|
state.hideFraudContracts =
|
|
saved.hideFraudContracts !== undefined
|
|
? saved.hideFraudContracts
|
|
: true;
|
|
state.hideDustTransactions =
|
|
saved.hideDustTransactions !== undefined
|
|
? saved.hideDustTransactions
|
|
: true;
|
|
state.dustThresholdGwei =
|
|
saved.dustThresholdGwei !== undefined
|
|
? saved.dustThresholdGwei
|
|
: 100000;
|
|
state.utcTimestamps =
|
|
saved.utcTimestamps !== undefined ? saved.utcTimestamps : false;
|
|
state.fraudContracts = saved.fraudContracts || [];
|
|
state.tokenHolderCache = saved.tokenHolderCache || {};
|
|
state.theme = saved.theme || "system";
|
|
state.debugMode =
|
|
saved.debugMode !== undefined ? saved.debugMode : false;
|
|
state.currentView = saved.currentView || null;
|
|
state.selectedWallet =
|
|
saved.selectedWallet !== undefined ? saved.selectedWallet : null;
|
|
state.selectedAddress =
|
|
saved.selectedAddress !== undefined ? saved.selectedAddress : null;
|
|
state.selectedToken = saved.selectedToken || null;
|
|
state.viewData = saved.viewData || {};
|
|
state.viewStack = restorableStack(saved.viewStack, state.currentView);
|
|
}
|
|
}
|
|
|
|
function currentAddress() {
|
|
if (state.selectedWallet === null || state.selectedAddress === null) {
|
|
return null;
|
|
}
|
|
return state.wallets[state.selectedWallet].addresses[state.selectedAddress];
|
|
}
|
|
|
|
module.exports = {
|
|
state,
|
|
saveState,
|
|
loadState,
|
|
currentAddress,
|
|
currentNetwork,
|
|
};
|