Weighs the soul of incoming HTTP requests to stop AI crawlers
1import processFast from "./proof-of-work.mjs";
2import processSlow from "./proof-of-work-slow.mjs";
3
4const defaultDifficulty = 4;
5const algorithms = {
6 fast: processFast,
7 slow: processSlow,
8};
9
10const status = document.getElementById("status");
11const difficultyInput = document.getElementById("difficulty-input");
12const algorithmSelect = document.getElementById("algorithm-select");
13const compareSelect = document.getElementById("compare-select");
14const header = document.getElementById("table-header");
15const headerCompare = document.getElementById("table-header-compare");
16const results = document.getElementById("results");
17
18const setupControls = () => {
19 difficultyInput.value = defaultDifficulty;
20 for (const alg of Object.keys(algorithms)) {
21 const option1 = document.createElement("option");
22 algorithmSelect.append(option1);
23 const option2 = document.createElement("option");
24 compareSelect.append(option2);
25 option1.value = option1.innerText = option2.value = option2.innerText = alg;
26 }
27};
28
29const benchmarkTrial = async (stats, difficulty, algorithm, signal) => {
30 if (!(difficulty >= 1)) {
31 throw new Error(`Invalid difficulty: ${difficulty}`);
32 }
33 const process = algorithms[algorithm];
34 if (process == null) {
35 throw new Error(`Unknown algorithm: ${algorithm}`);
36 }
37
38 const rawChallenge = new Uint8Array(32);
39 crypto.getRandomValues(rawChallenge);
40 const challenge = Array.from(rawChallenge)
41 .map((c) => c.toString(16).padStart(2, "0"))
42 .join("");
43
44 const t0 = performance.now();
45 const { hash, nonce } = await process(challenge, Number(difficulty), signal);
46 const t1 = performance.now();
47 console.log({ hash, nonce });
48
49 stats.time += t1 - t0;
50 stats.iters += nonce;
51
52 return { time: t1 - t0, nonce };
53};
54
55const stats = { time: 0, iters: 0 };
56const comparison = { time: 0, iters: 0 };
57const updateStatus = () => {
58 const mainRate = stats.iters / stats.time;
59 const compareRate = comparison.iters / comparison.time;
60 if (Number.isFinite(mainRate)) {
61 status.innerText = `Average hashrate: ${mainRate.toFixed(3)}kH/s`;
62 if (Number.isFinite(compareRate)) {
63 const change = ((mainRate - compareRate) / mainRate) * 100;
64 status.innerText += ` vs ${compareRate.toFixed(3)}kH/s (${change.toFixed(2)}% change)`;
65 }
66 } else {
67 status.innerText = "Benchmarking...";
68 }
69};
70
71const tableCell = (text) => {
72 const td = document.createElement("td");
73 td.innerText = text;
74 td.style.padding = "0 0.25rem";
75 return td;
76};
77
78const benchmarkLoop = async (controller) => {
79 const difficulty = difficultyInput.value;
80 const algorithm = algorithmSelect.value;
81 const compareAlgorithm = compareSelect.value;
82 updateStatus();
83
84 try {
85 const { time, nonce } = await benchmarkTrial(
86 stats,
87 difficulty,
88 algorithm,
89 controller.signal,
90 );
91
92 const tr = document.createElement("tr");
93 tr.style.display = "contents";
94 tr.append(tableCell(`${time}ms`), tableCell(nonce));
95
96 // auto-scroll to new rows
97 const atBottom =
98 results.scrollHeight - results.clientHeight <= results.scrollTop;
99 results.append(tr);
100 if (atBottom) {
101 results.scrollTop = results.scrollHeight - results.clientHeight;
102 }
103 updateStatus();
104
105 if (compareAlgorithm !== "NONE") {
106 const { time, nonce } = await benchmarkTrial(
107 comparison,
108 difficulty,
109 compareAlgorithm,
110 controller.signal,
111 );
112 tr.append(tableCell(`${time}ms`), tableCell(nonce));
113 }
114 } catch (e) {
115 if (e !== false) {
116 status.innerText = e;
117 }
118 return;
119 }
120
121 await benchmarkLoop(controller);
122};
123
124let controller = null;
125const reset = () => {
126 stats.time = stats.iters = 0;
127 comparison.time = comparison.iters = 0;
128 results.innerHTML = status.innerText = "";
129
130 const table = results.parentElement;
131 if (compareSelect.value !== "NONE") {
132 table.style.gridTemplateColumns = "repeat(4,auto)";
133 header.style.display = "none";
134 headerCompare.style.display = "contents";
135 } else {
136 table.style.gridTemplateColumns = "repeat(2,auto)";
137 header.style.display = "contents";
138 headerCompare.style.display = "none";
139 }
140
141 if (controller != null) {
142 controller.abort();
143 }
144 controller = new AbortController();
145 void benchmarkLoop(controller);
146};
147
148setupControls();
149difficultyInput.addEventListener("change", reset);
150algorithmSelect.addEventListener("change", reset);
151compareSelect.addEventListener("change", reset);
152reset();