Five defects, one of which destroyed every wallet, came from src/background reading and writing the module-level state singleton the MV3 worker never populates, which silently served DEFAULT_STATE. Each point fix created the next defect. The background now has its own per-call getState() and a queued read-modify-write updateState(); the singleton is unreachable from it, and an unpopulated read throws instead of serving defaults. The prohibition is enforced by the build, not by review: build.js asserts over esbuild's own metafile that no forbidden module is an input of a background bundle, so every specifier syntax esbuild resolves is covered, and both halves of the table are checked for rot -- a stale key, a stale module, an empty list, or an unlisted entry point under src/background/ all fail the build. The ESLint rule remains as fast local feedback and reads the same shared table. Known bounds are documented where the table lives. Also closes #320: getProvider() now requires a validated network id, so a cold worker no longer prepares a non-mainnet dApp transaction for mainnet and gets refused by the wallet's own verifier. backgroundRefresh() no longer mutates address objects across a network round trip, the broadcast path takes its endpoint and chain id from one snapshot, and eight test storage stubs now structured-clone on get as the real chrome.storage.local does. closes #320
42 lines
1.7 KiB
JavaScript
42 lines
1.7 KiB
JavaScript
// Consolidated chain-switch handler for the popup.
|
|
//
|
|
// Every state change required when the active network changes is
|
|
// performed here so that callers (settings UI, future chain additions) all go
|
|
// through a single code path.
|
|
//
|
|
// 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");
|
|
|
|
// Switch the active chain and reset all chain-specific cached state.
|
|
// Returns the network configuration object for the new chain.
|
|
async function onChainSwitch(newNetworkId) {
|
|
const { state, saveState } = require("./state");
|
|
|
|
const net = applyChainSwitchFields(state, newNetworkId);
|
|
|
|
// --- price cache ---
|
|
// 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.
|
|
clearPrices();
|
|
|
|
await saveState();
|
|
|
|
return net;
|
|
}
|
|
|
|
module.exports = { onChainSwitch };
|