diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 73606ea..9cd1560 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -214,6 +214,7 @@ nicksnyder
nobots
NONINFRINGEMENT
nosleep
+nullglob
OCOB
ogtag
oklch
@@ -278,6 +279,7 @@ Seo
setsebool
shellcheck
shirou
+shopt
Sidetrade
simprint
sitemap
diff --git a/lib/challenge/metarefresh/metarefresh_templ.go b/lib/challenge/metarefresh/metarefresh_templ.go
index 0c7a837..048260b 100644
--- a/lib/challenge/metarefresh/metarefresh_templ.go
+++ b/lib/challenge/metarefresh/metarefresh_templ.go
@@ -93,9 +93,9 @@ func page(redir string, difficulty int, loc *localization.SimpleLocalizer) templ
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
- templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d; url=%s", difficulty, redir))
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d; url=%s", difficulty+1, redir))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 16, Col: 83}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 16, Col: 85}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
diff --git a/lib/challenge/preact/build.sh b/lib/challenge/preact/build.sh
index c7f2e3f..ebd6d59 100755
--- a/lib/challenge/preact/build.sh
+++ b/lib/challenge/preact/build.sh
@@ -40,9 +40,9 @@ for the JavaScript code in this page.
mkdir -p static/js
-for file in js/*.jsx; do
+for file in js/*.tsx; do
filename="${file##*/}" # Extracts "app.jsx" from "./js/app.jsx"
- output="${filename%.jsx}.js" # Changes "app.jsx" to "app.js"
+ output="${filename%.tsx}.js" # Changes "app.jsx" to "app.js"
echo $output
esbuild "${file}" --minify --bundle --outfile=static/"${output}" --banner:js="${LICENSE}"
diff --git a/lib/challenge/preact/js/app.jsx b/lib/challenge/preact/js/app.jsx
deleted file mode 100644
index 4aafae9..0000000
--- a/lib/challenge/preact/js/app.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { render, h, Fragment } from 'preact';
-import { useState, useEffect } from 'preact/hooks';
-import { g, j, u, x } from "./xeact.js";
-import { Sha256 } from '@aws-crypto/sha256-js';
-
-/** @jsx h */
-/** @jsxFrag Fragment */
-
-function toHexString(arr) {
- return Array.from(arr)
- .map((c) => c.toString(16).padStart(2, "0"))
- .join("");
-}
-
-const App = () => {
- const [state, setState] = useState(null);
- const [imageURL, setImageURL] = useState(null);
- const [passed, setPassed] = useState(false);
- const [challenge, setChallenge] = useState(null);
-
- useEffect(() => {
- setState(j("preact_info"));
- });
-
- useEffect(() => {
- setImageURL(state.pensive_url);
- const hash = new Sha256('');
- hash.update(state.challenge);
- setChallenge(toHexString(hash.digestSync()));
- }, [state]);
-
- useEffect(() => {
- const timer = setTimeout(() => {
- setPassed(true);
- }, state.difficulty * 125);
-
- return () => clearTimeout(timer);
- }, [challenge]);
-
- useEffect(() => {
- window.location.href = u(state.redir, {
- result: challenge,
- });
- }, [passed]);
-
- return (
- <>
- {imageURL !== null && (
-
- )}
- {state !== null && (
- <>
-
{state.loading_message}
- {state.connection_security_message}
- >
- )}
- >
- );
-};
-
-x(g("app"));
-render(, g("app"));
\ No newline at end of file
diff --git a/lib/challenge/preact/js/app.tsx b/lib/challenge/preact/js/app.tsx
new file mode 100644
index 0000000..efa90bd
--- /dev/null
+++ b/lib/challenge/preact/js/app.tsx
@@ -0,0 +1,87 @@
+import { render, h, Fragment } from "preact";
+import { useState, useEffect } from "preact/hooks";
+import { g, j, r, u, x } from "./xeact.js";
+import { Sha256 } from "@aws-crypto/sha256-js";
+
+/** @jsx h */
+/** @jsxFrag Fragment */
+
+function toHexString(arr: Uint8Array) {
+ return Array.from(arr)
+ .map((c) => c.toString(16).padStart(2, "0"))
+ .join("");
+}
+
+interface PreactInfo {
+ redir: string;
+ challenge: string;
+ difficulty: number;
+ connection_security_message: string;
+ loading_message: string;
+ pensive_url: string;
+}
+
+const App = () => {
+ const [state, setState] = useState();
+ const [imageURL, setImageURL] = useState(null);
+ const [passed, setPassed] = useState(false);
+ const [challenge, setChallenge] = useState(null);
+
+ useEffect(() => {
+ setState(j("preact_info"));
+ });
+
+ useEffect(() => {
+ if (state === undefined) {
+ return;
+ }
+
+ setImageURL(state?.pensive_url);
+ const hash = new Sha256("");
+ hash.update(state.challenge);
+ setChallenge(toHexString(hash.digestSync()));
+ }, [state]);
+
+ useEffect(() => {
+ if (state === undefined) {
+ return;
+ }
+
+ const timer = setTimeout(() => {
+ setPassed(true);
+ }, state?.difficulty * 125);
+
+ return () => clearTimeout(timer);
+ }, [challenge]);
+
+ useEffect(() => {
+ if (state === undefined) {
+ return;
+ }
+
+ if (challenge === null) {
+ return;
+ }
+
+ window.location.href = u(state.redir, {
+ result: challenge,
+ });
+ }, [passed]);
+
+ return (
+ <>
+ {imageURL !== null && (
+
+ )}
+ {state !== undefined && (
+ <>
+ {state.loading_message}
+ {state.connection_security_message}
+ >
+ )}
+ >
+ );
+};
+
+x(g("app"));
+render(, g("app"));
diff --git a/web/build.sh b/web/build.sh
index 84aa654..d0818e0 100755
--- a/web/build.sh
+++ b/web/build.sh
@@ -39,9 +39,18 @@ for the JavaScript code in this page.
mkdir -p static/locales
cp ../lib/localization/locales/*.json static/locales/
-for file in js/*.mjs js/worker/*.mjs; do
- esbuild "${file}" --sourcemap --bundle --minify --outfile=static/"${file}" --banner:js="${LICENSE}"
- gzip -f -k -n static/${file}
- zstd -f -k --ultra -22 static/${file}
- brotli -fZk static/${file}
+shopt -s nullglob globstar
+
+for file in js/**/*.ts js/**/*.mjs; do
+ out="static/${file}"
+ if [[ "$file" == *.ts ]]; then
+ out="static/${file%.ts}.mjs"
+ fi
+
+ mkdir -p "$(dirname "$out")"
+
+ esbuild "$file" --sourcemap --bundle --minify --outfile="$out" --banner:js="$LICENSE"
+ gzip -f -k -n "$out"
+ zstd -f -k --ultra -22 "$out"
+ brotli -fZk "$out"
done
diff --git a/web/js/algorithms/fast.mjs b/web/js/algorithms/fast.ts
similarity index 68%
rename from web/js/algorithms/fast.mjs
rename to web/js/algorithms/fast.ts
index ee08a19..178cef8 100644
--- a/web/js/algorithms/fast.mjs
+++ b/web/js/algorithms/fast.ts
@@ -1,11 +1,21 @@
+type ProgressCallback = (nonce: number) => void;
+
+interface ProcessOptions {
+ basePrefix: string;
+ version: string;
+}
+
+const getHardwareConcurrency = () =>
+ navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1;
+
export default function process(
- { basePrefix, version },
- data,
- difficulty = 5,
- signal = null,
- progressCallback = null,
- threads = Math.trunc(Math.max(navigator.hardwareConcurrency / 2, 1)),
-) {
+ options: ProcessOptions,
+ data: string,
+ difficulty: number = 5,
+ signal: AbortSignal | null = null,
+ progressCallback?: ProgressCallback,
+ threads: number = Math.trunc(Math.max(getHardwareConcurrency() / 2, 1)),
+): Promise {
console.debug("fast algo");
let workerMethod = window.crypto !== undefined ? "webcrypto" : "purejs";
@@ -16,13 +26,17 @@ export default function process(
}
return new Promise((resolve, reject) => {
- let webWorkerURL = `${basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${workerMethod}.mjs?cacheBuster=${version}`;
+ let webWorkerURL = `${options.basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${workerMethod}.mjs?cacheBuster=${options.version}`;
- console.log(webWorkerURL);
-
- const workers = [];
+ const workers: Worker[] = [];
let settled = false;
+ const onAbort = () => {
+ console.log("PoW aborted");
+ cleanup();
+ reject(new DOMException("Aborted", "AbortError"));
+ };
+
const cleanup = () => {
if (settled) {
return;
@@ -34,12 +48,6 @@ export default function process(
}
};
- const onAbort = () => {
- console.log("PoW aborted");
- cleanup();
- reject(new DOMException("Aborted", "AbortError"));
- };
-
if (signal != null) {
if (signal.aborted) {
return onAbort();
diff --git a/web/js/algorithms/index.mjs b/web/js/algorithms/index.ts
similarity index 80%
rename from web/js/algorithms/index.mjs
rename to web/js/algorithms/index.ts
index cc1ae5d..5b57183 100644
--- a/web/js/algorithms/index.mjs
+++ b/web/js/algorithms/index.ts
@@ -1,4 +1,4 @@
-import fast from "./fast.mjs";
+import fast from "./fast";
export default {
fast: fast,
diff --git a/web/js/bench.mjs b/web/js/bench.ts
similarity index 78%
rename from web/js/bench.mjs
rename to web/js/bench.ts
index a3c4f09..719b167 100644
--- a/web/js/bench.mjs
+++ b/web/js/bench.ts
@@ -1,20 +1,24 @@
-import algorithms from "./algorithms/index.mjs";
+import algorithms from "./algorithms";
const defaultDifficulty = 4;
-const status = document.getElementById("status");
-const difficultyInput = document.getElementById("difficulty-input");
-const algorithmSelect = document.getElementById("algorithm-select");
-const compareSelect = document.getElementById("compare-select");
-const header = document.getElementById("table-header");
-const headerCompare = document.getElementById("table-header-compare");
-const results = document.getElementById("results");
+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 = () => {
- difficultyInput.value = defaultDifficulty;
+ if (defaultDifficulty == null) {
+ return;
+ }
+
+ difficultyInput.value = defaultDifficulty.toString();
for (const alg of Object.keys(algorithms)) {
const option1 = document.createElement("option");
- algorithmSelect.append(option1);
+ algorithmSelect?.append(option1);
const option2 = document.createElement("option");
compareSelect.append(option2);
option1.value = option1.innerText = option2.value = option2.innerText = alg;
@@ -116,13 +120,13 @@ const benchmarkLoop = async (controller) => {
await benchmarkLoop(controller);
};
-let controller = null;
+let controller: AbortController | null = null;
const reset = () => {
stats.time = stats.iters = 0;
comparison.time = comparison.iters = 0;
results.innerHTML = status.innerText = "";
- const table = results.parentElement;
+ const table = results.parentElement as HTMLElement;
if (compareSelect.value !== "NONE") {
table.style.gridTemplateColumns = "repeat(4,auto)";
header.style.display = "none";
diff --git a/web/js/main.mjs b/web/js/main.ts
similarity index 84%
rename from web/js/main.mjs
rename to web/js/main.ts
index 1d68627..e37c536 100644
--- a/web/js/main.mjs
+++ b/web/js/main.ts
@@ -1,12 +1,21 @@
-import algorithms from "./algorithms/index.mjs";
+import algorithms from "./algorithms";
// from Xeact
-const u = (url = "", params = {}) => {
+const u = (url: string = "", params: Record = {}) => {
let result = new URL(url, window.location.href);
Object.entries(params).forEach(([k, v]) => result.searchParams.set(k, v));
return result.toString();
};
+const j = (id: string): any | null => {
+ const elem = document.getElementById(id);
+ if (elem === null) {
+ return null;
+ }
+
+ return JSON.parse(elem.textContent);
+};
+
const imageURL = (mood, cacheBuster, basePrefix) =>
u(`${basePrefix}/.within.website/x/cmd/anubis/static/img/${mood}.webp`, {
cacheBuster,
@@ -14,9 +23,10 @@ const imageURL = (mood, cacheBuster, basePrefix) =>
// Detect available languages by loading the manifest
const getAvailableLanguages = async () => {
- const basePrefix = JSON.parse(
- document.getElementById("anubis_base_prefix").textContent,
- );
+ const basePrefix = j("anubis_base_prefix");
+ if (basePrefix === null) {
+ return;
+ }
try {
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`);
@@ -38,9 +48,11 @@ const getBrowserLanguage = async () =>
// Load translations from JSON files
const loadTranslations = async (lang) => {
- const basePrefix = JSON.parse(
- document.getElementById("anubis_base_prefix").textContent,
- );
+ const basePrefix = j("anubis_base_prefix");
+ if (basePrefix === null) {
+ return;
+ }
+
try {
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`);
return await response.json();
@@ -54,9 +66,10 @@ const loadTranslations = async (lang) => {
};
const getRedirectUrl = () => {
- const publicUrl = JSON.parse(
- document.getElementById("anubis_public_url").textContent,
- );
+ const publicUrl = j("anubis_public_url");
+ if (publicUrl === null) {
+ return;
+ }
if (publicUrl && window.location.href.startsWith(publicUrl)) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('redir');
@@ -91,16 +104,14 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
value: navigator.cookieEnabled,
},
];
- const status = document.getElementById("status");
- const image = document.getElementById("image");
- const title = document.getElementById("title");
- const progress = document.getElementById("progress");
- const anubisVersion = JSON.parse(
- document.getElementById("anubis_version").textContent,
- );
- const basePrefix = JSON.parse(
- document.getElementById("anubis_base_prefix").textContent,
- );
+
+ 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");
const details = document.querySelector("details");
let userReadDetails = false;
@@ -132,9 +143,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
}
}
- const { challenge, rules } = JSON.parse(
- document.getElementById("anubis_challenge").textContent,
- );
+ const { challenge, rules } = j("anubis_challenge");
const process = algorithms[rules.algorithm];
if (!process) {
@@ -182,7 +191,9 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
const probability = Math.pow(1 - likelihood, iters);
const distance = (1 - Math.pow(probability, 2)) * 100;
progress["aria-valuenow"] = distance;
- progress.firstElementChild.style.width = `${distance}%`;
+ if (progress.firstElementChild !== null) {
+ (progress.firstElementChild as HTMLElement).style.width = `${distance}%`;
+ }
if (probability < 0.1 && !showingApology) {
status.append(
@@ -197,7 +208,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
console.log({ hash, nonce });
if (userReadDetails) {
- const container = document.getElementById("progress");
+ const container: HTMLDivElement = document.getElementById("progress") as HTMLDivElement;
// Style progress bar as a continue button
container.style.display = "flex";
diff --git a/web/js/worker/sha256-purejs.mjs b/web/js/worker/sha256-purejs.ts
similarity index 97%
rename from web/js/worker/sha256-purejs.mjs
rename to web/js/worker/sha256-purejs.ts
index 3211b44..6906061 100644
--- a/web/js/worker/sha256-purejs.mjs
+++ b/web/js/worker/sha256-purejs.ts
@@ -6,7 +6,7 @@ const calculateSHA256 = (text) => {
return hash.digest();
};
-function toHexString(arr) {
+function toHexString(arr: Uint8Array): string {
return Array.from(arr)
.map((c) => c.toString(16).padStart(2, "0"))
.join("");
diff --git a/web/js/worker/sha256-webcrypto.mjs b/web/js/worker/sha256-webcrypto.ts
similarity index 94%
rename from web/js/worker/sha256-webcrypto.mjs
rename to web/js/worker/sha256-webcrypto.ts
index c2b071a..c83f466 100644
--- a/web/js/worker/sha256-webcrypto.mjs
+++ b/web/js/worker/sha256-webcrypto.ts
@@ -1,10 +1,11 @@
const encoder = new TextEncoder();
-const calculateSHA256 = async (input) => {
+
+const calculateSHA256 = async (input: string) => {
const data = encoder.encode(input);
return await crypto.subtle.digest("SHA-256", data);
};
-const toHexString = (byteArray) => {
+const toHexString = (byteArray: Uint8Array) => {
return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
};