diff --git a/package.json b/package.json
index 69cc385..9461308 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"main": "src/main.ts",
"scripts": {
"build": "esbuild src/main.ts --bundle --outfile=public/bundle.js",
+ "build-mini": "esbuild src/main.ts --minify --bundle --outfile=public/bundle.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
diff --git a/public/challenge.json b/public/challenge.json
new file mode 100644
index 0000000..d66771e
--- /dev/null
+++ b/public/challenge.json
@@ -0,0 +1,5 @@
+{
+ "algo": "SHA-256",
+ "prefixBits": "15",
+ "input": "challenge:"
+}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 861ac9f..92d9349 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,85 +2,9 @@
-
-
+
-
+
diff --git a/src/challenge.ts b/src/challenge.ts
new file mode 100644
index 0000000..b9045cd
--- /dev/null
+++ b/src/challenge.ts
@@ -0,0 +1,10 @@
+
+export type Challenge = {
+ algo: string,
+ prefixBits: number,
+ input: string,
+}
+
+export function validateChallenge(challenge: Challenge): boolean {
+ return !!challenge.algo && challenge.prefixBits > 0 && !!challenge.input;
+}
\ No newline at end of file
diff --git a/src/default-styles.css b/src/default-styles.css
new file mode 100644
index 0000000..afa7db5
--- /dev/null
+++ b/src/default-styles.css
@@ -0,0 +1,70 @@
+.captcha {
+ width: 300px;
+ height: 70px;
+ padding: 19px;
+ border: 1px solid grey;
+ box-sizing: border-box;
+ font-size: 20px;
+ font-family: sans-serif;
+}
+
+.captcha span {
+ position: relative;
+ top: -9px;
+}
+
+.captcha .checkbox {
+ position: relative;
+ margin: 0 20px 0 0;
+ height: 30px;
+ width: 30px;
+ display: inline-block;
+ background-color: white;
+ cursor: pointer;
+ border: 1px solid black;
+ border-radius: 3px;
+}
+
+.captcha .checkbox:hover {
+ background-color: lightgrey;
+}
+
+.captcha .checkbox.checked {
+ background-color: #2196F3;
+}
+
+.captcha .checkbox.checked::before {
+ position: absolute;
+ display: block;
+ content: "";
+ width: 7px;
+ height: 15px;
+ top: 2px;
+ left: 9px;
+ border: white solid;
+ border-width: 0 5px 5px 0;
+ transform: rotate(45deg);
+}
+
+.captcha .checkbox.loading {
+}
+
+.captcha .checkbox.loading::before {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ width: 17px;
+ height: 17px;
+ border: 4px solid;
+ border-radius: 50%;
+ border-color: black transparent black transparent;
+ animation: captchaLoading infinite 1s;
+}
+
+@keyframes captchaLoading {
+ to {
+ transform: rotate(.5turn)
+ }
+}
\ No newline at end of file
diff --git a/src/hash.ts b/src/hash.ts
index 94d62ed..65c347c 100644
--- a/src/hash.ts
+++ b/src/hash.ts
@@ -29,4 +29,4 @@ Uint8Array.prototype.hasPrefix = function(prefix: Uint8Array, bits: number): boo
export async function digest(algo: AlgorithmIdentifier, input: string): Promise {
return new Uint8Array(await crypto.subtle.digest(algo, new TextEncoder().encode(input)));
-}
\ No newline at end of file
+}
diff --git a/src/main.ts b/src/main.ts
index dbddc43..5903a07 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,9 @@
import {makeSuffix} from "./content";
import {digest, sha256} from "./hash";
+import "./default-styles.css";
+import {Challenge, validateChallenge} from "./challenge";
+
async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string): Promise {
const hashPrefix = new Uint8Array(Array(Math.ceil(hashPrefixBits / 8)).map(_ => 0));
let iteration = 0;
@@ -13,30 +16,56 @@ async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string):
return message;
}
+function initCaptchaContentAndGetCheckbox(captcha: Element): Element {
+ const checkbox = document.createElement("div");
+ checkbox.classList.add("checkbox");
+ captcha.append(checkbox);
+
+ const text = document.createElement("span");
+ text.innerText = "I am not a robot";
+ captcha.append(text);
+
+ return checkbox;
+}
+
+function prepareChallengeExecution(challenge: Challenge): () => Promise {
+ return async function() {
+ console.log("Calculating...");
+
+ this.classList.add("loading");
+
+ const response = await findHashWithPrefix(challenge.prefixBits, challenge.input);
+ console.log("Challenge Response: " + response);
+
+ this.classList.remove("loading");
+ this.classList.add("checked");
+ }
+}
+
+async function prepareCaptcha(captcha: Element) {
+ const challengeUrl = captcha.getAttribute("data-challenge-url");
+ if (!challengeUrl) {
+ console.warn("No challenge URL found.");
+ return;
+ }
+
+ const checkbox = initCaptchaContentAndGetCheckbox(captcha);
+ checkbox.classList.add("loading");
+
+ const challengeResponse = await fetch(challengeUrl);
+ const challenge = await challengeResponse.json() as Challenge;
+
+ if (!validateChallenge(challenge)) {
+ console.warn("Challenge is invalid.");
+ return;
+ }
+
+ checkbox.classList.remove("loading");
+
+ checkbox.addEventListener("click", prepareChallengeExecution(challenge));
+}
+
window.addEventListener("load", () => {
console.log("load");
- [...document.getElementsByClassName("captcha")].forEach(captcha => {
- console.dir(captcha);
- const checkbox = document.createElement("div");
- checkbox.classList.add("checkbox");
- captcha.append(checkbox);
-
- const text = document.createElement("span");
- text.innerText = "I am not a robot";
- captcha.append(text);
-
- checkbox.addEventListener("click", async function() {
- console.log("Calculating...");
-
- this.classList.add("loading");
-
- const challenge = makeSuffix(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) + ":" + makeSuffix(new Date().valueOf()) + ":";
-
- const response = await findHashWithPrefix(15, challenge);
- console.log("Challenge Response: " + response);
-
- this.classList.remove("loading");
- this.classList.add("checked");
- })
- });
+ [...document.getElementsByClassName("captcha")].forEach(prepareCaptcha);
});