All checks were successful
check / check (push) Successful in 14s
Add a new well at the bottom of the settings view that displays: - License (GPL-3.0) - Author (sneak) - Version (from package.json) - Build date (injected at build time) - Git commit short hash (linked to Gitea commit URL) Build-time injection: build.js now reads the git commit hash and version from package.json, injecting them via esbuild define constants. The Dockerfile and Makefile pass commit hashes as build args so the info is available even when .git is excluded from the Docker context. Easter egg: clicking the version number 10 times reveals a hidden debug well below the About well, containing a toggle for debug mode. The debug mode flag is persisted in state and enables verbose console logging via the runtime debug flag in the logger. closes #144
349 lines
13 KiB
JavaScript
349 lines
13 KiB
JavaScript
const { $, showView, showFlash, escapeHtml } = require("./helpers");
|
|
const { applyTheme } = require("../theme");
|
|
const { state, saveState, currentNetwork } = require("../../shared/state");
|
|
const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks");
|
|
const { onChainSwitch } = require("../../shared/chainSwitch");
|
|
const { log, debugFetch, setRuntimeDebug } = require("../../shared/log");
|
|
const deleteWallet = require("./deleteWallet");
|
|
const {
|
|
BUILD_VERSION,
|
|
BUILD_LICENSE,
|
|
BUILD_AUTHOR,
|
|
BUILD_COMMIT,
|
|
BUILD_DATE,
|
|
GITEA_COMMIT_URL,
|
|
} = require("../../shared/buildInfo");
|
|
|
|
const runtime =
|
|
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
|
|
|
|
let versionClickCount = 0;
|
|
let versionClickTimer = null;
|
|
|
|
function renderSiteList(containerId, siteMap, stateKey) {
|
|
const container = $(containerId);
|
|
const hostnames = [...new Set(Object.values(siteMap).flat())];
|
|
if (hostnames.length === 0) {
|
|
container.innerHTML = '<p class="text-xs text-muted">None</p>';
|
|
return;
|
|
}
|
|
let html = "";
|
|
hostnames.forEach((hostname) => {
|
|
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
|
html += `<span>${hostname}</span>`;
|
|
html += `<button class="btn-remove-site border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-key="${stateKey}" data-hostname="${hostname}">[x]</button>`;
|
|
html += `</div>`;
|
|
});
|
|
container.innerHTML = html;
|
|
container.querySelectorAll(".btn-remove-site").forEach((btn) => {
|
|
btn.addEventListener("click", async () => {
|
|
const key = btn.dataset.key;
|
|
const host = btn.dataset.hostname;
|
|
for (const addr of Object.keys(state[key])) {
|
|
state[key][addr] = state[key][addr].filter((h) => h !== host);
|
|
if (state[key][addr].length === 0) {
|
|
delete state[key][addr];
|
|
}
|
|
}
|
|
await saveState();
|
|
runtime.sendMessage({ type: "AUTISTMASK_REMOVE_SITE" });
|
|
renderSiteList(containerId, state[key], key);
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderTrackedTokens() {
|
|
const container = $("settings-tracked-tokens");
|
|
if (state.trackedTokens.length === 0) {
|
|
container.innerHTML = '<p class="text-xs text-muted">None</p>';
|
|
return;
|
|
}
|
|
let html = "";
|
|
state.trackedTokens.forEach((token, idx) => {
|
|
const label = token.name
|
|
? escapeHtml(token.name) + " (" + escapeHtml(token.symbol) + ")"
|
|
: escapeHtml(token.symbol);
|
|
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
|
html += `<span>${label}</span>`;
|
|
html += `<button class="btn-remove-token border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-idx="${idx}">[x]</button>`;
|
|
html += `</div>`;
|
|
});
|
|
container.innerHTML = html;
|
|
container.querySelectorAll(".btn-remove-token").forEach((btn) => {
|
|
btn.addEventListener("click", async () => {
|
|
const idx = parseInt(btn.dataset.idx, 10);
|
|
state.trackedTokens.splice(idx, 1);
|
|
await saveState();
|
|
renderTrackedTokens();
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderWalletListSettings() {
|
|
const container = $("settings-wallet-list");
|
|
if (state.wallets.length === 0) {
|
|
container.innerHTML = '<p class="text-xs text-muted">No wallets.</p>';
|
|
return;
|
|
}
|
|
let html = "";
|
|
state.wallets.forEach((wallet, idx) => {
|
|
const name = escapeHtml(wallet.name || "Wallet " + (idx + 1));
|
|
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
|
html += `<span class="settings-wallet-name cursor-pointer underline decoration-dashed" data-idx="${idx}">${name}</span>`;
|
|
html += `<button class="btn-delete-wallet border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-idx="${idx}">[x]</button>`;
|
|
html += `</div>`;
|
|
});
|
|
container.innerHTML = html;
|
|
container.querySelectorAll(".btn-delete-wallet").forEach((btn) => {
|
|
btn.addEventListener("click", () => {
|
|
const idx = parseInt(btn.dataset.idx, 10);
|
|
deleteWallet.show(idx);
|
|
});
|
|
});
|
|
|
|
// Inline rename on click
|
|
container.querySelectorAll(".settings-wallet-name").forEach((span) => {
|
|
span.addEventListener("click", () => {
|
|
const idx = parseInt(span.dataset.idx, 10);
|
|
const wallet = state.wallets[idx];
|
|
const input = document.createElement("input");
|
|
input.type = "text";
|
|
input.className =
|
|
"border border-border p-0 text-xs bg-bg text-fg w-full";
|
|
input.value = wallet.name || "Wallet " + (idx + 1);
|
|
span.replaceWith(input);
|
|
input.focus();
|
|
input.select();
|
|
const finish = async () => {
|
|
const val = input.value.trim();
|
|
if (val && val !== wallet.name) {
|
|
wallet.name = val;
|
|
await saveState();
|
|
}
|
|
renderWalletListSettings();
|
|
};
|
|
input.addEventListener("blur", finish);
|
|
input.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") input.blur();
|
|
if (e.key === "Escape") {
|
|
input.value = wallet.name || "Wallet " + (idx + 1);
|
|
input.blur();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function show() {
|
|
$("settings-rpc").value = state.rpcUrl;
|
|
$("settings-blockscout").value = state.blockscoutUrl;
|
|
const networkSelect = $("settings-network");
|
|
if (networkSelect) {
|
|
networkSelect.value = state.networkId;
|
|
}
|
|
renderTrackedTokens();
|
|
renderSiteLists();
|
|
renderWalletListSettings();
|
|
|
|
// Populate About well
|
|
$("about-license").textContent = BUILD_LICENSE;
|
|
// Show only the name part of the author field (strip email)
|
|
const authorName = BUILD_AUTHOR.replace(/\s*<[^>]+>/, "");
|
|
$("about-author").textContent = authorName;
|
|
$("about-version").textContent = BUILD_VERSION;
|
|
$("about-build-date").textContent = BUILD_DATE;
|
|
$("about-commit-link").textContent = BUILD_COMMIT;
|
|
$("about-commit-link").href = GITEA_COMMIT_URL;
|
|
|
|
// Reset version click counter each time settings opens
|
|
versionClickCount = 0;
|
|
|
|
// Show debug well if debug mode is already enabled
|
|
const debugWell = $("settings-debug-well");
|
|
if (state.debugMode) {
|
|
debugWell.style.display = "";
|
|
} else {
|
|
debugWell.style.display = "none";
|
|
}
|
|
$("settings-debug-mode").checked = state.debugMode;
|
|
|
|
showView("settings");
|
|
}
|
|
|
|
function renderSiteLists() {
|
|
renderSiteList(
|
|
"settings-allowed-sites",
|
|
state.allowedSites,
|
|
"allowedSites",
|
|
);
|
|
renderSiteList("settings-denied-sites", state.deniedSites, "deniedSites");
|
|
}
|
|
|
|
function init(ctx) {
|
|
deleteWallet.init(ctx);
|
|
|
|
$("btn-save-rpc").addEventListener("click", async () => {
|
|
const url = $("settings-rpc").value.trim();
|
|
if (!url) {
|
|
showFlash("Please enter an RPC URL.");
|
|
return;
|
|
}
|
|
showFlash("Testing endpoint...");
|
|
try {
|
|
const resp = await debugFetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "eth_chainId",
|
|
params: [],
|
|
}),
|
|
});
|
|
const json = await resp.json();
|
|
if (json.error) {
|
|
log.errorf("RPC validation error:", json.error);
|
|
showFlash("Endpoint returned error: " + json.error.message);
|
|
return;
|
|
}
|
|
const net = currentNetwork();
|
|
if (json.result !== net.chainId) {
|
|
showFlash(
|
|
"Wrong network (expected " +
|
|
net.name +
|
|
", got chain " +
|
|
json.result +
|
|
").",
|
|
);
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
log.errorf("RPC validation fetch failed:", e.message);
|
|
showFlash("Could not reach endpoint.");
|
|
return;
|
|
}
|
|
state.rpcUrl = url;
|
|
await saveState();
|
|
showFlash("Saved.");
|
|
});
|
|
|
|
$("btn-save-blockscout").addEventListener("click", async () => {
|
|
const url = $("settings-blockscout").value.trim();
|
|
if (!url) {
|
|
showFlash("Please enter a Blockscout API URL.");
|
|
return;
|
|
}
|
|
showFlash("Testing endpoint...");
|
|
try {
|
|
const resp = await debugFetch(url + "/stats");
|
|
if (!resp.ok) {
|
|
showFlash("Endpoint returned HTTP " + resp.status + ".");
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
log.errorf("Blockscout validation failed:", e.message);
|
|
showFlash("Could not reach endpoint.");
|
|
return;
|
|
}
|
|
state.blockscoutUrl = url;
|
|
await saveState();
|
|
showFlash("Saved.");
|
|
});
|
|
|
|
const networkSelect = $("settings-network");
|
|
if (networkSelect) {
|
|
networkSelect.addEventListener("change", async () => {
|
|
const newId = networkSelect.value;
|
|
const net = await onChainSwitch(newId);
|
|
$("settings-rpc").value = state.rpcUrl;
|
|
$("settings-blockscout").value = state.blockscoutUrl;
|
|
showFlash("Switched to " + net.name + ".");
|
|
});
|
|
}
|
|
|
|
$("settings-show-zero-balances").checked = state.showZeroBalanceTokens;
|
|
$("settings-show-zero-balances").addEventListener("change", async () => {
|
|
state.showZeroBalanceTokens = $("settings-show-zero-balances").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-theme").value = state.theme;
|
|
$("settings-theme").addEventListener("change", async () => {
|
|
state.theme = $("settings-theme").value;
|
|
await saveState();
|
|
applyTheme(state.theme);
|
|
});
|
|
|
|
$("settings-hide-low-holders").checked = state.hideLowHolderTokens;
|
|
$("settings-hide-low-holders").addEventListener("change", async () => {
|
|
state.hideLowHolderTokens = $("settings-hide-low-holders").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-hide-fraud-contracts").checked = state.hideFraudContracts;
|
|
$("settings-hide-fraud-contracts").addEventListener("change", async () => {
|
|
state.hideFraudContracts = $("settings-hide-fraud-contracts").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-hide-dust").checked = state.hideDustTransactions;
|
|
$("settings-hide-dust").addEventListener("change", async () => {
|
|
state.hideDustTransactions = $("settings-hide-dust").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-dust-threshold").value = state.dustThresholdGwei;
|
|
$("settings-dust-threshold").addEventListener("change", async () => {
|
|
const val = parseInt($("settings-dust-threshold").value, 10);
|
|
if (!isNaN(val) && val >= 0) {
|
|
state.dustThresholdGwei = val;
|
|
await saveState();
|
|
}
|
|
});
|
|
|
|
$("settings-utc-timestamps").checked = state.utcTimestamps;
|
|
$("settings-utc-timestamps").addEventListener("change", async () => {
|
|
state.utcTimestamps = $("settings-utc-timestamps").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
|
|
|
|
$("btn-settings-add-token").addEventListener(
|
|
"click",
|
|
ctx.showSettingsAddTokenView,
|
|
);
|
|
|
|
// Easter egg: click version 10 times to reveal the debug well
|
|
$("about-version").addEventListener("click", () => {
|
|
versionClickCount++;
|
|
clearTimeout(versionClickTimer);
|
|
// Reset counter if user stops clicking for 3 seconds
|
|
versionClickTimer = setTimeout(() => {
|
|
versionClickCount = 0;
|
|
}, 3000);
|
|
if (versionClickCount >= 10) {
|
|
versionClickCount = 0;
|
|
clearTimeout(versionClickTimer);
|
|
$("settings-debug-well").style.display = "";
|
|
}
|
|
});
|
|
|
|
// Debug mode toggle
|
|
$("settings-debug-mode").addEventListener("change", async () => {
|
|
state.debugMode = $("settings-debug-mode").checked;
|
|
setRuntimeDebug(state.debugMode);
|
|
await saveState();
|
|
});
|
|
|
|
// Sync runtime debug flag on init
|
|
setRuntimeDebug(state.debugMode);
|
|
|
|
$("btn-settings-back").addEventListener("click", () => {
|
|
ctx.renderWalletList();
|
|
showView("main");
|
|
});
|
|
}
|
|
|
|
module.exports = { init, show, renderSiteLists };
|