chore: set up commitlint, husky, and prettier (#1451)

* chore: add prettier configuration

Signed-off-by: Xe Iaso <me@xeiaso.net>

* format: run prettier tree-wide

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(prettier): ignore intentionally ungrammatical files

Signed-off-by: Xe Iaso <me@xeiaso.net>

* ci: add PR title lint rule

Signed-off-by: Xe Iaso <me@xeiaso.net>

* ci: add DCO check

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: add commitlint and husky

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: add CONTRIBUTING guidelines

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: set SKIP_INTEGRATION in precommit tests

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: update spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

* ci(dco): remove reopened trigger

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: remove dead file

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(prettier): don't format nginx includes

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso
2026-02-15 13:19:12 +00:00
committed by GitHub
parent 005750903d
commit bf5d66222c
165 changed files with 2854 additions and 1621 deletions

View File

@@ -6,7 +6,9 @@ interface ProcessOptions {
}
const getHardwareConcurrency = () =>
navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1;
navigator.hardwareConcurrency !== undefined
? navigator.hardwareConcurrency
: 1;
export default function process(
options: ProcessOptions,
@@ -25,7 +27,10 @@ export default function process(
workerMethod = "webcrypto";
}
if (navigator.userAgent.includes("Firefox") || navigator.userAgent.includes("Goanna")) {
if (
navigator.userAgent.includes("Firefox") ||
navigator.userAgent.includes("Goanna")
) {
console.log("Firefox detected, using pure-JS fallback");
workerMethod = "purejs";
}

View File

@@ -3,4 +3,4 @@ import fast from "./fast";
export default {
fast: fast,
slow: fast, // XXX(Xe): slow is deprecated, but keep this around in case anything goes bad
}
};

View File

@@ -2,13 +2,27 @@ import algorithms from "./algorithms";
const defaultDifficulty = 4;
const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement;
const difficultyInput: HTMLInputElement = document.getElementById("difficulty-input") as HTMLInputElement;
const algorithmSelect: HTMLSelectElement = document.getElementById("algorithm-select") as HTMLSelectElement;
const compareSelect: HTMLSelectElement = document.getElementById("compare-select") as HTMLSelectElement;
const header: HTMLTableRowElement = document.getElementById("table-header") as HTMLTableRowElement;
const headerCompare: HTMLTableSectionElement = document.getElementById("table-header-compare") as HTMLTableSectionElement;
const results: HTMLTableRowElement = document.getElementById("results") as HTMLTableRowElement;
const status: HTMLParagraphElement = document.getElementById(
"status",
) as HTMLParagraphElement;
const difficultyInput: HTMLInputElement = document.getElementById(
"difficulty-input",
) as HTMLInputElement;
const algorithmSelect: HTMLSelectElement = document.getElementById(
"algorithm-select",
) as HTMLSelectElement;
const compareSelect: HTMLSelectElement = document.getElementById(
"compare-select",
) as HTMLSelectElement;
const header: HTMLTableRowElement = document.getElementById(
"table-header",
) as HTMLTableRowElement;
const headerCompare: HTMLTableSectionElement = document.getElementById(
"table-header-compare",
) as HTMLTableSectionElement;
const results: HTMLTableRowElement = document.getElementById(
"results",
) as HTMLTableRowElement;
const setupControls = () => {
if (defaultDifficulty == null) {
@@ -41,7 +55,12 @@ const benchmarkTrial = async (stats, difficulty, algorithm, signal) => {
.join("");
const t0 = performance.now();
const { hash, nonce } = await process({ basePrefix: "/", version: "devel" }, challenge, Number(difficulty), signal);
const { hash, nonce } = await process(
{ basePrefix: "/", version: "devel" },
challenge,
Number(difficulty),
signal,
);
const t1 = performance.now();
console.log({ hash, nonce });

View File

@@ -29,22 +29,25 @@ const getAvailableLanguages = async () => {
}
try {
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`);
const response = await fetch(
`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`,
);
if (response.ok) {
const manifest = await response.json();
return manifest.supportedLanguages || ['en'];
return manifest.supportedLanguages || ["en"];
}
} catch (error) {
console.warn('Failed to load language manifest, falling back to default languages');
console.warn(
"Failed to load language manifest, falling back to default languages",
);
}
// Fallback to default languages if manifest loading fails
return ['en'];
return ["en"];
};
// Use the browser language from the HTML lang attribute which is set by the server settings or request headers
const getBrowserLanguage = async () =>
document.documentElement.lang;
const getBrowserLanguage = async () => document.documentElement.lang;
// Load translations from JSON files
const loadTranslations = async (lang) => {
@@ -54,12 +57,16 @@ const loadTranslations = async (lang) => {
}
try {
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`);
const response = await fetch(
`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`,
);
return await response.json();
} catch (error) {
console.warn(`Failed to load translations for ${lang}, falling back to English`);
if (lang !== 'en') {
return await loadTranslations('en');
console.warn(
`Failed to load translations for ${lang}, falling back to English`,
);
if (lang !== "en") {
return await loadTranslations("en");
}
throw error;
}
@@ -72,10 +79,10 @@ const getRedirectUrl = () => {
}
if (publicUrl && window.location.href.startsWith(publicUrl)) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('redir');
return urlParams.get("redir");
}
return window.location.href;
}
};
let translations = {};
let currentLang;
@@ -95,20 +102,28 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
const dependencies = [
{
name: "Web Workers",
msg: t('web_workers_error'),
msg: t("web_workers_error"),
value: window.Worker,
},
{
name: "Cookies",
msg: t('cookies_error'),
msg: t("cookies_error"),
value: navigator.cookieEnabled,
},
];
const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement;
const image: HTMLImageElement = document.getElementById("image") as HTMLImageElement;
const title: HTMLHeadingElement = document.getElementById("title") as HTMLHeadingElement;
const progress: HTMLDivElement = document.getElementById("progress") as HTMLDivElement;
const status: HTMLParagraphElement = document.getElementById(
"status",
) as HTMLParagraphElement;
const image: HTMLImageElement = document.getElementById(
"image",
) as HTMLImageElement;
const title: HTMLHeadingElement = document.getElementById(
"title",
) as HTMLHeadingElement;
const progress: HTMLDivElement = document.getElementById(
"progress",
) as HTMLDivElement;
const anubisVersion = j("anubis_version");
const basePrefix = j("anubis_base_prefix");
@@ -130,12 +145,12 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
progress.style.display = "none";
};
status.innerHTML = t('calculating');
status.innerHTML = t("calculating");
for (const { value, name, msg } of dependencies) {
if (!value) {
ohNoes({
titleMsg: `${t('missing_feature')} ${name}`,
titleMsg: `${t("missing_feature")} ${name}`,
statusMsg: msg,
imageSrc: imageURL("reject", anubisVersion, basePrefix),
});
@@ -148,20 +163,20 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
const process = algorithms[rules.algorithm];
if (!process) {
ohNoes({
titleMsg: t('challenge_error'),
statusMsg: t('challenge_error_msg'),
titleMsg: t("challenge_error"),
statusMsg: t("challenge_error_msg"),
imageSrc: imageURL("reject", anubisVersion, basePrefix),
});
return;
}
status.innerHTML = `${t('calculating_difficulty')} ${rules.difficulty}, `;
status.innerHTML = `${t("calculating_difficulty")} ${rules.difficulty}, `;
progress.style.display = "inline-block";
// the whole text, including "Speed:", as a single node, because some browsers
// (Firefox mobile) present screen readers with each node as a separate piece
// of text.
const rateText = document.createTextNode(`${t('speed')} 0kH/s`);
const rateText = document.createTextNode(`${t("speed")} 0kH/s`);
status.appendChild(rateText);
let lastSpeedUpdate = 0;
@@ -180,7 +195,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
// only update the speed every second so it's less visually distracting
if (delta - lastSpeedUpdate > 1000) {
lastSpeedUpdate = delta;
rateText.data = `${t('speed')} ${(iters / delta).toFixed(3)}kH/s`;
rateText.data = `${t("speed")} ${(iters / delta).toFixed(3)}kH/s`;
}
// the probability of still being on the page is (1 - likelihood) ^ iters.
// by definition, half of the time the progress bar only gets to half, so
@@ -192,13 +207,14 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
const distance = (1 - Math.pow(probability, 2)) * 100;
progress["aria-valuenow"] = distance;
if (progress.firstElementChild !== null) {
(progress.firstElementChild as HTMLElement).style.width = `${distance}%`;
(progress.firstElementChild as HTMLElement).style.width =
`${distance}%`;
}
if (probability < 0.1 && !showingApology) {
status.append(
document.createElement("br"),
document.createTextNode(t('verification_longer')),
document.createTextNode(t("verification_longer")),
);
showingApology = true;
}
@@ -208,7 +224,9 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
console.log({ hash, nonce });
if (userReadDetails) {
const container: HTMLDivElement = document.getElementById("progress") as HTMLDivElement;
const container: HTMLDivElement = document.getElementById(
"progress",
) as HTMLDivElement;
// Style progress bar as a continue button
container.style.display = "flex";
@@ -224,7 +242,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
container.style.outlineOffset = "2px";
container.style.width = "min(20rem, 90%)";
container.style.margin = "1rem auto 2rem";
container.innerHTML = t('finished_reading');
container.innerHTML = t("finished_reading");
function onDetailsExpand() {
const redir = getRedirectUrl();
@@ -255,8 +273,8 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
}
} catch (err) {
ohNoes({
titleMsg: t('calculation_error'),
statusMsg: `${t('calculation_error_msg')} ${err.message}`,
titleMsg: t("calculation_error"),
statusMsg: `${t("calculation_error_msg")} ${err.message}`,
imageSrc: imageURL("reject", anubisVersion, basePrefix),
});
}

View File

@@ -1,4 +1,4 @@
import { Sha256 } from '@aws-crypto/sha256-js';
import { Sha256 } from "@aws-crypto/sha256-js";
const calculateSHA256 = (text) => {
const hash = new Sha256();
@@ -12,7 +12,7 @@ function toHexString(arr: Uint8Array): string {
.join("");
}
addEventListener('message', async ({ data: eventData }) => {
addEventListener("message", async ({ data: eventData }) => {
const { data, difficulty, threads } = eventData;
let nonce = eventData.nonce;
const isMainThread = nonce === 0;
@@ -21,7 +21,7 @@ addEventListener('message', async ({ data: eventData }) => {
const requiredZeroBytes = Math.floor(difficulty / 2);
const isDifficultyOdd = difficulty % 2 !== 0;
for (; ;) {
for (;;) {
const hashBuffer = await calculateSHA256(data + nonce);
const hashArray = new Uint8Array(hashBuffer);
@@ -34,7 +34,7 @@ addEventListener('message', async ({ data: eventData }) => {
}
if (isValid && isDifficultyOdd) {
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
if (hashArray[requiredZeroBytes] >> 4 !== 0) {
isValid = false;
}
}
@@ -55,7 +55,7 @@ addEventListener('message', async ({ data: eventData }) => {
/* Truncate the decimal portion of the nonce. This is a bit of an evil bit
* hack, but it works reliably enough. The core of why this works is:
*
*
* > 13.4 % 1 !== 0
* true
* > 13 % 1 !== 0
@@ -70,4 +70,4 @@ addEventListener('message', async ({ data: eventData }) => {
postMessage(nonce);
}
}
});
});

View File

@@ -6,7 +6,10 @@ const calculateSHA256 = async (input: string) => {
};
const toHexString = (byteArray: Uint8Array) => {
return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
return byteArray.reduce(
(str, byte) => str + byte.toString(16).padStart(2, "0"),
"",
);
};
addEventListener("message", async ({ data: eventData }) => {
@@ -18,7 +21,7 @@ addEventListener("message", async ({ data: eventData }) => {
const requiredZeroBytes = Math.floor(difficulty / 2);
const isDifficultyOdd = difficulty % 2 !== 0;
for (; ;) {
for (;;) {
const hashBuffer = await calculateSHA256(data + nonce);
const hashArray = new Uint8Array(hashBuffer);
@@ -31,7 +34,7 @@ addEventListener("message", async ({ data: eventData }) => {
}
if (isValid && isDifficultyOdd) {
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
if (hashArray[requiredZeroBytes] >> 4 !== 0) {
isValid = false;
}
}
@@ -52,7 +55,7 @@ addEventListener("message", async ({ data: eventData }) => {
/* Truncate the decimal portion of the nonce. This is a bit of an evil bit
* hack, but it works reliably enough. The core of why this works is:
*
*
* > 13.4 % 1 !== 0
* true
* > 13 % 1 !== 0
@@ -67,4 +70,4 @@ addEventListener("message", async ({ data: eventData }) => {
postMessage(nonce);
}
}
});
});