harden: stop the background reading the shared state singleton, and enforce it at build time (closes #324)
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
This commit was merged in pull request #344.
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const { makeStorageStub } = require("./support/storageStub");
|
||||
|
||||
const POPUP_HTML = fs.readFileSync(
|
||||
path.join(__dirname, "..", "src", "popup", "index.html"),
|
||||
"utf8",
|
||||
@@ -51,19 +53,17 @@ describe("the UTC Timestamps checkbox placement", () => {
|
||||
});
|
||||
|
||||
describe("the UTC Timestamps setting round-trips through storage", () => {
|
||||
let store;
|
||||
let storage;
|
||||
|
||||
// The stub clones in both directions, as the real chrome.storage.local
|
||||
// does. It used to alias, which is fatal to a round-trip test in
|
||||
// particular: the object the module holds and the object "storage" holds
|
||||
// are then the same object, so the setting appears to have been persisted
|
||||
// and read back on a build where neither happened. See
|
||||
// tests/support/storageStub.js.
|
||||
function loadStateModule() {
|
||||
store = {};
|
||||
global.chrome = {
|
||||
storage: {
|
||||
local: {
|
||||
get: async (key) =>
|
||||
key in store ? { [key]: store[key] } : {},
|
||||
set: async (obj) => Object.assign(store, obj),
|
||||
},
|
||||
},
|
||||
};
|
||||
storage = makeStorageStub();
|
||||
global.chrome = { storage };
|
||||
jest.resetModules();
|
||||
return require("../src/shared/state");
|
||||
}
|
||||
@@ -86,12 +86,18 @@ describe("the UTC Timestamps setting round-trips through storage", () => {
|
||||
// What the change handler in views/settings.js does.
|
||||
first.state.utcTimestamps = true;
|
||||
await first.saveState();
|
||||
expect(store.autistmask.utcTimestamps).toBe(true);
|
||||
expect(storage.read("autistmask").utcTimestamps).toBe(true);
|
||||
|
||||
// A fresh popup load sees it.
|
||||
// A fresh popup load sees it — and, before that load, refuses to
|
||||
// answer at all rather than reporting the default. That refusal is
|
||||
// what makes the assertion below evidence of a read from storage
|
||||
// instead of a value that was already sitting in memory
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/324).
|
||||
jest.resetModules();
|
||||
const second = require("../src/shared/state");
|
||||
expect(second.state.utcTimestamps).toBe(false);
|
||||
expect(() => second.state.utcTimestamps).toThrow(
|
||||
second.StateNotLoadedError,
|
||||
);
|
||||
await second.loadState();
|
||||
expect(second.state.utcTimestamps).toBe(true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user