import argon2id from "./algos/argon2id.mjs";
import fast from "./algos/fast.mjs";
import slow from "./algos/slow.mjs";
import sha256 from "./algos/sha256.mjs";
import { testVideo } from "./video.mjs";
import { u } from "./xeact.mjs";
const algorithms = {
"argon2id": argon2id,
"fast": fast,
"slow": slow,
"sha256": sha256,
};
const imageURL = (mood, cacheBuster) =>
u(`/.within.website/x/cmd/anubis/static/img/${mood}.webp`, { cacheBuster });
const dependencies = [
{
name: "WebCrypto",
msg: "Your browser doesn't have a functioning web.crypto element. Are you viewing this over a secure context?",
value: window.crypto,
},
{
name: "Web Workers",
msg: "Your browser doesn't support web workers (Anubis uses this to avoid freezing your browser). Do you have a plugin like JShelter installed?",
value: window.Worker,
},
{
name: "WebAssembly",
msg: "Your browser doesn't have WebAssembly support. If you are running a big endian system, I'm sorry but this is something we can't work around with a polyfill.",
value: window.WebAssembly,
},
];
function showContinueBar(hash, nonce, t0, t1) {
const barContainer = document.createElement("div");
barContainer.style.marginTop = "1rem";
barContainer.style.width = "100%";
barContainer.style.maxWidth = "32rem";
barContainer.style.background = "#3c3836";
barContainer.style.borderRadius = "4px";
barContainer.style.overflow = "hidden";
barContainer.style.cursor = "pointer";
barContainer.style.height = "2rem";
barContainer.style.marginLeft = "auto";
barContainer.style.marginRight = "auto";
barContainer.title = "Click to continue";
const barInner = document.createElement("div");
barInner.className = "bar-inner";
barInner.style.display = "flex";
barInner.style.alignItems = "center";
barInner.style.justifyContent = "center";
barInner.style.color = "white";
barInner.style.fontWeight = "bold";
barInner.style.height = "100%";
barInner.style.width = "0";
barInner.innerText = "I've finished reading, continue →";
barContainer.appendChild(barInner);
document.body.appendChild(barContainer);
requestAnimationFrame(() => {
barInner.style.width = "100%";
});
barContainer.onclick = () => {
const redir = window.location.href;
window.location.replace(
u("/.within.website/x/cmd/anubis/api/pass-challenge", {
response: hash,
nonce,
redir,
elapsedTime: t1 - t0
})
);
};
}
(async () => {
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 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";
};
if (!window.isSecureContext) {
ohNoes({
titleMsg: "Your context is not secure!",
statusMsg: `Try connecting over HTTPS or let the admin know to set up HTTPS. For more information, see MDN.`,
imageSrc: imageURL("reject", anubisVersion),
});
return;
}
// const testarea = document.getElementById('testarea');
// const videoWorks = await testVideo(testarea);
// console.log(`videoWorks: ${videoWorks}`);
// if (!videoWorks) {
// title.innerHTML = "Oh no!";
// status.innerHTML = "Checks failed. Please check your browser's settings and try again.";
// image.src = imageURL("reject");
// progress.style.display = "none";
// return;
// }
status.innerHTML = 'Calculating...';
for (const { value, name, msg } of dependencies) {
if (!value) {
ohNoes({
titleMsg: `Missing feature ${name}`,
statusMsg: msg,
imageSrc: imageURL("reject", anubisVersion),
});
}
}
const { challenge, rules } = await fetch("/.within.website/x/cmd/anubis/api/make-challenge", { method: "POST" })
.then(r => {
if (!r.ok) throw new Error("Failed to fetch config");
return r.json();
})
.catch(err => {
ohNoes({
titleMsg: "Internal error!",
statusMsg: `Failed to fetch challenge config: ${err.message}`,
imageSrc: imageURL("reject", anubisVersion),
});
throw err;
});
const process = algorithms[rules.algorithm];
if (!process) {
ohNoes({
titleMsg: "Challenge error!",
statusMsg: `Failed to resolve check algorithm. You may want to reload the page.`,
imageSrc: imageURL("reject", anubisVersion),
});
return;
}
status.innerHTML = `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("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(
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 = `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(
"Verification is taking longer than expected. Please do not refresh the page.",
),
);
showingApology = true;
}
},
);
const t1 = Date.now();
console.log({ hash, nonce });
title.innerHTML = "Success!";
status.innerHTML = `Done! Took ${t1 - t0}ms, ${nonce} iterations`;
image.src = imageURL("happy", anubisVersion);
progress.style.display = "none";
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 = "I've finished reading, continue →";
function onDetailsExpand() {
const redir = window.location.href;
window.location.replace(
u("/.within.website/x/cmd/anubis/api/pass-challenge", {
response: hash,
nonce,
redir,
elapsedTime: t1 - t0
}),
);
}
container.onclick = onDetailsExpand;
setTimeout(onDetailsExpand, 30000);
} else {
setTimeout(() => {
const redir = window.location.href;
window.location.replace(
u("/.within.website/x/cmd/anubis/api/pass-challenge", {
response: hash,
nonce,
redir,
elapsedTime: t1 - t0
}),
);
}, 250);
}
} catch (err) {
ohNoes({
titleMsg: "Calculation error!",
statusMsg: `Failed to calculate challenge: ${err.message}`,
imageSrc: imageURL("reject", anubisVersion),
});
}
})();