A page that had not yet taken another page's network switch could lose tracked tokens. - Its save wrote its network's list as the new network's list. saveState() now keeps that list and merges the change into its own network's list in networkTrackedTokens. - Its own switch wrote its old per-network lists over the stored ones. onChainSwitch() now first takes the stored switch with takeStoredNetwork(). - takeStoredNetwork() dropped the page's unsaved change. It now keeps it in the list of the page's network. This also fixes #706. - A page already on the stored network now takes the stored networkTrackedTokens before it switches, because another page's save may have changed a list this page holds. Model: opus-5-5
113 lines
4.9 KiB
JavaScript
113 lines
4.9 KiB
JavaScript
// The popup's network switch.
|
|
//
|
|
// A network switch reaches the popup's `state` in one of two ways:
|
|
//
|
|
// - onChainSwitch() below makes the switch the user picks in Settings, with
|
|
// applyChainSwitchFields() (chainSwitchFields.js).
|
|
// - followStoredSettings() in src/popup/index.js takes a switch the background
|
|
// or another popup page saved, from the stored record, with
|
|
// takeStoredNetwork() (state.js), which copies it with
|
|
// copyChainSwitchFields() (chainSwitchFields.js).
|
|
//
|
|
// Both change `state` inside a save, holding the lock, and both reset the
|
|
// popup's own caches through clearPopupCaches(), so a reset added there
|
|
// reaches both. A field applyChainSwitchFields() sets has to be copied by
|
|
// copyChainSwitchFields() too.
|
|
//
|
|
// Adding a new chain (e.g. ETC) requires only a new entry in
|
|
// networks.js — no per-caller wiring is needed.
|
|
//
|
|
// The background does NOT come through here: this function mutates the
|
|
// module-level `state` singleton, which the MV3 service worker never
|
|
// populates, and a background switch performed on it wrote DEFAULT_STATE over
|
|
// the user's whole profile
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/316). The field mutations
|
|
// themselves live in chainSwitchFields.js, which takes the record to mutate as
|
|
// an argument; src/background/state.js applies them inside a read-modify-write
|
|
// against storage, and the singleton is not reachable from the background
|
|
// bundle at all (enforced by the ESLint rule in eslint.config.js).
|
|
|
|
const { applyChainSwitchFields } = require("./chainSwitchFields");
|
|
const { clearPrices } = require("./prices");
|
|
|
|
// The popup's own caches a network switch resets. Prices are chain-specific
|
|
// (testnet tokens are worthless, ETC has different pricing, etc.). In-memory
|
|
// and per bundle, so this is the popup's own cache — the only context that
|
|
// ever fills it.
|
|
function clearPopupCaches() {
|
|
clearPrices();
|
|
}
|
|
|
|
// Switch the active chain and reset all chain-specific cached state.
|
|
// Returns the network configuration object for the new chain, or null when
|
|
// `state` already has that network once the save holds the lock, and nothing
|
|
// is switched.
|
|
//
|
|
// `state` is switched inside the save that stores the switch, holding the
|
|
// lock, so it is never switched while a follow of a switch saved elsewhere
|
|
// waits for the lock (followStoredSettings() in src/popup/index.js): that
|
|
// follow runs first, and this switch is made after it.
|
|
//
|
|
// The save writes only the fields that differ from what this page last saved.
|
|
// When this page last saved a balance of "0", clearing that balance is no
|
|
// change. So the switch also resets these fields in the stored record:
|
|
//
|
|
// - every address's balance and token balances;
|
|
// - the time of the last balance refresh;
|
|
// - the fraud contracts.
|
|
//
|
|
// A balance or a fraud contract that the background or another page stored
|
|
// since this page's last save is then not kept under the new network
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/653,
|
|
// https://git.eeqj.de/sneak/AutistMask/issues/692).
|
|
//
|
|
// When the stored record is on another network, a switch saved elsewhere has
|
|
// not reached this page yet. This page first takes that switch with
|
|
// takeStoredNetwork() (state.js), as followStoredSettings() does. The switch to
|
|
// `newNetworkId` is then made from the stored record's network. So every
|
|
// network's list of tracked tokens in the stored record is kept
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/701). When the stored record is
|
|
// already on `newNetworkId`, the switch made from there only resets the
|
|
// balances and caches.
|
|
//
|
|
// When the stored record is on this page's network, this page first takes the
|
|
// stored `networkTrackedTokens` (takeStoredNetworkTrackedTokens() in state.js),
|
|
// because another page's save may have changed a list this page holds.
|
|
//
|
|
// Before the switch, `state` takes the tracked tokens that another page stored
|
|
// since this page's last save (takeStoredTrackedTokens() in state.js). The
|
|
// switch then puts them away under the previous network, the network they were
|
|
// added on.
|
|
async function onChainSwitch(newNetworkId) {
|
|
const {
|
|
state,
|
|
saveState,
|
|
takeStoredNetwork,
|
|
takeStoredTrackedTokens,
|
|
takeStoredNetworkTrackedTokens,
|
|
} = require("./state");
|
|
let net = null;
|
|
await saveState((stored) => {
|
|
if (state.networkId === newNetworkId) return false;
|
|
if (stored.networkId !== state.networkId) {
|
|
takeStoredNetwork(stored);
|
|
} else {
|
|
takeStoredNetworkTrackedTokens(stored);
|
|
}
|
|
takeStoredTrackedTokens(stored);
|
|
net = applyChainSwitchFields(state, newNetworkId);
|
|
stored.lastBalanceRefresh = 0;
|
|
stored.fraudContracts = [];
|
|
for (const wallet of stored.wallets) {
|
|
for (const addr of wallet.addresses) {
|
|
addr.balance = "0";
|
|
addr.tokenBalances = [];
|
|
}
|
|
}
|
|
clearPopupCaches();
|
|
});
|
|
return net;
|
|
}
|
|
|
|
module.exports = { onChainSwitch, clearPopupCaches };
|