Remove hard-coded upstream tracking

This commit is contained in:
2026-08-25 16:24:51 +00:00
parent b956829560
commit 52969fd1a8
4 changed files with 102 additions and 0 deletions
+5
View File
@@ -15,6 +15,8 @@ RUN --mount=type=cache,target=/root/.npm npm ci
COPY upstream/HubFrontend/ ./
COPY docker/hcaptcha-sitekey.patch /tmp/hcaptcha-sitekey.patch
COPY docker/remove-analytics.mjs /tmp/remove-analytics.mjs
COPY docker/replace-fallback-avatar.mjs /tmp/replace-fallback-avatar.mjs
RUN if grep -q 'sitekey="1788882d-3695-4807-abac-7d7166ec6325"' src/routes/auth/login.js; then \
patch -p1 < /tmp/hcaptcha-sitekey.patch; \
@@ -25,6 +27,9 @@ RUN if grep -q 'sitekey="1788882d-3695-4807-abac-7d7166ec6325"' src/routes/auth/
exit 1; \
fi
RUN node /tmp/remove-analytics.mjs
RUN node /tmp/replace-fallback-avatar.mjs
ARG ASSETS_BASE=""
ARG VITE_USE_MULTIHUB=false
ARG VITE_MULTIHUB_DISCOVERY=""
+9
View File
@@ -46,6 +46,15 @@ example, use `"abbr": "api"` with `"prefix": "/api"`.
The frontend uses the Vite values during the image build. Rebuild the image after
you change `.env`, including the hCaptcha site key.
The build removes the hard-coded upstream Google Analytics integration and its
consent dialog. If upstream removes the known integration, the build skips this
step. If an unknown Analytics implementation remains, the build stops instead
of publishing it.
The build also replaces the hard-coded external login avatar with the bundled
`/logo.png` file. If upstream removes the known avatar URL, the build skips this
step.
## Start the deployment
```bash
+56
View File
@@ -0,0 +1,56 @@
import { readFileSync, writeFileSync } from "node:fs";
const appPath = "src/App.js";
let source = readFileSync(appPath, "utf8");
const analyticsStart = " const [cookieSettings, setCookieSettings] = useState(localStorage.getItem(\"cookie-settings\"));";
const analyticsEnd = " const hasSpeedDial =";
const consentStart = " {!window.isElectron && cookieSettings === null && !sidebarForceHidden && (";
const consentEnd = " <SimpleBar ";
const hasKnownAnalytics = source.includes("G-SLZ5TY9MVN");
const hasKnownState = source.includes(analyticsStart);
const hasKnownConsent = source.includes(consentStart);
if (hasKnownState && hasKnownConsent) {
const analyticsStartIndex = source.indexOf(analyticsStart);
const analyticsEndIndex = source.indexOf(analyticsEnd, analyticsStartIndex);
const consentStartIndex = source.indexOf(consentStart);
const consentEndIndex = source.indexOf(consentEnd, consentStartIndex);
if (analyticsEndIndex === -1 || consentEndIndex === -1) {
throw new Error("Cannot locate the complete upstream Analytics implementation.");
}
source = source.slice(0, analyticsStartIndex) + source.slice(analyticsEndIndex);
const updatedConsentStartIndex = source.indexOf(consentStart);
const updatedConsentEndIndex = source.indexOf(consentEnd, updatedConsentStartIndex);
source = source.slice(0, updatedConsentStartIndex) + source.slice(updatedConsentEndIndex);
const materialImport = "Typography, Button, Grid";
if (!source.includes(materialImport)) {
throw new Error("Cannot remove the Button import used by the upstream consent dialog.");
}
source = source.replace(materialImport, "Typography, Grid");
console.log("Removed the upstream Google Analytics integration and consent dialog.");
} else if (hasKnownAnalytics || hasKnownState || hasKnownConsent) {
throw new Error("Unsupported upstream Analytics implementation.");
} else {
console.log("Upstream does not contain the known Analytics implementation; skip removal.");
}
const unsupportedAnalytics = [
/googletagmanager\.com/i,
/google-analytics\.com/i,
/\bgtag\s*\(/,
/\bdataLayer\b/,
/cookie-settings/,
];
if (unsupportedAnalytics.some(pattern => pattern.test(source))) {
throw new Error("An unsupported Analytics implementation remains in src/App.js.");
}
writeFileSync(appPath, source);
+32
View File
@@ -0,0 +1,32 @@
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
import { extname, join } from "node:path";
const sourceRoot = "src";
const upstreamAvatar = "https://charlws.com/me.gif";
const localAvatar = "/logo.png";
const sourceExtensions = new Set([".js", ".jsx", ".ts", ".tsx"]);
let replacementCount = 0;
function updateDirectory(directory) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory()) {
updateDirectory(path);
} else if (sourceExtensions.has(extname(entry.name))) {
const source = readFileSync(path, "utf8");
const occurrences = source.split(upstreamAvatar).length - 1;
if (occurrences > 0) {
writeFileSync(path, source.replaceAll(upstreamAvatar, localAvatar));
replacementCount += occurrences;
}
}
}
}
updateDirectory(sourceRoot);
if (replacementCount === 0) {
console.log("Upstream does not contain the known fallback avatar; skip replacement.");
} else {
console.log(`Replaced ${replacementCount} upstream fallback avatar reference(s).`);
}