aboutsummaryrefslogtreecommitdiff
path: root/cmd/anubis/static/js
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2025-01-18 17:09:02 -0500
committerXe Iaso <me@xeiaso.net>2025-01-18 17:09:02 -0500
commitbcc7c0d28e8c225c730b5574718aa7d963c1cdea (patch)
tree08b6abb8fa8009600a07ad2548a9b968ca0f7549 /cmd/anubis/static/js
parentb586be783c91f4d579bfa3907e8062f5c6ada739 (diff)
downloadx-bcc7c0d28e8c225c730b5574718aa7d963c1cdea.tar.xz
x-bcc7c0d28e8c225c730b5574718aa7d963c1cdea.zip
cmd: add Anubis
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd/anubis/static/js')
-rw-r--r--cmd/anubis/static/js/main.mjs47
-rw-r--r--cmd/anubis/static/js/proof-of-work.mjs65
2 files changed, 112 insertions, 0 deletions
diff --git a/cmd/anubis/static/js/main.mjs b/cmd/anubis/static/js/main.mjs
new file mode 100644
index 0000000..d13e1ad
--- /dev/null
+++ b/cmd/anubis/static/js/main.mjs
@@ -0,0 +1,47 @@
+import { process } from './proof-of-work.mjs';
+
+// from Xeact
+const u = (url = "", params = {}) => {
+ let result = new URL(url, window.location.href);
+ Object.entries(params).forEach((kv) => {
+ let [k, v] = kv;
+ result.searchParams.set(k, v);
+ });
+ return result.toString();
+};
+
+(async () => {
+ const status = document.getElementById('status');
+ const image = document.getElementById('image');
+ const title = document.getElementById('title');
+
+ status.innerHTML = 'Calculating...';
+
+ const { challenge, difficulty } = 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 => {
+ status.innerHTML = `Failed to fetch config: ${err.message}`;
+ image.src = "/.within.website/x/cmd/anubis/static/img/sad.webp";
+ throw err;
+ });
+
+ status.innerHTML = `Calculating...<br/>Difficulty: ${difficulty}`;
+
+ const t0 = Date.now();
+ const { hash, nonce } = await process(challenge, difficulty);
+ const t1 = Date.now();
+
+ title.innerHTML = "Success!";
+ status.innerHTML = `Done! Took ${t1 - t0}ms, ${nonce} iterations`;
+ image.src = "/.within.website/x/cmd/anubis/static/img/happy.webp";
+
+ setTimeout(() => {
+ const redir = window.location.href;
+ window.location.href = u("/.within.website/x/cmd/anubis/api/pass-challenge", { response: hash, nonce, redir, elapsedTime: t1 - t0 });
+ }, 2000);
+})(); \ No newline at end of file
diff --git a/cmd/anubis/static/js/proof-of-work.mjs b/cmd/anubis/static/js/proof-of-work.mjs
new file mode 100644
index 0000000..4019fec
--- /dev/null
+++ b/cmd/anubis/static/js/proof-of-work.mjs
@@ -0,0 +1,65 @@
+// https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm
+
+export function process(data, difficulty = 5) {
+ return new Promise((resolve, reject) => {
+ let webWorkerURL = URL.createObjectURL(new Blob([
+ '(', processTask(), ')()'
+ ], { type: 'application/javascript' }));
+
+ let worker = new Worker(webWorkerURL);
+
+ worker.onmessage = (event) => {
+ worker.terminate();
+ resolve(event.data);
+ };
+
+ worker.onerror = (event) => {
+ worker.terminate();
+ reject();
+ };
+
+ worker.postMessage({
+ data,
+ difficulty
+ });
+
+ URL.revokeObjectURL(webWorkerURL);
+ });
+}
+
+function processTask() {
+ return function () {
+ function sha256(text) {
+ return new Promise((resolve, reject) => {
+ let buffer = (new TextEncoder).encode(text);
+
+ crypto.subtle.digest('SHA-256', buffer.buffer).then(result => {
+ resolve(Array.from(new Uint8Array(result)).map(
+ c => c.toString(16).padStart(2, '0')
+ ).join(''));
+ }, reject);
+ });
+ }
+
+ addEventListener('message', async (event) => {
+ let data = event.data.data;
+ let difficulty = event.data.difficulty;
+
+ let hash;
+ let nonce = 0;
+ do {
+ hash = await sha256(data + nonce++);
+ } while (hash.substr(0, difficulty) !== Array(difficulty + 1).join('0'));
+
+ nonce -= 1; // last nonce was post-incremented
+
+ postMessage({
+ hash,
+ data,
+ difficulty,
+ nonce,
+ });
+ });
+ }.toString();
+}
+