mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-08-03 09:48:08 -04:00

* chore(web/js): delete proof-of-work-slow.mjs This code has served its purpose and now needs to be retired to the great beyond. There is no replacement for this, the fast implementation will be used instead. Signed-off-by: Xe Iaso <me@xeiaso.net> * chore(web): handle building multiple JS entrypoints and web workers Signed-off-by: Xe Iaso <me@xeiaso.net> * feat(web): rewrite frontend worker handling This completely rewrites how the proof of work challenge works based on feedback from browser engine developers and starts the process of making the proof of work function easier to change out. - Import @aws-crypto/sha256-js to use in Firefox as its implementation of WebCrypto doesn't jump directly from highly optimized browser internals to JIT-ed JavaScript like Chrome's seems to. - Move the worker code to `web/js/worker/*` with each worker named after the hashing method and hash method implementation it uses. - Update bench.mjs to import algorithms the new way. - Delete video.mjs, it was part of a legacy experiment that I never had time to finish. - Update LibreJS comment to add info about the use of @aws-crypto/sha256-js. - Also update my email to my @techaro.lol address. Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(web): don't hard dep webcrypto anymore Signed-off-by: Xe Iaso <me@xeiaso.net> * chore(lib/policy): start the deprecation process for slow This mostly adds a warning, but the "slow" method is in the process of being removed. Warn admins with slog.Warn. Signed-off-by: Xe Iaso <me@xeiaso.net> * docs: update CHANGELOG Signed-off-by: Xe Iaso <me@xeiaso.net> * feat(web/js): allow running Anubis in non-secure contexts Signed-off-by: Xe Iaso <me@xeiaso.net> * Update metadata check-spelling run (pull_request) for Xe/purge-slow Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com> on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev> --------- Signed-off-by: Xe Iaso <me@xeiaso.net> Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
240 lines
7.3 KiB
JavaScript
240 lines
7.3 KiB
JavaScript
import algorithms from "./algorithms/index.mjs";
|
|
|
|
// from Xeact
|
|
const u = (url = "", params = {}) => {
|
|
let result = new URL(url, window.location.href);
|
|
Object.entries(params).forEach(([k, v]) => result.searchParams.set(k, v));
|
|
return result.toString();
|
|
};
|
|
|
|
const imageURL = (mood, cacheBuster, basePrefix) =>
|
|
u(`${basePrefix}/.within.website/x/cmd/anubis/static/img/${mood}.webp`, {
|
|
cacheBuster,
|
|
});
|
|
|
|
// Detect available languages by loading the manifest
|
|
const getAvailableLanguages = async () => {
|
|
const basePrefix = JSON.parse(
|
|
document.getElementById("anubis_base_prefix").textContent,
|
|
);
|
|
|
|
try {
|
|
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'];
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to load language manifest, falling back to default languages');
|
|
}
|
|
|
|
// Fallback to default languages if manifest loading fails
|
|
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;
|
|
|
|
// Load translations from JSON files
|
|
const loadTranslations = async (lang) => {
|
|
const basePrefix = JSON.parse(
|
|
document.getElementById("anubis_base_prefix").textContent,
|
|
);
|
|
try {
|
|
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');
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
let translations = {};
|
|
let currentLang;
|
|
|
|
// Initialize translations
|
|
const initTranslations = async () => {
|
|
currentLang = await getBrowserLanguage();
|
|
translations = await loadTranslations(currentLang);
|
|
};
|
|
|
|
const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
|
|
|
(async () => {
|
|
// Initialize translations first
|
|
await initTranslations();
|
|
|
|
const dependencies = [
|
|
{
|
|
name: "Web Workers",
|
|
msg: t('web_workers_error'),
|
|
value: window.Worker,
|
|
},
|
|
{
|
|
name: "Cookies",
|
|
msg: t('cookies_error'),
|
|
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 details = document.querySelector("details");
|
|
let userReadDetails = false;
|
|
|
|
if (details) {
|
|
details.addEventListener("toggle", () => {
|
|
if (details.open) {
|
|
userReadDetails = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
const ohNoes = ({ titleMsg, statusMsg, imageSrc }) => {
|
|
title.innerHTML = titleMsg;
|
|
status.innerHTML = statusMsg;
|
|
image.src = imageSrc;
|
|
progress.style.display = "none";
|
|
};
|
|
|
|
status.innerHTML = t('calculating');
|
|
|
|
for (const { value, name, msg } of dependencies) {
|
|
if (!value) {
|
|
ohNoes({
|
|
titleMsg: `${t('missing_feature')} ${name}`,
|
|
statusMsg: msg,
|
|
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
const { challenge, rules } = JSON.parse(
|
|
document.getElementById("anubis_challenge").textContent,
|
|
);
|
|
|
|
const process = algorithms[rules.algorithm];
|
|
if (!process) {
|
|
ohNoes({
|
|
titleMsg: t('challenge_error'),
|
|
statusMsg: t('challenge_error_msg'),
|
|
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
|
});
|
|
return;
|
|
}
|
|
|
|
status.innerHTML = `${t('calculating_difficulty')} ${rules.report_as}, `;
|
|
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`);
|
|
status.appendChild(rateText);
|
|
|
|
let lastSpeedUpdate = 0;
|
|
let showingApology = false;
|
|
const likelihood = Math.pow(16, -rules.report_as);
|
|
|
|
try {
|
|
const t0 = Date.now();
|
|
const { hash, nonce } = await process(
|
|
{ basePrefix, version: anubisVersion },
|
|
challenge,
|
|
rules.difficulty,
|
|
null,
|
|
(iters) => {
|
|
const delta = Date.now() - t0;
|
|
// 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`;
|
|
}
|
|
// 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
|
|
// apply a polynomial ease-out function to move faster in the beginning
|
|
// and then slow down as things get increasingly unlikely. quadratic felt
|
|
// the best in testing, but this may need adjustment in the future.
|
|
|
|
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 (probability < 0.1 && !showingApology) {
|
|
status.append(
|
|
document.createElement("br"),
|
|
document.createTextNode(t('verification_longer')),
|
|
);
|
|
showingApology = true;
|
|
}
|
|
},
|
|
);
|
|
const t1 = Date.now();
|
|
console.log({ hash, nonce });
|
|
|
|
if (userReadDetails) {
|
|
const container = document.getElementById("progress");
|
|
|
|
// Style progress bar as a continue button
|
|
container.style.display = "flex";
|
|
container.style.alignItems = "center";
|
|
container.style.justifyContent = "center";
|
|
container.style.height = "2rem";
|
|
container.style.borderRadius = "1rem";
|
|
container.style.cursor = "pointer";
|
|
container.style.background = "#b16286";
|
|
container.style.color = "white";
|
|
container.style.fontWeight = "bold";
|
|
container.style.outline = "4px solid #b16286";
|
|
container.style.outlineOffset = "2px";
|
|
container.style.width = "min(20rem, 90%)";
|
|
container.style.margin = "1rem auto 2rem";
|
|
container.innerHTML = t('finished_reading');
|
|
|
|
function onDetailsExpand() {
|
|
const redir = window.location.href;
|
|
window.location.replace(
|
|
u(`${basePrefix}/.within.website/x/cmd/anubis/api/pass-challenge`, {
|
|
response: hash,
|
|
nonce,
|
|
redir,
|
|
elapsedTime: t1 - t0,
|
|
}),
|
|
);
|
|
}
|
|
|
|
container.onclick = onDetailsExpand;
|
|
setTimeout(onDetailsExpand, 30000);
|
|
} else {
|
|
const redir = window.location.href;
|
|
window.location.replace(
|
|
u(`${basePrefix}/.within.website/x/cmd/anubis/api/pass-challenge`, {
|
|
response: hash,
|
|
nonce,
|
|
redir,
|
|
elapsedTime: t1 - t0,
|
|
}),
|
|
);
|
|
}
|
|
} catch (err) {
|
|
ohNoes({
|
|
titleMsg: t('calculation_error'),
|
|
statusMsg: `${t('calculation_error_msg')} ${err.message}`,
|
|
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
|
});
|
|
}
|
|
})();
|