Out of the box the wallet sent the user's addresses to Blockscout. It sent each recipient to Etherscan for its label. Two settings, "Use Blockscout" and "Check recipients on Etherscan", now gate those requests. Both are off in a new install and offered unticked on Welcome and in Settings. While one is off, the screens that relied on it say so in a plain line. Each gated request reads its setting from storage before it asks and when the answer lands. An answer that lands after the setting went off is dropped. An open popup takes a change another page saved and draws its screens again. While "Use Blockscout" is off, no address keeps an untracked token. ENS lookups ask only the RPC endpoint. Model: opus-5-5
108 lines
3.5 KiB
JavaScript
108 lines
3.5 KiB
JavaScript
// The balance refresh, and the address scan after a wallet is created, do not
|
|
// report a request the popup's own closing cancelled, and still report one
|
|
// that failed while the popup was open
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/218,
|
|
// https://git.eeqj.de/sneak/AutistMask/issues/475).
|
|
//
|
|
// In the popup a cancelled fetch() fails with the same "Failed to fetch" as a
|
|
// server that cannot be reached, so every request here fails that way, and
|
|
// only the signal the popup aborts on pagehide tells the two cases apart.
|
|
|
|
const { FetchRequest } = require("ethers");
|
|
const { refreshBalances, scanForAddresses } = require("../src/shared/balances");
|
|
const {
|
|
generateMnemonic,
|
|
hdWalletFromMnemonic,
|
|
} = require("../src/shared/wallet");
|
|
|
|
const RPC_URL = "https://rpc.example.invalid";
|
|
const EXPLORER_URL = "https://explorer.example.invalid/api/v2";
|
|
const ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
const TRACKED = {
|
|
address: "0x7777777777777777777777777777777777777777",
|
|
symbol: "TRK",
|
|
decimals: 6,
|
|
};
|
|
|
|
// Blockscout is asked only while the stored "Use Blockscout" is on.
|
|
const { makeStorageStub } = require("./support/storageStub");
|
|
globalThis.chrome = {
|
|
storage: makeStorageStub({ autistmask: { useBlockscout: true } }),
|
|
};
|
|
|
|
const realFetch = globalThis.fetch;
|
|
let logged;
|
|
|
|
beforeEach(() => {
|
|
logged = [];
|
|
jest.spyOn(console, "error").mockImplementation((...args) => {
|
|
logged.push(args.map(String).join(" "));
|
|
});
|
|
const failedToFetch = async () => {
|
|
throw new TypeError("Failed to fetch");
|
|
};
|
|
// The RPC calls (ETH balance, ENS name, the tracked token's balance, and
|
|
// the scan's balance and transaction count) and the explorer request (the
|
|
// other token balances) all fail the same way.
|
|
FetchRequest.registerGetUrl(failedToFetch);
|
|
globalThis.fetch = jest.fn(failedToFetch);
|
|
});
|
|
|
|
afterEach(() => {
|
|
FetchRequest.registerGetUrl(FetchRequest.createGetUrlFunc());
|
|
globalThis.fetch = realFetch;
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
function refresh(signal) {
|
|
const wallets = [{ addresses: [{ address: ADDRESS }] }];
|
|
return refreshBalances(
|
|
wallets,
|
|
RPC_URL,
|
|
EXPLORER_URL,
|
|
[TRACKED],
|
|
"mainnet",
|
|
signal,
|
|
);
|
|
}
|
|
|
|
test("a failure while the popup is open is reported", async () => {
|
|
await refresh(new AbortController().signal);
|
|
for (const label of [
|
|
"ETH balance failed",
|
|
"ENS reverse failed",
|
|
"token balance failed",
|
|
"fetchTokenBalances failed: Failed to fetch",
|
|
]) {
|
|
expect(logged.some((line) => line.includes(label))).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("a failure once the popup has closed is not", async () => {
|
|
const pageClosed = new AbortController();
|
|
pageClosed.abort();
|
|
await refresh(pageClosed.signal);
|
|
expect(logged).toEqual([]);
|
|
});
|
|
|
|
function scan(signal) {
|
|
// What the scan found is logged at info level, which is not under test.
|
|
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
const { xpub } = hdWalletFromMnemonic(generateMnemonic());
|
|
return scanForAddresses(xpub, RPC_URL, "mainnet", signal);
|
|
}
|
|
|
|
test("a scan failure while the popup is open is reported", async () => {
|
|
await scan(new AbortController().signal);
|
|
expect(
|
|
logged.some((line) => line.includes("scanForAddresses check failed")),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("a scan failure once the popup has closed is not", async () => {
|
|
const pageClosed = new AbortController();
|
|
pageClosed.abort();
|
|
await scan(pageClosed.signal);
|
|
expect(logged).toEqual([]);
|
|
});
|