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;