From cc69c1b2a00eb5f21e260ff6bd2d16c9b65413ca Mon Sep 17 00:00:00 2001 From: user Date: Sun, 1 Mar 2026 11:33:44 -0800 Subject: [PATCH] feat: add About well to settings with build info and debug easter egg 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 --- Dockerfile | 5 ++++ Makefile | 5 +++- build.js | 50 +++++++++++++++++++++++++++++++ src/popup/index.html | 48 +++++++++++++++++++++++++++++ src/popup/views/settings.js | 60 ++++++++++++++++++++++++++++++++++++- src/shared/buildInfo.js | 35 ++++++++++++++++++++++ src/shared/log.js | 21 +++++++++++-- src/shared/state.js | 4 +++ 8 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 src/shared/buildInfo.js diff --git a/Dockerfile b/Dockerfile index d9e1ac2..b2cf0b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,5 +11,10 @@ RUN yarn install --frozen-lockfile COPY . . +ARG GIT_COMMIT_SHORT=unknown +ARG GIT_COMMIT_FULL=unknown +ENV GIT_COMMIT_SHORT=${GIT_COMMIT_SHORT} +ENV GIT_COMMIT_FULL=${GIT_COMMIT_FULL} + RUN make check RUN make build diff --git a/Makefile b/Makefile index ff9ffe8..4d596a1 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,10 @@ dev: @yarn run build --watch 2>&1 docker: - @docker build -t autistmask . + @docker build \ + --build-arg GIT_COMMIT_SHORT=$$(git rev-parse --short HEAD 2>/dev/null || echo unknown) \ + --build-arg GIT_COMMIT_FULL=$$(git rev-parse HEAD 2>/dev/null || echo unknown) \ + -t autistmask . hooks: @echo "Installing pre-commit hook..." diff --git a/build.js b/build.js index fdc9a24..4fd43d3 100644 --- a/build.js +++ b/build.js @@ -11,9 +11,55 @@ function ensureDir(dir) { fs.mkdirSync(dir, { recursive: true }); } +function getBuildInfo() { + const pkg = JSON.parse( + fs.readFileSync(path.join(__dirname, "package.json"), "utf8"), + ); + let commitHash = process.env.GIT_COMMIT_SHORT || "unknown"; + if (commitHash === "unknown") { + try { + commitHash = execSync("git rev-parse --short HEAD", { + encoding: "utf8", + }).trim(); + } catch (_) { + // not a git repo or git not available + } + } + let commitHashFull = process.env.GIT_COMMIT_FULL || "unknown"; + if (commitHashFull === "unknown") { + try { + commitHashFull = execSync("git rev-parse HEAD", { + encoding: "utf8", + }).trim(); + } catch (_) { + // not a git repo or git not available + } + } + return { + version: pkg.version, + license: pkg.license, + author: pkg.author, + commitHash, + commitHashFull, + buildDate: new Date().toISOString().slice(0, 10), + }; +} + async function build() { console.log("Building AutistMask extension..."); + const buildInfo = getBuildInfo(); + console.log("Build info:", buildInfo); + + const define = { + __BUILD_VERSION__: JSON.stringify(buildInfo.version), + __BUILD_LICENSE__: JSON.stringify(buildInfo.license), + __BUILD_AUTHOR__: JSON.stringify(buildInfo.author), + __BUILD_COMMIT__: JSON.stringify(buildInfo.commitHash), + __BUILD_COMMIT_FULL__: JSON.stringify(buildInfo.commitHashFull), + __BUILD_DATE__: JSON.stringify(buildInfo.buildDate), + }; + // compile tailwind CSS console.log("Compiling Tailwind CSS..."); const tailwindInput = path.join(SRC, "popup", "styles", "main.css"); @@ -38,6 +84,7 @@ async function build() { platform: "browser", target: ["chrome110", "firefox110"], minify: true, + define, }); // bundle background script @@ -49,6 +96,7 @@ async function build() { platform: "browser", target: ["chrome110", "firefox110"], minify: true, + define, }); // bundle content script @@ -60,6 +108,7 @@ async function build() { platform: "browser", target: ["chrome110", "firefox110"], minify: true, + define, }); // bundle inpage script (injected into page context, separate file) @@ -71,6 +120,7 @@ async function build() { platform: "browser", target: ["chrome110", "firefox110"], minify: true, + define, }); // copy popup HTML diff --git a/src/popup/index.html b/src/popup/index.html index 90fb615..53c9113 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -1002,6 +1002,54 @@

+ +
+

About

+
+
+ License: + +
+
+ Author: + +
+
+ Version: + +
+
+ Build date: + +
+
+ Commit: + +
+
+
+ + diff --git a/src/popup/views/settings.js b/src/popup/views/settings.js index 9bae55a..b140519 100644 --- a/src/popup/views/settings.js +++ b/src/popup/views/settings.js @@ -2,12 +2,23 @@ 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 { log, debugFetch } = require("../../shared/log"); +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())]; @@ -133,6 +144,28 @@ function show() { 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"); } @@ -285,6 +318,31 @@ function init(ctx) { 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"); diff --git a/src/shared/buildInfo.js b/src/shared/buildInfo.js new file mode 100644 index 0000000..63c4b1a --- /dev/null +++ b/src/shared/buildInfo.js @@ -0,0 +1,35 @@ +// Build-time constants injected by esbuild define in build.js. +// These globals are replaced at bundle time with string literals. + +/* global __BUILD_VERSION__, __BUILD_LICENSE__, __BUILD_AUTHOR__, + __BUILD_COMMIT__, __BUILD_COMMIT_FULL__, __BUILD_DATE__ */ + +const BUILD_VERSION = + typeof __BUILD_VERSION__ !== "undefined" ? __BUILD_VERSION__ : "dev"; +const BUILD_LICENSE = + typeof __BUILD_LICENSE__ !== "undefined" ? __BUILD_LICENSE__ : "GPL-3.0"; +const BUILD_AUTHOR = + typeof __BUILD_AUTHOR__ !== "undefined" + ? __BUILD_AUTHOR__ + : "sneak "; +const BUILD_COMMIT = + typeof __BUILD_COMMIT__ !== "undefined" ? __BUILD_COMMIT__ : "unknown"; +const BUILD_COMMIT_FULL = + typeof __BUILD_COMMIT_FULL__ !== "undefined" + ? __BUILD_COMMIT_FULL__ + : "unknown"; +const BUILD_DATE = + typeof __BUILD_DATE__ !== "undefined" ? __BUILD_DATE__ : "unknown"; + +const GITEA_COMMIT_URL = + "https://git.eeqj.de/sneak/AutistMask/commit/" + BUILD_COMMIT_FULL; + +module.exports = { + BUILD_VERSION, + BUILD_LICENSE, + BUILD_AUTHOR, + BUILD_COMMIT, + BUILD_COMMIT_FULL, + BUILD_DATE, + GITEA_COMMIT_URL, +}; diff --git a/src/shared/log.js b/src/shared/log.js index b4a63f9..551fb20 100644 --- a/src/shared/log.js +++ b/src/shared/log.js @@ -1,12 +1,27 @@ // Leveled logger. Outputs to console with [AutistMask] prefix. -// Level is DEBUG when the DEBUG constant is true, INFO otherwise. +// Level is DEBUG when the compile-time DEBUG constant is true or the runtime +// debugMode state flag is enabled. The runtime flag is checked lazily so it +// responds immediately when toggled in settings. const { DEBUG } = require("./constants"); const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; -const threshold = DEBUG ? LEVELS.debug : LEVELS.info; + +// Runtime debug mode flag — set by settings.js when the user toggles debug +// mode via the easter egg. Kept here as a simple mutable reference so it can +// be updated without circular dependency issues with state.js. +let _runtimeDebug = false; + +function setRuntimeDebug(enabled) { + _runtimeDebug = enabled; +} + +function isDebug() { + return DEBUG || _runtimeDebug; +} function emit(level, method, args) { + const threshold = isDebug() ? LEVELS.debug : LEVELS.info; if (LEVELS[level] >= threshold) { console[method]("[AutistMask]", ...args); } @@ -37,4 +52,4 @@ async function debugFetch(url, opts) { return resp; } -module.exports = { log, debugFetch }; +module.exports = { log, debugFetch, setRuntimeDebug, isDebug }; diff --git a/src/shared/state.js b/src/shared/state.js index b39faed..1d97b8d 100644 --- a/src/shared/state.js +++ b/src/shared/state.js @@ -29,6 +29,7 @@ const DEFAULT_STATE = { fraudContracts: [], tokenHolderCache: {}, theme: "system", + debugMode: false, }; const state = { @@ -67,6 +68,7 @@ async function saveState() { fraudContracts: state.fraudContracts, tokenHolderCache: state.tokenHolderCache, theme: state.theme, + debugMode: state.debugMode, currentView: state.currentView, selectedWallet: state.selectedWallet, selectedAddress: state.selectedAddress, @@ -126,6 +128,8 @@ async function loadState() { state.fraudContracts = saved.fraudContracts || []; state.tokenHolderCache = saved.tokenHolderCache || {}; state.theme = saved.theme || "system"; + state.debugMode = + saved.debugMode !== undefined ? saved.debugMode : false; state.currentView = saved.currentView || null; state.selectedWallet = saved.selectedWallet !== undefined ? saved.selectedWallet : null;