diff --git a/README.md b/README.md index c35a781..7b4ffc2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -Cap logo +Cap logo # cap — modern, lightning-quick PoW captcha Cap is a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW. -| [Learn more](https://cap.tiagorangel.com) +| [Learn more](https://capjs.js.org) |----------| [![npm @cap.js/server](https://img.shields.io/badge/@cap.js/-server-CB0200?logo=npm)](https://www.npmjs.com/package/@cap.js/server) [![npm @cap.js/widget](https://img.shields.io/badge/@cap.js/-widget-CB0200?logo=npm)](https://www.npmjs.com/package/@cap.js/widget) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9920/badge)](https://www.bestpractices.dev/projects/9920) diff --git a/docs/guide/effectiveness.md b/docs/guide/effectiveness.md index e9ea3ea..d65e262 100644 --- a/docs/guide/effectiveness.md +++ b/docs/guide/effectiveness.md @@ -29,4 +29,9 @@ Imagine I can send 10,000 emails to 10,000 random recipients for $1. If I can ea By applying similar concepts to CAPTCHA, we make it costly for bots to spam, effectively discouraging them from trying. -If you want to read more about proof-of-work, I recommend reading [this whitepaper](https://www.researchgate.net/publication/374638786_Proof-of-Work_CAPTCHA_with_password_cracking_functionality) \ No newline at end of file +If you want to read more about proof-of-work, I recommend reading [this whitepaper](https://www.researchgate.net/publication/374638786_Proof-of-Work_CAPTCHA_with_password_cracking_functionality) + + +## Why proof-of-work? + +Every captcha is solvable by bots or by paid humans. The only question is how to make it costly for bots but invisible for users. Proof-of-work is the perfect balance for this problem. \ No newline at end of file diff --git a/docs/guide/standalone.md b/docs/guide/standalone.md index 81b0a68..7a2e203 100644 --- a/docs/guide/standalone.md +++ b/docs/guide/standalone.md @@ -33,7 +33,7 @@ Then, you can access the dashboard at `http://localhost:3000`, log in, and creat ### Client-side -First, add the Cap widget to your website by following the [Cap docs](https://cap.tiagorangel.com/). +First, add the Cap widget to your website by following [this guide](widget.md). Then, you need to configure the widget to use your self-hosted Cap Standalone server. To do this, set the widget's API endpoint option to: diff --git a/solver/README.md b/solver/README.md index c4754ee..fb4611c 100644 --- a/solver/README.md +++ b/solver/README.md @@ -1,2 +1,3 @@ # Server solver for the Cap CAPTCHA -See [https://cap.tiagorangel.com](https://cap.tiagorangel.com) \ No newline at end of file + +See [https://capjs.js.org](https://capjs.js.org) diff --git a/standalone/package.json b/standalone/package.json index 55bf3d0..718af4b 100644 --- a/standalone/package.json +++ b/standalone/package.json @@ -3,7 +3,8 @@ "version": "1.0.6", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "bun run --watch src/index.js" + "dev": "bun run --watch src/index.js", + "docker:publish": "docker buildx build --platform linux/amd64,linux/arm64 -t tiago2/cap:latest . --push" }, "dependencies": { "@cap.js/server": "^1.0.6", diff --git a/standalone/public/lock.html b/standalone/public/lock.html index 7b51775..091c32d 100644 --- a/standalone/public/lock.html +++ b/standalone/public/lock.html @@ -186,7 +186,7 @@

- Powered by Cap + Powered by Cap

@@ -209,7 +209,7 @@ "Content-Type": "application/json", }, body: JSON.stringify({ - password + password, }), }) ).json(); diff --git a/widget/src/cap.compat.min.js b/widget/src/cap.compat.min.js index bc5f058..07aed4b 100644 --- a/widget/src/cap.compat.min.js +++ b/widget/src/cap.compat.min.js @@ -1 +1 @@ -"use strict";function _classPrivateFieldLooseBase(e,t){if(!{}.hasOwnProperty.call(e,t))throw new TypeError("attempted to use private field on non-instance");return e}var id=0;function _classPrivateFieldLooseKey(e){return"__private_"+id+++"_"+e}!function(){let e;const t=function(e,t){return void 0===t&&(t=1e4),new Promise(((s,a)=>{const i=setTimeout((()=>{a(new Error("Initialize timeout"))}),t),r=()=>{e()?(clearTimeout(i),s()):setTimeout(r,500)};r()}))};var s=_classPrivateFieldLooseKey("workerUrl"),a=_classPrivateFieldLooseKey("resetTimer"),i=_classPrivateFieldLooseKey("workersCount"),r=_classPrivateFieldLooseKey("token"),o=_classPrivateFieldLooseKey("shadow"),n=_classPrivateFieldLooseKey("div"),c=_classPrivateFieldLooseKey("host"),l=_classPrivateFieldLooseKey("solving"),d=_classPrivateFieldLooseKey("eventHandlers");class h extends HTMLElement{static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","workers"]}constructor(){super(),Object.defineProperty(this,s,{writable:!0,value:""}),Object.defineProperty(this,a,{writable:!0,value:null}),Object.defineProperty(this,i,{writable:!0,value:navigator.hardwareConcurrency||8}),Object.defineProperty(this,r,{writable:!0,value:null}),Object.defineProperty(this,o,{writable:!0,value:void 0}),Object.defineProperty(this,n,{writable:!0,value:void 0}),Object.defineProperty(this,c,{writable:!0,value:void 0}),Object.defineProperty(this,l,{writable:!0,value:!1}),Object.defineProperty(this,d,{writable:!0,value:void 0}),_classPrivateFieldLooseBase(this,d)[d]&&_classPrivateFieldLooseBase(this,d)[d].forEach(((e,t)=>{this.removeEventListener(t.slice(2),e)})),_classPrivateFieldLooseBase(this,d)[d]=new Map,this.boundHandleProgress=this.handleProgress.bind(this),this.boundHandleSolve=this.handleSolve.bind(this),this.boundHandleError=this.handleError.bind(this),this.boundHandleReset=this.handleReset.bind(this)}async initialize(){_classPrivateFieldLooseBase(this,s)[s]&&URL.revokeObjectURL(_classPrivateFieldLooseBase(this,s)[s]);try{await t((()=>!!e)),_classPrivateFieldLooseBase(this,s)[s]=URL.createObjectURL(new Blob([e],{type:"application/javascript"}))}catch(e){throw this.error("Failed to initialize worker"),e}}attributeChangedCallback(e,t,s){if(e.startsWith("on")){const t=e.slice(2),a=_classPrivateFieldLooseBase(this,d)[d].get(e);if(a&&this.removeEventListener(t,a),s){const s=t=>{const s=this.getAttribute(e);"function"==typeof window[s]&&window[s].call(this,t)};_classPrivateFieldLooseBase(this,d)[d].set(e,s),this.addEventListener(t,s)}}}async connectedCallback(){_classPrivateFieldLooseBase(this,c)[c]=this,_classPrivateFieldLooseBase(this,o)[o]=this.attachShadow({mode:"open"}),_classPrivateFieldLooseBase(this,n)[n]=document.createElement("div"),this.createUI(),this.addEventListeners(),await this.initialize(),_classPrivateFieldLooseBase(this,n)[n].removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count");this.setWorkersCount(parseInt(e)?parseInt(e,10):navigator.hardwareConcurrency||8),_classPrivateFieldLooseBase(this,c)[c].innerHTML=''}async solve(){if(!_classPrivateFieldLooseBase(this,l)[l])try{_classPrivateFieldLooseBase(this,l)[l]=!0,this.updateUI("verifying","Verifying...",!0),await t((()=>!!_classPrivateFieldLooseBase(this,s)[s])),this.dispatchEvent("progress",{progress:0});try{const e=this.getAttribute("data-cap-api-endpoint");if(!e)throw new Error("Missing API endpoint");const{challenge:t,target:s,token:i}=await(await fetch(`${e}challenge`,{method:"POST"})).json(),o=await this.solveChallenges(t),n=await(await fetch(`${e}redeem`,{method:"POST",body:JSON.stringify({token:i,solutions:o}),headers:{"Content-Type":"application/json"}})).json();if(!n.success)throw new Error("Invalid solution");this.dispatchEvent("progress",{progress:100}),this.dispatchEvent("solve",{token:n.token}),_classPrivateFieldLooseBase(this,r)[r]=n.token,this.querySelector("input[name='cap-token']")&&(this.querySelector("input[name='cap-token']").value=n.token),_classPrivateFieldLooseBase(this,a)[a]&&clearTimeout(_classPrivateFieldLooseBase(this,a)[a]);const c=new Date(n.expires).getTime()-Date.now();return c>0&&c<864e5?_classPrivateFieldLooseBase(this,a)[a]=setTimeout((()=>this.reset()),c):this.error("Invalid expiration time"),{success:!0,token:_classPrivateFieldLooseBase(this,r)[r]}}catch(e){throw this.error(e.message),e}}finally{_classPrivateFieldLooseBase(this,l)[l]=!1}}async solveChallenges(e){const t=e.length;let a=0;const r=Array(_classPrivateFieldLooseBase(this,i)[i]).fill(null).map((()=>new Worker(_classPrivateFieldLooseBase(this,s)[s]))),o=(e,i)=>{let[o,n]=e;return new Promise(((e,c)=>{const l=r[i],d=setTimeout((()=>{l.terminate(),r[i]=new Worker(_classPrivateFieldLooseBase(this,s)[s]),c(new Error("Worker timeout"))}),3e4);l.onmessage=s=>{let{data:i}=s;i.found&&(clearTimeout(d),a++,this.dispatchEvent("progress",{progress:Math.round(a/t*100)}),e([o,n,i.nonce]))},l.onerror=e=>{clearTimeout(d),this.error(`Error in worker: ${e}`),c(e)},l.postMessage({salt:o,target:n})}))},n=[];try{for(let t=0;to(e,t))));n.push(...a)}}finally{r.forEach((e=>e.terminate()))}return n}setWorkersCount(e){const t=parseInt(e,10),s=Math.min(navigator.hardwareConcurrency||8,16);_classPrivateFieldLooseBase(this,i)[i]=!isNaN(t)&&t>0&&t<=s?t:navigator.hardwareConcurrency||8}createUI(){_classPrivateFieldLooseBase(this,n)[n].classList.add("captcha"),_classPrivateFieldLooseBase(this,n)[n].setAttribute("role","button"),_classPrivateFieldLooseBase(this,n)[n].setAttribute("tabindex","0"),_classPrivateFieldLooseBase(this,n)[n].setAttribute("disabled","true"),_classPrivateFieldLooseBase(this,n)[n].innerHTML='

I\'m a human

Secured by Cap',_classPrivateFieldLooseBase(this,o)[o].innerHTML='',_classPrivateFieldLooseBase(this,o)[o].appendChild(_classPrivateFieldLooseBase(this,n)[n])}addEventListeners(){_classPrivateFieldLooseBase(this,n)[n].querySelector("a").addEventListener("click",(e=>{e.stopPropagation(),e.preventDefault(),window.open("#","_blank")})),_classPrivateFieldLooseBase(this,n)[n].addEventListener("click",(()=>{_classPrivateFieldLooseBase(this,n)[n].hasAttribute("disabled")||this.solve()})),this.addEventListener("progress",this.boundHandleProgress),this.addEventListener("solve",this.boundHandleSolve),this.addEventListener("error",this.boundHandleError),this.addEventListener("reset",this.boundHandleReset)}updateUI(e,t,s){void 0===s&&(s=!1),_classPrivateFieldLooseBase(this,n)[n].setAttribute("data-state",e),_classPrivateFieldLooseBase(this,n)[n].querySelector("p").innerText=t,s?_classPrivateFieldLooseBase(this,n)[n].setAttribute("disabled","true"):_classPrivateFieldLooseBase(this,n)[n].removeAttribute("disabled")}handleProgress(e){const t=_classPrivateFieldLooseBase(this,n)[n].querySelector("p");t&&(_classPrivateFieldLooseBase(this,n)[n].querySelector(".checkbox").style.setProperty("--progress",`${e.detail.progress}%`),t.innerText=`Verifying... ${e.detail.progress}%`),this.executeAttributeCode("onprogress",e)}handleSolve(e){this.updateUI("done","You're a human",!0),this.executeAttributeCode("onsolve",e)}handleError(e){this.updateUI("error","Error. Try again."),this.executeAttributeCode("onerror",e)}handleReset(e){this.updateUI("","I'm a human"),this.executeAttributeCode("onreset",e)}executeAttributeCode(e,t){const s=this.getAttribute(e);if(!s)return;new Function("event",s).call(this,t)}error(e){void 0===e&&(e="Unknown error"),console.error("[Cap] Error:",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t){void 0===t&&(t={});const s=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(s)}reset(){_classPrivateFieldLooseBase(this,a)[a]&&(clearTimeout(_classPrivateFieldLooseBase(this,a)[a]),_classPrivateFieldLooseBase(this,a)[a]=null),this.dispatchEvent("reset"),_classPrivateFieldLooseBase(this,r)[r]=null,this.querySelector("input[name='cap-token']")&&(this.querySelector("input[name='cap-token']").value="")}get token(){return _classPrivateFieldLooseBase(this,r)[r]}disconnectedCallback(){this.removeEventListener("progress",this.boundHandleProgress),this.removeEventListener("solve",this.boundHandleSolve),this.removeEventListener("error",this.boundHandleError),this.removeEventListener("reset",this.boundHandleReset),_classPrivateFieldLooseBase(this,d)[d].forEach(((e,t)=>{this.removeEventListener(t.slice(2),e)})),_classPrivateFieldLooseBase(this,d)[d].clear(),_classPrivateFieldLooseBase(this,o)[o]&&(_classPrivateFieldLooseBase(this,o)[o].innerHTML=""),this.reset(),this.cleanup()}cleanup(){_classPrivateFieldLooseBase(this,a)[a]&&(clearTimeout(_classPrivateFieldLooseBase(this,a)[a]),_classPrivateFieldLooseBase(this,a)[a]=null),_classPrivateFieldLooseBase(this,s)[s]&&(URL.revokeObjectURL(_classPrivateFieldLooseBase(this,s)[s]),_classPrivateFieldLooseBase(this,s)[s]="")}}class p{constructor(e,t){void 0===e&&(e={});let s=t||document.createElement("cap-widget");if(Object.entries(e).forEach((e=>{let[t,a]=e;s.setAttribute(t,a)})),!e.apiEndpoint)throw s.remove(),new Error("Missing API endpoint");console.log(e),s.setAttribute("data-cap-api-endpoint",e.apiEndpoint),this.widget=s,this.solve=this.widget.solve.bind(this.widget),this.reset=this.widget.reset.bind(this.widget),this.addEventListener=this.widget.addEventListener.bind(this.widget),Object.defineProperty(this,"token",{get:()=>s.getToken(),configurable:!0,enumerable:!0}),t||(s.style.display="none",document.documentElement.appendChild(s))}}const v=new CSSStyleSheet;v.replaceSync('html{--cap-font:system,-apple-system,"BlinkMacSystemFont",".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande","Ubuntu","arial",sans-serif;--cap-color:#212121;--cap-background:#fdfdfd;--cap-border-color:#dddddd8f;--cap-border-radius:14px;--cap-checkbox-border:1px solid #aaaaaad1;--cap-checkbox-border-radius:6px;--cap-checkbox-background:#fafafa91;--cap-widget-width:240px;--cap-widget-padding:14px;--cap-checkbox-size:24px;--cap-checkbox-margin:2px;--cap-transition-duration:0.2s;--cap-gap:15px;--cap-opacity-hover:0.8;--cap-hover-filter:brightness(97%);--cap-active-scale:0.98;--cap-credits-font-size:12px;--cap-spinner-color:black;--cap-spinner-background-color:#eee;--cap-error-cross:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'96\' height=\'96\' viewBox=\'0 0 24 24\'%3E%3Cpath fill=\'%23f55b50\' d=\'M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8\'/%3E%3C/svg%3E");--cap-checkmark:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E");--cap-spinner-thickness:5px;}'),document.adoptedStyleSheets.push(v);const u=function(){let e;self.onmessage=async t=>{let{data:{salt:s,target:a}}=t,i=0;const r=5e4;let o=0;const n=new TextEncoder;if("object"!=typeof WebAssembly||"function"!=typeof WebAssembly.instantiate){console.log("[cap] WASM not enabled, falling back to crypto.subtle\nThis is significanty slower than the WASM implementation");const e=new Uint8Array(a.length/2);for(let t=0;t { const i = setTimeout((() => { a(new Error("Initialize timeout")) }), t), r = () => { e() ? (clearTimeout(i), s()) : setTimeout(r, 500) }; r() })) }; var s = _classPrivateFieldLooseKey("workerUrl"), a = _classPrivateFieldLooseKey("resetTimer"), i = _classPrivateFieldLooseKey("workersCount"), r = _classPrivateFieldLooseKey("token"), o = _classPrivateFieldLooseKey("shadow"), n = _classPrivateFieldLooseKey("div"), c = _classPrivateFieldLooseKey("host"), l = _classPrivateFieldLooseKey("solving"), d = _classPrivateFieldLooseKey("eventHandlers"); class h extends HTMLElement { static get observedAttributes() { return ["onsolve", "onprogress", "onreset", "onerror", "workers"] } constructor() { super(), Object.defineProperty(this, s, { writable: !0, value: "" }), Object.defineProperty(this, a, { writable: !0, value: null }), Object.defineProperty(this, i, { writable: !0, value: navigator.hardwareConcurrency || 8 }), Object.defineProperty(this, r, { writable: !0, value: null }), Object.defineProperty(this, o, { writable: !0, value: void 0 }), Object.defineProperty(this, n, { writable: !0, value: void 0 }), Object.defineProperty(this, c, { writable: !0, value: void 0 }), Object.defineProperty(this, l, { writable: !0, value: !1 }), Object.defineProperty(this, d, { writable: !0, value: void 0 }), _classPrivateFieldLooseBase(this, d)[d] && _classPrivateFieldLooseBase(this, d)[d].forEach(((e, t) => { this.removeEventListener(t.slice(2), e) })), _classPrivateFieldLooseBase(this, d)[d] = new Map, this.boundHandleProgress = this.handleProgress.bind(this), this.boundHandleSolve = this.handleSolve.bind(this), this.boundHandleError = this.handleError.bind(this), this.boundHandleReset = this.handleReset.bind(this) } async initialize() { _classPrivateFieldLooseBase(this, s)[s] && URL.revokeObjectURL(_classPrivateFieldLooseBase(this, s)[s]); try { await t((() => !!e)), _classPrivateFieldLooseBase(this, s)[s] = URL.createObjectURL(new Blob([e], { type: "application/javascript" })) } catch (e) { throw this.error("Failed to initialize worker"), e } } attributeChangedCallback(e, t, s) { if (e.startsWith("on")) { const t = e.slice(2), a = _classPrivateFieldLooseBase(this, d)[d].get(e); if (a && this.removeEventListener(t, a), s) { const s = t => { const s = this.getAttribute(e); "function" == typeof window[s] && window[s].call(this, t) }; _classPrivateFieldLooseBase(this, d)[d].set(e, s), this.addEventListener(t, s) } } } async connectedCallback() { _classPrivateFieldLooseBase(this, c)[c] = this, _classPrivateFieldLooseBase(this, o)[o] = this.attachShadow({ mode: "open" }), _classPrivateFieldLooseBase(this, n)[n] = document.createElement("div"), this.createUI(), this.addEventListeners(), await this.initialize(), _classPrivateFieldLooseBase(this, n)[n].removeAttribute("disabled"); const e = this.getAttribute("data-cap-worker-count"); this.setWorkersCount(parseInt(e) ? parseInt(e, 10) : navigator.hardwareConcurrency || 8), _classPrivateFieldLooseBase(this, c)[c].innerHTML = '' } async solve() { if (!_classPrivateFieldLooseBase(this, l)[l]) try { _classPrivateFieldLooseBase(this, l)[l] = !0, this.updateUI("verifying", "Verifying...", !0), await t((() => !!_classPrivateFieldLooseBase(this, s)[s])), this.dispatchEvent("progress", { progress: 0 }); try { const e = this.getAttribute("data-cap-api-endpoint"); if (!e) throw new Error("Missing API endpoint"); const { challenge: t, target: s, token: i } = await (await fetch(`${e}challenge`, { method: "POST" })).json(), o = await this.solveChallenges(t), n = await (await fetch(`${e}redeem`, { method: "POST", body: JSON.stringify({ token: i, solutions: o }), headers: { "Content-Type": "application/json" } })).json(); if (!n.success) throw new Error("Invalid solution"); this.dispatchEvent("progress", { progress: 100 }), this.dispatchEvent("solve", { token: n.token }), _classPrivateFieldLooseBase(this, r)[r] = n.token, this.querySelector("input[name='cap-token']") && (this.querySelector("input[name='cap-token']").value = n.token), _classPrivateFieldLooseBase(this, a)[a] && clearTimeout(_classPrivateFieldLooseBase(this, a)[a]); const c = new Date(n.expires).getTime() - Date.now(); return c > 0 && c < 864e5 ? _classPrivateFieldLooseBase(this, a)[a] = setTimeout((() => this.reset()), c) : this.error("Invalid expiration time"), { success: !0, token: _classPrivateFieldLooseBase(this, r)[r] } } catch (e) { throw this.error(e.message), e } } finally { _classPrivateFieldLooseBase(this, l)[l] = !1 } } async solveChallenges(e) { const t = e.length; let a = 0; const r = Array(_classPrivateFieldLooseBase(this, i)[i]).fill(null).map((() => new Worker(_classPrivateFieldLooseBase(this, s)[s]))), o = (e, i) => { let [o, n] = e; return new Promise(((e, c) => { const l = r[i], d = setTimeout((() => { l.terminate(), r[i] = new Worker(_classPrivateFieldLooseBase(this, s)[s]), c(new Error("Worker timeout")) }), 3e4); l.onmessage = s => { let { data: i } = s; i.found && (clearTimeout(d), a++, this.dispatchEvent("progress", { progress: Math.round(a / t * 100) }), e([o, n, i.nonce])) }, l.onerror = e => { clearTimeout(d), this.error(`Error in worker: ${e}`), c(e) }, l.postMessage({ salt: o, target: n }) })) }, n = []; try { for (let t = 0; t < e.length; t += _classPrivateFieldLooseBase(this, i)[i]) { const s = e.slice(t, Math.min(t + _classPrivateFieldLooseBase(this, i)[i], e.length)), a = await Promise.all(s.map(((e, t) => o(e, t)))); n.push(...a) } } finally { r.forEach((e => e.terminate())) } return n } setWorkersCount(e) { const t = parseInt(e, 10), s = Math.min(navigator.hardwareConcurrency || 8, 16); _classPrivateFieldLooseBase(this, i)[i] = !isNaN(t) && t > 0 && t <= s ? t : navigator.hardwareConcurrency || 8 } createUI() { _classPrivateFieldLooseBase(this, n)[n].classList.add("captcha"), _classPrivateFieldLooseBase(this, n)[n].setAttribute("role", "button"), _classPrivateFieldLooseBase(this, n)[n].setAttribute("tabindex", "0"), _classPrivateFieldLooseBase(this, n)[n].setAttribute("disabled", "true"), _classPrivateFieldLooseBase(this, n)[n].innerHTML = '

I\'m a human

Secured by Cap', _classPrivateFieldLooseBase(this, o)[o].innerHTML = '', _classPrivateFieldLooseBase(this, o)[o].appendChild(_classPrivateFieldLooseBase(this, n)[n]) } addEventListeners() { _classPrivateFieldLooseBase(this, n)[n].querySelector("a").addEventListener("click", (e => { e.stopPropagation(), e.preventDefault(), window.open("#", "_blank") })), _classPrivateFieldLooseBase(this, n)[n].addEventListener("click", (() => { _classPrivateFieldLooseBase(this, n)[n].hasAttribute("disabled") || this.solve() })), this.addEventListener("progress", this.boundHandleProgress), this.addEventListener("solve", this.boundHandleSolve), this.addEventListener("error", this.boundHandleError), this.addEventListener("reset", this.boundHandleReset) } updateUI(e, t, s) { void 0 === s && (s = !1), _classPrivateFieldLooseBase(this, n)[n].setAttribute("data-state", e), _classPrivateFieldLooseBase(this, n)[n].querySelector("p").innerText = t, s ? _classPrivateFieldLooseBase(this, n)[n].setAttribute("disabled", "true") : _classPrivateFieldLooseBase(this, n)[n].removeAttribute("disabled") } handleProgress(e) { const t = _classPrivateFieldLooseBase(this, n)[n].querySelector("p"); t && (_classPrivateFieldLooseBase(this, n)[n].querySelector(".checkbox").style.setProperty("--progress", `${e.detail.progress}%`), t.innerText = `Verifying... ${e.detail.progress}%`), this.executeAttributeCode("onprogress", e) } handleSolve(e) { this.updateUI("done", "You're a human", !0), this.executeAttributeCode("onsolve", e) } handleError(e) { this.updateUI("error", "Error. Try again."), this.executeAttributeCode("onerror", e) } handleReset(e) { this.updateUI("", "I'm a human"), this.executeAttributeCode("onreset", e) } executeAttributeCode(e, t) { const s = this.getAttribute(e); if (!s) return; new Function("event", s).call(this, t) } error(e) { void 0 === e && (e = "Unknown error"), console.error("[Cap] Error:", e), this.dispatchEvent("error", { isCap: !0, message: e }) } dispatchEvent(e, t) { void 0 === t && (t = {}); const s = new CustomEvent(e, { bubbles: !0, composed: !0, detail: t }); super.dispatchEvent(s) } reset() { _classPrivateFieldLooseBase(this, a)[a] && (clearTimeout(_classPrivateFieldLooseBase(this, a)[a]), _classPrivateFieldLooseBase(this, a)[a] = null), this.dispatchEvent("reset"), _classPrivateFieldLooseBase(this, r)[r] = null, this.querySelector("input[name='cap-token']") && (this.querySelector("input[name='cap-token']").value = "") } get token() { return _classPrivateFieldLooseBase(this, r)[r] } disconnectedCallback() { this.removeEventListener("progress", this.boundHandleProgress), this.removeEventListener("solve", this.boundHandleSolve), this.removeEventListener("error", this.boundHandleError), this.removeEventListener("reset", this.boundHandleReset), _classPrivateFieldLooseBase(this, d)[d].forEach(((e, t) => { this.removeEventListener(t.slice(2), e) })), _classPrivateFieldLooseBase(this, d)[d].clear(), _classPrivateFieldLooseBase(this, o)[o] && (_classPrivateFieldLooseBase(this, o)[o].innerHTML = ""), this.reset(), this.cleanup() } cleanup() { _classPrivateFieldLooseBase(this, a)[a] && (clearTimeout(_classPrivateFieldLooseBase(this, a)[a]), _classPrivateFieldLooseBase(this, a)[a] = null), _classPrivateFieldLooseBase(this, s)[s] && (URL.revokeObjectURL(_classPrivateFieldLooseBase(this, s)[s]), _classPrivateFieldLooseBase(this, s)[s] = "") } } class p { constructor(e, t) { void 0 === e && (e = {}); let s = t || document.createElement("cap-widget"); if (Object.entries(e).forEach((e => { let [t, a] = e; s.setAttribute(t, a) })), !e.apiEndpoint) throw s.remove(), new Error("Missing API endpoint"); console.log(e), s.setAttribute("data-cap-api-endpoint", e.apiEndpoint), this.widget = s, this.solve = this.widget.solve.bind(this.widget), this.reset = this.widget.reset.bind(this.widget), this.addEventListener = this.widget.addEventListener.bind(this.widget), Object.defineProperty(this, "token", { get: () => s.getToken(), configurable: !0, enumerable: !0 }), t || (s.style.display = "none", document.documentElement.appendChild(s)) } } const v = new CSSStyleSheet; v.replaceSync('html{--cap-font:system,-apple-system,"BlinkMacSystemFont",".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande","Ubuntu","arial",sans-serif;--cap-color:#212121;--cap-background:#fdfdfd;--cap-border-color:#dddddd8f;--cap-border-radius:14px;--cap-checkbox-border:1px solid #aaaaaad1;--cap-checkbox-border-radius:6px;--cap-checkbox-background:#fafafa91;--cap-widget-width:240px;--cap-widget-padding:14px;--cap-checkbox-size:24px;--cap-checkbox-margin:2px;--cap-transition-duration:0.2s;--cap-gap:15px;--cap-opacity-hover:0.8;--cap-hover-filter:brightness(97%);--cap-active-scale:0.98;--cap-credits-font-size:12px;--cap-spinner-color:black;--cap-spinner-background-color:#eee;--cap-error-cross:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'96\' height=\'96\' viewBox=\'0 0 24 24\'%3E%3Cpath fill=\'%23f55b50\' d=\'M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8\'/%3E%3C/svg%3E");--cap-checkmark:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E");--cap-spinner-thickness:5px;}'), document.adoptedStyleSheets.push(v); const u = function () { let e; self.onmessage = async t => { let { data: { salt: s, target: a } } = t, i = 0; const r = 5e4; let o = 0; const n = new TextEncoder; if ("object" != typeof WebAssembly || "function" != typeof WebAssembly.instantiate) { console.log("[cap] WASM not enabled, falling back to crypto.subtle\nThis is significanty slower than the WASM implementation"); const e = new Uint8Array(a.length / 2); for (let t = 0; t < e.length; t++)e[t] = parseInt(a.substring(2 * t, 2 * t + 2), 16); const t = e.length; for (; ;)try { for (let a = 0; a < r; a++) { const a = s + i, r = n.encode(a), o = await crypto.subtle.digest("SHA-256", r), c = new Uint8Array(o, 0, t); let l = !0; for (let s = 0; s < t; s++)if (c[s] !== e[s]) { l = !1; break } if (l) return void self.postMessage({ nonce: i, found: !0 }); i++ } o += r } catch (e) { return console.error("[cap] fallback worker error", e), void self.postMessage({ found: !1, error: e.message }) } } e || (e = await hashwasm.createSHA256()); const c = new Uint8Array(128); for (; ;)try { for (let t = 0; t < r; t++) { const t = s + i.toString(), r = n.encode(t); c.set(r), e.init(), e.update(c.subarray(0, r.length)); if (e.digest("hex").startsWith(a)) return void self.postMessage({ nonce: i, found: !0 }); i++ } o += r } catch (e) { return void self.postMessage({ found: !1, error: e.message }) } } }; setTimeout((async function () { e = await (await fetch("https://cdn.jsdelivr.net/npm/@cap.js/widget/wasm-hashes.min.js")).text() + u.toString().replace(/^function\s*\([^\)]*\)\s*{|\}$/g, "").trim() }), 1), window.Cap = p, customElements.get("cap-widget") ? console.warn("The cap-widget element has already been defined. Skipping re-defining it.") : customElements.define("cap-widget", h), "object" == typeof exports && "undefined" != typeof module ? module.exports = p : "function" == typeof define && define.amd && define([], (function () { return p })), "undefined" != typeof exports && (exports.default = p) }(); \ No newline at end of file diff --git a/widget/src/cap.min.js b/widget/src/cap.min.js index 851a924..0e37fb4 100644 --- a/widget/src/cap.min.js +++ b/widget/src/cap.min.js @@ -1 +1 @@ -"use strict";!function(){let e;const t=(e,t=1e4)=>new Promise(((r,s)=>{const a=setTimeout((()=>{s(new Error("Initialize timeout"))}),t),i=()=>{e()?(clearTimeout(a),r()):setTimeout(i,500)};i()}));class r extends HTMLElement{#e="";#t=null;#r=navigator.hardwareConcurrency||8;#s=null;#a;#i;#n;#o=!1;#c;static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","workers"]}constructor(){super(),this.#c&&this.#c.forEach(((e,t)=>{this.removeEventListener(t.slice(2),e)})),this.#c=new Map,this.boundHandleProgress=this.handleProgress.bind(this),this.boundHandleSolve=this.handleSolve.bind(this),this.boundHandleError=this.handleError.bind(this),this.boundHandleReset=this.handleReset.bind(this)}async initialize(){this.#e&&URL.revokeObjectURL(this.#e);try{await t((()=>!!e)),this.#e=URL.createObjectURL(new Blob([e],{type:"application/javascript"}))}catch(e){throw this.error("Failed to initialize worker"),e}}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),s=this.#c.get(e);if(s&&this.removeEventListener(t,s),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.#c.set(e,r),this.addEventListener(t,r)}}}async connectedCallback(){this.#n=this,this.#a=this.attachShadow({mode:"open"}),this.#i=document.createElement("div"),this.createUI(),this.addEventListeners(),await this.initialize(),this.#i.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count");this.setWorkersCount(parseInt(e)?parseInt(e,10):navigator.hardwareConcurrency||8),this.#n.innerHTML=''}async solve(){if(!this.#o)try{this.#o=!0,this.updateUI("verifying","Verifying...",!0),await t((()=>!!this.#e)),this.dispatchEvent("progress",{progress:0});try{const e=this.getAttribute("data-cap-api-endpoint");if(!e)throw new Error("Missing API endpoint");const{challenge:t,target:r,token:s}=await(await fetch(`${e}challenge`,{method:"POST"})).json(),a=await this.solveChallenges(t),i=await(await fetch(`${e}redeem`,{method:"POST",body:JSON.stringify({token:s,solutions:a}),headers:{"Content-Type":"application/json"}})).json();if(!i.success)throw new Error("Invalid solution");this.dispatchEvent("progress",{progress:100}),this.dispatchEvent("solve",{token:i.token}),this.#s=i.token,this.querySelector("input[name='cap-token']")&&(this.querySelector("input[name='cap-token']").value=i.token),this.#t&&clearTimeout(this.#t);const n=new Date(i.expires).getTime()-Date.now();return n>0&&n<864e5?this.#t=setTimeout((()=>this.reset()),n):this.error("Invalid expiration time"),{success:!0,token:this.#s}}catch(e){throw this.error(e.message),e}}finally{this.#o=!1}}async solveChallenges(e){const t=e.length;let r=0;const s=Array(this.#r).fill(null).map((()=>new Worker(this.#e))),a=([e,a],i)=>new Promise(((n,o)=>{const c=s[i],d=setTimeout((()=>{c.terminate(),s[i]=new Worker(this.#e),o(new Error("Worker timeout"))}),3e4);c.onmessage=({data:s})=>{s.found&&(clearTimeout(d),r++,this.dispatchEvent("progress",{progress:Math.round(r/t*100)}),n([e,a,s.nonce]))},c.onerror=e=>{clearTimeout(d),this.error(`Error in worker: ${e}`),o(e)},c.postMessage({salt:e,target:a})})),i=[];try{for(let t=0;ta(e,t))));i.push(...s)}}finally{s.forEach((e=>e.terminate()))}return i}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#r=!isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}createUI(){this.#i.classList.add("captcha"),this.#i.setAttribute("role","button"),this.#i.setAttribute("tabindex","0"),this.#i.setAttribute("disabled","true"),this.#i.innerHTML='

I\'m a human

Secured by Cap',this.#a.innerHTML='',this.#a.appendChild(this.#i)}addEventListeners(){this.#i.querySelector("a").addEventListener("click",(e=>{e.stopPropagation(),e.preventDefault(),window.open("#","_blank")})),this.#i.addEventListener("click",(()=>{this.#i.hasAttribute("disabled")||this.solve()})),this.addEventListener("progress",this.boundHandleProgress),this.addEventListener("solve",this.boundHandleSolve),this.addEventListener("error",this.boundHandleError),this.addEventListener("reset",this.boundHandleReset)}updateUI(e,t,r=!1){this.#i.setAttribute("data-state",e),this.#i.querySelector("p").innerText=t,r?this.#i.setAttribute("disabled","true"):this.#i.removeAttribute("disabled")}handleProgress(e){const t=this.#i.querySelector("p");t&&(this.#i.querySelector(".checkbox").style.setProperty("--progress",`${e.detail.progress}%`),t.innerText=`Verifying... ${e.detail.progress}%`),this.executeAttributeCode("onprogress",e)}handleSolve(e){this.updateUI("done","You're a human",!0),this.executeAttributeCode("onsolve",e)}handleError(e){this.updateUI("error","Error. Try again."),this.executeAttributeCode("onerror",e)}handleReset(e){this.updateUI("","I'm a human"),this.executeAttributeCode("onreset",e)}executeAttributeCode(e,t){const r=this.getAttribute(e);if(!r)return;new Function("event",r).call(this,t)}error(e="Unknown error"){console.error("[Cap] Error:",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const r=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(r)}reset(){this.#t&&(clearTimeout(this.#t),this.#t=null),this.dispatchEvent("reset"),this.#s=null,this.querySelector("input[name='cap-token']")&&(this.querySelector("input[name='cap-token']").value="")}get token(){return this.#s}disconnectedCallback(){this.removeEventListener("progress",this.boundHandleProgress),this.removeEventListener("solve",this.boundHandleSolve),this.removeEventListener("error",this.boundHandleError),this.removeEventListener("reset",this.boundHandleReset),this.#c.forEach(((e,t)=>{this.removeEventListener(t.slice(2),e)})),this.#c.clear(),this.#a&&(this.#a.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#t&&(clearTimeout(this.#t),this.#t=null),this.#e&&(URL.revokeObjectURL(this.#e),this.#e="")}}class s{constructor(e={},t){let r=t||document.createElement("cap-widget");if(Object.entries(e).forEach((([e,t])=>{r.setAttribute(e,t)})),!e.apiEndpoint)throw r.remove(),new Error("Missing API endpoint");console.log(e),r.setAttribute("data-cap-api-endpoint",e.apiEndpoint),this.widget=r,this.solve=this.widget.solve.bind(this.widget),this.reset=this.widget.reset.bind(this.widget),this.addEventListener=this.widget.addEventListener.bind(this.widget),Object.defineProperty(this,"token",{get:()=>r.getToken(),configurable:!0,enumerable:!0}),t||(r.style.display="none",document.documentElement.appendChild(r))}}const a=new CSSStyleSheet;a.replaceSync('html{--cap-font:system,-apple-system,"BlinkMacSystemFont",".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande","Ubuntu","arial",sans-serif;--cap-color:#212121;--cap-background:#fdfdfd;--cap-border-color:#dddddd8f;--cap-border-radius:14px;--cap-checkbox-border:1px solid #aaaaaad1;--cap-checkbox-border-radius:6px;--cap-checkbox-background:#fafafa91;--cap-widget-width:240px;--cap-widget-padding:14px;--cap-checkbox-size:24px;--cap-checkbox-margin:2px;--cap-transition-duration:0.2s;--cap-gap:15px;--cap-opacity-hover:0.8;--cap-hover-filter:brightness(97%);--cap-active-scale:0.98;--cap-credits-font-size:12px;--cap-spinner-color:black;--cap-spinner-background-color:#eee;--cap-error-cross:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'96\' height=\'96\' viewBox=\'0 0 24 24\'%3E%3Cpath fill=\'%23f55b50\' d=\'M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8\'/%3E%3C/svg%3E");--cap-checkmark:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E");--cap-spinner-thickness:5px;}'),document.adoptedStyleSheets.push(a);const i=function(){let e;self.onmessage=async({data:{salt:t,target:r}})=>{let s=0;const a=5e4;let i=0;const n=new TextEncoder;if("object"!=typeof WebAssembly||"function"!=typeof WebAssembly.instantiate){console.log("[cap] WASM not enabled, falling back to crypto.subtle\nThis is significanty slower than the WASM implementation");const e=new Uint8Array(r.length/2);for(let t=0;t new Promise(((r, s) => { const a = setTimeout((() => { s(new Error("Initialize timeout")) }), t), i = () => { e() ? (clearTimeout(a), r()) : setTimeout(i, 500) }; i() })); class r extends HTMLElement { #e = ""; #t = null; #r = navigator.hardwareConcurrency || 8; #s = null; #a; #i; #n; #o = !1; #c; static get observedAttributes() { return ["onsolve", "onprogress", "onreset", "onerror", "workers"] } constructor() { super(), this.#c && this.#c.forEach(((e, t) => { this.removeEventListener(t.slice(2), e) })), this.#c = new Map, this.boundHandleProgress = this.handleProgress.bind(this), this.boundHandleSolve = this.handleSolve.bind(this), this.boundHandleError = this.handleError.bind(this), this.boundHandleReset = this.handleReset.bind(this) } async initialize() { this.#e && URL.revokeObjectURL(this.#e); try { await t((() => !!e)), this.#e = URL.createObjectURL(new Blob([e], { type: "application/javascript" })) } catch (e) { throw this.error("Failed to initialize worker"), e } } attributeChangedCallback(e, t, r) { if (e.startsWith("on")) { const t = e.slice(2), s = this.#c.get(e); if (s && this.removeEventListener(t, s), r) { const r = t => { const r = this.getAttribute(e); "function" == typeof window[r] && window[r].call(this, t) }; this.#c.set(e, r), this.addEventListener(t, r) } } } async connectedCallback() { this.#n = this, this.#a = this.attachShadow({ mode: "open" }), this.#i = document.createElement("div"), this.createUI(), this.addEventListeners(), await this.initialize(), this.#i.removeAttribute("disabled"); const e = this.getAttribute("data-cap-worker-count"); this.setWorkersCount(parseInt(e) ? parseInt(e, 10) : navigator.hardwareConcurrency || 8), this.#n.innerHTML = '' } async solve() { if (!this.#o) try { this.#o = !0, this.updateUI("verifying", "Verifying...", !0), await t((() => !!this.#e)), this.dispatchEvent("progress", { progress: 0 }); try { const e = this.getAttribute("data-cap-api-endpoint"); if (!e) throw new Error("Missing API endpoint"); const { challenge: t, target: r, token: s } = await (await fetch(`${e}challenge`, { method: "POST" })).json(), a = await this.solveChallenges(t), i = await (await fetch(`${e}redeem`, { method: "POST", body: JSON.stringify({ token: s, solutions: a }), headers: { "Content-Type": "application/json" } })).json(); if (!i.success) throw new Error("Invalid solution"); this.dispatchEvent("progress", { progress: 100 }), this.dispatchEvent("solve", { token: i.token }), this.#s = i.token, this.querySelector("input[name='cap-token']") && (this.querySelector("input[name='cap-token']").value = i.token), this.#t && clearTimeout(this.#t); const n = new Date(i.expires).getTime() - Date.now(); return n > 0 && n < 864e5 ? this.#t = setTimeout((() => this.reset()), n) : this.error("Invalid expiration time"), { success: !0, token: this.#s } } catch (e) { throw this.error(e.message), e } } finally { this.#o = !1 } } async solveChallenges(e) { const t = e.length; let r = 0; const s = Array(this.#r).fill(null).map((() => new Worker(this.#e))), a = ([e, a], i) => new Promise(((n, o) => { const c = s[i], d = setTimeout((() => { c.terminate(), s[i] = new Worker(this.#e), o(new Error("Worker timeout")) }), 3e4); c.onmessage = ({ data: s }) => { s.found && (clearTimeout(d), r++, this.dispatchEvent("progress", { progress: Math.round(r / t * 100) }), n([e, a, s.nonce])) }, c.onerror = e => { clearTimeout(d), this.error(`Error in worker: ${e}`), o(e) }, c.postMessage({ salt: e, target: a }) })), i = []; try { for (let t = 0; t < e.length; t += this.#r) { const r = e.slice(t, Math.min(t + this.#r, e.length)), s = await Promise.all(r.map(((e, t) => a(e, t)))); i.push(...s) } } finally { s.forEach((e => e.terminate())) } return i } setWorkersCount(e) { const t = parseInt(e, 10), r = Math.min(navigator.hardwareConcurrency || 8, 16); this.#r = !isNaN(t) && t > 0 && t <= r ? t : navigator.hardwareConcurrency || 8 } createUI() { this.#i.classList.add("captcha"), this.#i.setAttribute("role", "button"), this.#i.setAttribute("tabindex", "0"), this.#i.setAttribute("disabled", "true"), this.#i.innerHTML = '

I\'m a human

Secured by Cap', this.#a.innerHTML = '', this.#a.appendChild(this.#i) } addEventListeners() { this.#i.querySelector("a").addEventListener("click", (e => { e.stopPropagation(), e.preventDefault(), window.open("#", "_blank") })), this.#i.addEventListener("click", (() => { this.#i.hasAttribute("disabled") || this.solve() })), this.addEventListener("progress", this.boundHandleProgress), this.addEventListener("solve", this.boundHandleSolve), this.addEventListener("error", this.boundHandleError), this.addEventListener("reset", this.boundHandleReset) } updateUI(e, t, r = !1) { this.#i.setAttribute("data-state", e), this.#i.querySelector("p").innerText = t, r ? this.#i.setAttribute("disabled", "true") : this.#i.removeAttribute("disabled") } handleProgress(e) { const t = this.#i.querySelector("p"); t && (this.#i.querySelector(".checkbox").style.setProperty("--progress", `${e.detail.progress}%`), t.innerText = `Verifying... ${e.detail.progress}%`), this.executeAttributeCode("onprogress", e) } handleSolve(e) { this.updateUI("done", "You're a human", !0), this.executeAttributeCode("onsolve", e) } handleError(e) { this.updateUI("error", "Error. Try again."), this.executeAttributeCode("onerror", e) } handleReset(e) { this.updateUI("", "I'm a human"), this.executeAttributeCode("onreset", e) } executeAttributeCode(e, t) { const r = this.getAttribute(e); if (!r) return; new Function("event", r).call(this, t) } error(e = "Unknown error") { console.error("[Cap] Error:", e), this.dispatchEvent("error", { isCap: !0, message: e }) } dispatchEvent(e, t = {}) { const r = new CustomEvent(e, { bubbles: !0, composed: !0, detail: t }); super.dispatchEvent(r) } reset() { this.#t && (clearTimeout(this.#t), this.#t = null), this.dispatchEvent("reset"), this.#s = null, this.querySelector("input[name='cap-token']") && (this.querySelector("input[name='cap-token']").value = "") } get token() { return this.#s } disconnectedCallback() { this.removeEventListener("progress", this.boundHandleProgress), this.removeEventListener("solve", this.boundHandleSolve), this.removeEventListener("error", this.boundHandleError), this.removeEventListener("reset", this.boundHandleReset), this.#c.forEach(((e, t) => { this.removeEventListener(t.slice(2), e) })), this.#c.clear(), this.#a && (this.#a.innerHTML = ""), this.reset(), this.cleanup() } cleanup() { this.#t && (clearTimeout(this.#t), this.#t = null), this.#e && (URL.revokeObjectURL(this.#e), this.#e = "") } } class s { constructor(e = {}, t) { let r = t || document.createElement("cap-widget"); if (Object.entries(e).forEach((([e, t]) => { r.setAttribute(e, t) })), !e.apiEndpoint) throw r.remove(), new Error("Missing API endpoint"); console.log(e), r.setAttribute("data-cap-api-endpoint", e.apiEndpoint), this.widget = r, this.solve = this.widget.solve.bind(this.widget), this.reset = this.widget.reset.bind(this.widget), this.addEventListener = this.widget.addEventListener.bind(this.widget), Object.defineProperty(this, "token", { get: () => r.getToken(), configurable: !0, enumerable: !0 }), t || (r.style.display = "none", document.documentElement.appendChild(r)) } } const a = new CSSStyleSheet; a.replaceSync('html{--cap-font:system,-apple-system,"BlinkMacSystemFont",".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande","Ubuntu","arial",sans-serif;--cap-color:#212121;--cap-background:#fdfdfd;--cap-border-color:#dddddd8f;--cap-border-radius:14px;--cap-checkbox-border:1px solid #aaaaaad1;--cap-checkbox-border-radius:6px;--cap-checkbox-background:#fafafa91;--cap-widget-width:240px;--cap-widget-padding:14px;--cap-checkbox-size:24px;--cap-checkbox-margin:2px;--cap-transition-duration:0.2s;--cap-gap:15px;--cap-opacity-hover:0.8;--cap-hover-filter:brightness(97%);--cap-active-scale:0.98;--cap-credits-font-size:12px;--cap-spinner-color:black;--cap-spinner-background-color:#eee;--cap-error-cross:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'96\' height=\'96\' viewBox=\'0 0 24 24\'%3E%3Cpath fill=\'%23f55b50\' d=\'M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8\'/%3E%3C/svg%3E");--cap-checkmark:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E");--cap-spinner-thickness:5px;}'), document.adoptedStyleSheets.push(a); const i = function () { let e; self.onmessage = async ({ data: { salt: t, target: r } }) => { let s = 0; const a = 5e4; let i = 0; const n = new TextEncoder; if ("object" != typeof WebAssembly || "function" != typeof WebAssembly.instantiate) { console.log("[cap] WASM not enabled, falling back to crypto.subtle\nThis is significanty slower than the WASM implementation"); const e = new Uint8Array(r.length / 2); for (let t = 0; t < e.length; t++)e[t] = parseInt(r.substring(2 * t, 2 * t + 2), 16); const o = e.length; for (; ;)try { for (let r = 0; r < a; r++) { const r = t + s, a = n.encode(r), i = await crypto.subtle.digest("SHA-256", a), c = new Uint8Array(i, 0, o); let d = !0; for (let t = 0; t < o; t++)if (c[t] !== e[t]) { d = !1; break } if (d) return void self.postMessage({ nonce: s, found: !0 }); s++ } i += a } catch (e) { return console.error("[cap] fallback worker error", e), void self.postMessage({ found: !1, error: e.message }) } } e || (e = await hashwasm.createSHA256()); const o = new Uint8Array(128); for (; ;)try { for (let i = 0; i < a; i++) { const a = t + s.toString(), i = n.encode(a); o.set(i), e.init(), e.update(o.subarray(0, i.length)); if (e.digest("hex").startsWith(r)) return void self.postMessage({ nonce: s, found: !0 }); s++ } i += a } catch (e) { return void self.postMessage({ found: !1, error: e.message }) } } }; setTimeout((async function () { e = await (await fetch("https://cdn.jsdelivr.net/npm/@cap.js/widget/wasm-hashes.min.js")).text() + i.toString().replace(/^function\s*\([^\)]*\)\s*{|\}$/g, "").trim() }), 1), window.Cap = s, customElements.get("cap-widget") ? console.warn("The cap-widget element has already been defined. Skipping re-defining it.") : customElements.define("cap-widget", r), "object" == typeof exports && "undefined" != typeof module ? module.exports = s : "function" == typeof define && define.amd && define([], (function () { return s })), "undefined" != typeof exports && (exports.default = s) }(); \ No newline at end of file diff --git a/widget/src/package.json b/widget/src/package.json index 9b10155..269e6b8 100644 --- a/widget/src/package.json +++ b/widget/src/package.json @@ -1,6 +1,6 @@ { "name": "@cap.js/widget", - "version": "0.0.15", + "version": "0.0.16", "description": "Cap widget", "keywords": [ "captcha", diff --git a/widget/src/src/cap.js b/widget/src/src/cap.js index db37735..98ee2e7 100644 --- a/widget/src/src/cap.js +++ b/widget/src/src/cap.js @@ -225,8 +225,8 @@ const maxWorkers = Math.min(navigator.hardwareConcurrency || 8, 16); this.#workersCount = !isNaN(parsedWorkers) && - parsedWorkers > 0 && - parsedWorkers <= maxWorkers + parsedWorkers > 0 && + parsedWorkers <= maxWorkers ? parsedWorkers : navigator.hardwareConcurrency || 8; } @@ -236,7 +236,7 @@ this.#div.setAttribute("role", "button"); this.#div.setAttribute("tabindex", "0"); this.#div.setAttribute("disabled", "true"); - this.#div.innerHTML = `

I'm a human

Secured by Cap`; + this.#div.innerHTML = `

I'm a human

Secured by Cap`; this.#shadow.innerHTML = ``;