feat: progress bar and incremental PoW generation

This commit is contained in:
Aravinth Manivannan 2023-10-29 06:28:05 +05:30
parent ad4582cc16
commit 9dfb0713ad
No known key found for this signature in database
GPG key ID: F8F50389936984FF
8 changed files with 221 additions and 137 deletions

View file

@ -37,8 +37,8 @@
"webpack-dev-server": "^4.15.1" "webpack-dev-server": "^4.15.1"
}, },
"dependencies": { "dependencies": {
"@mcaptcha/pow-wasm": "^0.1.0-rc1", "@mcaptcha/pow_sha256-polyfill": "^0.1.0-rc2",
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-rc1", "@mcaptcha/vanilla-glue": "^0.1.0-rc1",
"@mcaptcha/vanilla-glue": "^0.1.0-rc1" "@mcaptcha/pow-wasm": "^0.1.0-rc2"
} }
} }

View file

@ -6,51 +6,54 @@ SPDX-License-Identifier: MIT OR Apache-2.0
<. include!("../components/headers/widget-headers.html"); .> <. include!("../components/headers/widget-headers.html"); .>
<body> <body>
<form class="widget__contaienr"> <main class="widget__container">
<noscript> <form class="widget__inner-container">
<div class="widget__noscript-container"> <noscript>
<span class="widget__noscript-warning"> <div class="widget__noscript-container">
Please enable JavaScript to receive mCaptcha challenge <span class="widget__noscript-warning">
</span> Please enable JavaScript to receive mCaptcha challenge
<a class="widget__source-code" href="https://github.com/mCaptcha"> </span>
Read our source code <a class="widget__source-code" href="https://github.com/mCaptcha">
</a> Read our source code
</div> </a>
</noscript> </div>
<label class="widget__verification-container" for="widget__verification-checkbox"> </noscript>
<input <label class="widget__verification-container" for="widget__verification-checkbox">
id="widget__verification-checkbox" <input
class="widget__verification-checkbox" id="widget__verification-checkbox"
type="checkbox" /> class="widget__verification-checkbox"
<span id="widget__verification-text--before">I'm not a robot</span> type="checkbox" />
<span id="widget__verification-text--during">Processing...</span> <span id="widget__verification-text--before">I'm not a robot</span>
<span id="widget__verification-text--after">Verified!</span> <span id="widget__verification-text--during">Processing...</span>
<span id="widget__verification-text--error">Something went wrong</span> <span id="widget__verification-text--after">Verified!</span>
</label> <span id="widget__verification-text--error">Something went wrong</span>
<div class="widget__mcaptcha-details"> </label>
<a href="<.= crate::PKG_HOMEPAGE .>" <div class="widget__mcaptcha-details">
class="widget__mcaptcha-logo-container" <a href="<.= crate::PKG_HOMEPAGE .>"
target="_blank" class="widget__mcaptcha-logo-container"
> target="_blank"
<img >
class="widget__mcaptcha-logo" <img
src="<.= crate::FILES.get("./static/cache/img/icon-trans.png").unwrap().>" class="widget__mcaptcha-logo"
alt="mCaptcha logo" src="<.= crate::FILES.get("./static/cache/img/icon-trans.png").unwrap().>"
/> alt="mCaptcha logo"
<p class="widget__mcaptcha-brand-name">mCaptcha</p> />
</a> <p class="widget__mcaptcha-brand-name">mCaptcha</p>
<div class="widget__mcaptcha-info-container">
<a class="widget__mcaptcha-info-link"
target="_blank"
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
Privacy
</a>
<a class="widget__mcaptcha-info-link"
target="_blank"
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
Terms
</a> </a>
<div class="widget__mcaptcha-info-container">
<a class="widget__mcaptcha-info-link"
target="_blank"
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
Privacy
</a>
<a class="widget__mcaptcha-info-link"
target="_blank"
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
Terms
</a>
</div>
</div> </div>
</div> </form>
</form> <div class="progress__bar"><div class="progress__fill"></div></div>
</main>
<.include!("./footer.html"); .> <.include!("./footer.html"); .>

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
import { Work, ServiceWorkerWork } from "./types"; import { Work, ServiceWorkerMessage } from "./types";
import fetchPoWConfig from "./fetchPoWConfig"; import fetchPoWConfig from "./fetchPoWConfig";
import sendWork from "./sendWork"; import sendWork from "./sendWork";
import sendToParent from "./sendToParent"; import sendToParent from "./sendToParent";
@ -24,6 +24,9 @@ export const registerVerificationEventHandler = (): void => {
}; };
export const solveCaptchaRunner = async (e: Event): Promise<void> => { export const solveCaptchaRunner = async (e: Event): Promise<void> => {
const PROGRESS_FILL = <HTMLElement>document.querySelector(".progress__fill");
let width = 0;
if (LOCK) { if (LOCK) {
e.preventDefault(); e.preventDefault();
return; return;
@ -32,6 +35,8 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
try { try {
LOCK = true; LOCK = true;
if (CONST.btn().checked == false) { if (CONST.btn().checked == false) {
width = 0;
PROGRESS_FILL.style.width = `${width}%`;
CONST.messageText().before(); CONST.messageText().before();
LOCK = false; LOCK = false;
return; return;
@ -43,32 +48,49 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
CONST.messageText().during(); CONST.messageText().during();
// 1. get config // 1. get config
const config = await fetchPoWConfig(); const config = await fetchPoWConfig();
const max_recorded_nonce = config.max_recorded_nonce;
// 2. prove work // 2. prove work
worker.postMessage(config); worker.postMessage(config);
worker.onmessage = async (event: MessageEvent) => { worker.onmessage = async (event: MessageEvent) => {
const resp: ServiceWorkerWork = event.data; const resp: ServiceWorkerMessage = event.data;
console.log(
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.work.time}`
);
const proof: Work = { if (resp.type === "work") {
key: CONST.sitekey(), width = 80;
string: config.string, PROGRESS_FILL.style.width = `${width}%`;
nonce: resp.work.nonce, console.log(
result: resp.work.result, `Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.value.work.time}`
time: Math.trunc(resp.work.time), );
worker_type: resp.work.worker_type,
};
// 3. submit work const proof: Work = {
const token = await sendWork(proof); key: CONST.sitekey(),
// 4. send token string: config.string,
sendToParent(token); nonce: resp.value.work.nonce,
// 5. mark checkbox checked result: resp.value.work.result,
CONST.btn().checked = true; time: Math.trunc(resp.value.work.time),
CONST.messageText().after(); worker_type: resp.value.work.worker_type,
LOCK = false; };
width = 90;
PROGRESS_FILL.style.width = `${width}%`;
// 3. submit work
const token = await sendWork(proof);
// 4. send token
sendToParent(token);
// 5. mark checkbox checked
CONST.btn().checked = true;
width = 100;
PROGRESS_FILL.style.width = `${width}%`;
CONST.messageText().after();
LOCK = false;
}
if (resp.type === "progress") {
if (width < 80) {
width = (resp.nonce / max_recorded_nonce) * 100;
PROGRESS_FILL.style.width = `${width}%`;
}
console.log(`received nonce ${resp.nonce}`);
}
}; };
} catch (e) { } catch (e) {
CONST.messageText().error(); CONST.messageText().error();

View file

@ -7,106 +7,138 @@
@import "../reset"; @import "../reset";
.widget__contaienr { body {
align-items: center; display: flex;
box-sizing: border-box; flex-direction: column;
display: flex; width: 100%;
height: 100%; }
.widget__container {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
margin: auto 0;
}
.widget__inner-container {
align-items: center;
box-sizing: border-box;
display: flex;
height: 100%;
width: 100%;
} }
.widget__noscript-container { .widget__noscript-container {
display: flex; display: flex;
font-size: 0.7rem; font-size: 0.7rem;
line-height: 20px; line-height: 20px;
flex-direction: column; flex-direction: column;
padding: 5px; padding: 5px;
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
margin: auto; margin: auto;
} }
.widget__noscript-warning { .widget__noscript-warning {
display: block; display: block;
margin: auto; margin: auto;
flex: 2; flex: 2;
width: 100%; width: 100%;
margin: auto; margin: auto;
} }
.widget__source-code { .widget__source-code {
display: block; display: block;
flex: 1; flex: 1;
} }
.widget__verification-container { .widget__verification-container {
align-items: center; align-items: center;
display: none; display: none;
line-height: 30px; line-height: 30px;
font-size: 1rem; font-size: 1rem;
} }
.widget__verification-checkbox { .widget__verification-checkbox {
width: 30px; width: 30px;
height: 30px; height: 30px;
margin: 0 10px; margin: 0 10px;
} }
#widget__verification-text--during { #widget__verification-text--during {
display: none; display: none;
} }
#widget__verification-text--after { #widget__verification-text--after {
display: none; display: none;
color: green; color: green;
} }
#widget__verification-text--error { #widget__verification-text--error {
display: none; display: none;
color: red; color: red;
} }
.widget__verification-checkbox:checked ~ #widget__verification-text--before { .widget__verification-checkbox:checked ~ #widget__verification-text--before {
display: none; display: none;
} }
.widget__verification-checkbox:checked ~ #widget__verification-text--during { .widget__verification-checkbox:checked ~ #widget__verification-text--during {
display: none; display: none;
} }
.widget__verification-checkbox:checked ~ #widget__verification-text--error { .widget__verification-checkbox:checked ~ #widget__verification-text--error {
display: none; display: none;
} }
.widget__verification-checkbox:checked ~ #widget__verification-text--after { .widget__verification-checkbox:checked ~ #widget__verification-text--after {
display: block; display: block;
} }
.widget__mcaptcha-details { .widget__mcaptcha-details {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: auto; margin-left: auto;
margin-right: 10px; margin-right: 10px;
} }
.widget__mcaptcha-brand-name { .widget__mcaptcha-brand-name {
font-size: 0.7rem; font-size: 0.7rem;
font-weight: 600; font-weight: 600;
margin: auto; margin: auto;
text-align: center; text-align: center;
} }
.widget__mcaptcha-logo { .widget__mcaptcha-logo {
display: block; display: block;
width: 35px; width: 35px;
margin: auto; margin: auto;
} }
.widget__mcaptcha-info-container { .widget__mcaptcha-info-container {
display: flex; display: flex;
margin: auto; margin: auto;
} }
.widget__mcaptcha-info-link { .widget__mcaptcha-info-link {
font-size: 0.5rem; font-size: 0.5rem;
margin: 2px; margin: 2px;
}
/* progress bar courtesy of https://codepen.io/Bizzy-Coding/pen/poOymVJ?editors=1111 */
.progress__bar {
position: relative;
height: 5px;
width: 100%;
background: #fff;
border-radius: 15px;
}
.progress__fill {
background: #65a2e0;
border-radius: 15px;
height: 100%;
width: 0%;
} }

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
import { gen_pow } from "@mcaptcha/pow-wasm"; import { stepped_gen_pow } from "@mcaptcha/pow-wasm";
import * as p from "@mcaptcha/pow_sha256-polyfill"; import * as p from "@mcaptcha/pow_sha256-polyfill";
import { WasmWork, PoWConfig, SubmitWork } from "./types"; import { WasmWork, PoWConfig, SubmitWork } from "./types";
@ -12,19 +12,25 @@ import { WasmWork, PoWConfig, SubmitWork } from "./types";
* @param {PoWConfig} config - the proof-of-work configuration using which * @param {PoWConfig} config - the proof-of-work configuration using which
* work needs to be computed * work needs to be computed
* */ * */
const prove = async (config: PoWConfig): Promise<SubmitWork> => { const prove = async (
config: PoWConfig,
progress: (nonce: number) => void
): Promise<SubmitWork> => {
const WASM = "wasm"; const WASM = "wasm";
const JS = "js"; const JS = "js";
const STEPS = 5000;
if (WasmSupported) { if (WasmSupported) {
let proof: WasmWork = null; let proof: WasmWork = null;
let res: SubmitWork = null; let res: SubmitWork = null;
let time: number = null; let time: number = null;
const t0 = performance.now(); const t0 = performance.now();
const proofString = gen_pow( const proofString = stepped_gen_pow(
config.salt, config.salt,
config.string, config.string,
config.difficulty_factor config.difficulty_factor,
STEPS,
progress
); );
const t1 = performance.now(); const t1 = performance.now();
time = t1 - t0; time = t1 - t0;
@ -47,10 +53,12 @@ const prove = async (config: PoWConfig): Promise<SubmitWork> => {
const t0 = performance.now(); const t0 = performance.now();
proof = await p.generate_work( proof = await p.stepped_generate_work(
config.salt, config.salt,
config.string, config.string,
config.difficulty_factor config.difficulty_factor,
STEPS,
progress
); );
const t1 = performance.now(); const t1 = performance.now();
time = t1 - t0; time = t1 - t0;

View file

@ -6,17 +6,31 @@
import log from "../logger"; import log from "../logger";
import prove from "./prove"; import prove from "./prove";
import { PoWConfig, ServiceWorkerWork } from "./types"; import { PoWConfig, ServiceWorkerMessage, ServiceWorkerWork } from "./types";
log.log("worker registered"); log.log("worker registered");
onmessage = async (e) => { onmessage = async (e) => {
console.debug("message received at worker"); console.debug("message received at worker");
const config: PoWConfig = e.data; const config: PoWConfig = e.data;
const work = await prove(config); const progressCallback = (nonce: number) => {
const res: ServiceWorkerWork = { const res: ServiceWorkerMessage = {
type: "progress",
nonce: nonce,
};
postMessage(res);
};
const work = await prove(config, progressCallback);
const w: ServiceWorkerWork = {
work, work,
}; };
const res: ServiceWorkerMessage = {
type: "work",
value: w,
};
postMessage(res); postMessage(res);
}; };

View file

@ -32,8 +32,13 @@ export type PoWConfig = {
string: string; string: string;
difficulty_factor: number; difficulty_factor: number;
salt: string; salt: string;
max_recorded_nonce: number;
}; };
export type Token = { export type Token = {
token: string; token: string;
}; };
export type ServiceWorkerMessage =
| { type: "work"; value: ServiceWorkerWork }
| { type: "progress"; nonce: number };

View file

@ -631,15 +631,15 @@
resolved "https://registry.yarnpkg.com/@mcaptcha/core-glue/-/core-glue-0.1.0-rc1.tgz#76d665a3fc537062061e12e274f969ac3e053685" resolved "https://registry.yarnpkg.com/@mcaptcha/core-glue/-/core-glue-0.1.0-rc1.tgz#76d665a3fc537062061e12e274f969ac3e053685"
integrity sha512-P4SgUioJDR38QpnP9sPY72NyaYex8MXD6RbzrfKra+ngamT26XjqVZEHBiZU2RT7u0SsWhuko4N1ntNOghsgpg== integrity sha512-P4SgUioJDR38QpnP9sPY72NyaYex8MXD6RbzrfKra+ngamT26XjqVZEHBiZU2RT7u0SsWhuko4N1ntNOghsgpg==
"@mcaptcha/pow-wasm@^0.1.0-rc1": "@mcaptcha/pow-wasm@^0.1.0-rc2":
version "0.1.0-rc1" version "0.1.0-rc2"
resolved "https://registry.yarnpkg.com/@mcaptcha/pow-wasm/-/pow-wasm-0.1.0-rc1.tgz#eef8409e0c74e9c7261587bdebd80a8c4af92f9e" resolved "https://registry.yarnpkg.com/@mcaptcha/pow-wasm/-/pow-wasm-0.1.0-rc2.tgz#c7aaa678325600a178b11a702e2aeb9f8143e605"
integrity sha512-7+PGKoe1StFRsa9TEqztzK4/obbdY4OfavFX+geTk8b3K26D+eHPyimJ9BPlpI1VZl8ujR3CnbfbnQSRaqS7ZQ== integrity sha512-2G0nQ2GQWECRcE5kzfULDsQ032s6/PDzE1rncMdQAR1Mu2YQfFZHgnX4zLJmQnjKIhy9meIjXvatVSyIllrbtg==
"@mcaptcha/pow_sha256-polyfill@^0.1.0-rc1": "@mcaptcha/pow_sha256-polyfill@^0.1.0-rc2":
version "0.1.0-rc1" version "0.1.0-rc2"
resolved "https://registry.yarnpkg.com/@mcaptcha/pow_sha256-polyfill/-/pow_sha256-polyfill-0.1.0-rc1.tgz#dfeee88f5f6fd99aeae65dbcff6fbb09fe8a1696" resolved "https://registry.yarnpkg.com/@mcaptcha/pow_sha256-polyfill/-/pow_sha256-polyfill-0.1.0-rc2.tgz#253320e7a6666e395ef9dfb123d1102066d72b87"
integrity sha512-OFA4W3/vh8ORUnifbm8c/8eP22CbiXr4Un6/l4fMyqLj1aoQLMGAiuqab0trGqBnY0DU2bwTMyxflx26/cWgIw== integrity sha512-ERIbxIo+ZnQKtti/T4FLmcY0neuc5R05L97qYc62Hm++i+3dx/W6A8oC4V9U0XKCPYnHZFoZozAZlbsGXjrsVQ==
"@mcaptcha/vanilla-glue@^0.1.0-rc1": "@mcaptcha/vanilla-glue@^0.1.0-rc1":
version "0.1.0-rc1" version "0.1.0-rc1"