@cap.js/widget 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/cap.min.js +1 -1
- package/package.json +2 -2
- package/src/cap.js +181 -210
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Cap — A modern, lightning-quick PoW captcha
|
|
2
|
+
Copyright © 2025 Tiago Rangel
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
package/cap.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";!function(){let e;const t=(e,t=1e4)=>new Promise(((r,a)=>{const s=setTimeout((()=>{a(new Error("Initialize timeout"))}),t),i=()=>{e()?(clearTimeout(s),r()):setTimeout(i,500)};i()}));class r{#e="";#t=null;#r=null;#a=navigator.hardwareConcurrency||8;#s=null;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}}async solve(){await t((()=>!!this.#e)),this.dispatchEvent("progress",{progress:0});try{const e=this.#t.getAttribute("data-cap-api-endpoint");if(!e)throw new Error("Missing API endpoint");const{challenge:t,target:r,token:a}=await(await fetch(`${e}challenge`,{method:"POST"})).json(),s=await this.solveChallenges({challenge:t,target:r,token:a}),i=await(await fetch(`${e}redeem`,{method:"POST",body:JSON.stringify({token:a,solutions:s}),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.#t.querySelector("input[name='cap-token']")&&(this.#t.querySelector("input[name='cap-token']").value=i.token),this.#r&&clearTimeout(this.#r);const n=new Date(i.expires).getTime()-Date.now();return n>0&&n<864e5?this.#r=setTimeout((()=>this.reset()),n):this.error("Invalid expiration time"),{success:!0,token:this.#s}}catch(e){throw this.error(e.message),e}}async solveChallenges({challenge:e,target:t}){const r=e.length;let a=0;const s=Array(this.#a).fill(null).map((()=>new Worker(this.#e))),i=([e,t],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),a++,this.dispatchEvent("progress",{progress:Math.round(a/r*100)}),n([e,t,s.nonce]))},c.onerror=e=>{clearTimeout(d),this.error(`Error in worker: ${e}`),o(e)},c.postMessage({salt:e,target:t})})),n=[];try{for(let t=0;t<e.length;t+=this.#a){const r=e.slice(t,Math.min(t+this.#a,e.length)),a=await Promise.all(r.map(((e,t)=>i(e,t))));n.push(...a)}}finally{s.forEach((e=>e.terminate()))}return n}reset(){this.#r&&(clearTimeout(this.#r),this.#r=null),this.dispatchEvent("reset"),this.#s=null,this.#t.querySelector("input[name='cap-token']")&&(this.#t.querySelector("input[name='cap-token']").value="")}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});this.#t.dispatchEvent(r)}setElement(e){this.#t=e}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#a=!isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}getToken(){return this.#s}cleanup(){this.#r&&(clearTimeout(this.#r),this.#r=null),this.#e&&(URL.revokeObjectURL(this.#e),this.#e="")}}class a extends HTMLElement{#i=new r;#n;#o;#c;#d=!1;eventHandlers;static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","workers"]}constructor(){super(),this.eventHandlers&&this.eventHandlers.forEach(((e,t)=>{this.removeEventListener(t.slice(2),e)})),this.eventHandlers=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)}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),a=this.eventHandlers.get(e);if(a&&this.removeEventListener(t,a),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.eventHandlers.set(e,r),this.addEventListener(t,r)}}}async connectedCallback(){this.#c=this,this.#n=this.attachShadow({mode:"open"}),this.#i.setElement(this),this.#o=document.createElement("div"),this.createUI(),this.addEventListeners(),await this.#i.initialize(),this.#o.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count");this.#i.setWorkersCount(parseInt(e)?parseInt(e,10):navigator.hardwareConcurrency||8),this.#c.innerHTML='<input type="hidden" name="cap-token">'}createUI(){this.#o.classList.add("captcha"),this.#o.setAttribute("role","button"),this.#o.setAttribute("tabindex","0"),this.#o.setAttribute("disabled","true"),this.#o.innerHTML='<div class="checkbox"></div><p>I\'m a human</p><a href="#" class="credits" target="_blank"><span>Secured by </span>Cap</a>',this.#n.innerHTML='<style>.captcha{background-color:var(--cap-background);border:1px solid var(--cap-border-color);border-radius:var(--cap-border-radius);width:var(--cap-widget-width);display:flex;align-items:center;padding:var(--cap-widget-padding);gap:var(--cap-gap);cursor:pointer;transition:filter var(--cap-transition-duration),transform var(--cap-transition-duration);position:relative;-webkit-tap-highlight-color:rgba(255,255,255,0);overflow:hidden;color:var(--cap-color)}.captcha:hover{filter:var(--cap-hover-filter)}.captcha:not([disabled]):active{transform:scale(var(--cap-active-scale))}.checkbox{width:var(--cap-checkbox-size);height:var(--cap-checkbox-size);border:var(--cap-checkbox-border);border-radius:var(--cap-checkbox-border-radius);background-color:var(--cap-checkbox-background);transition:opacity var(--cap-transition-duration);margin-top:var(--cap-checkbox-margin);margin-bottom:var(--cap-checkbox-margin)}.captcha *{font-family:var(--cap-font)}.captcha p{margin:0;font-weight:500;font-size:15px;user-select:none;transition:opacity var(--cap-transition-duration)}.captcha[data-state=verifying] .checkbox{background: none;display:flex;align-items:center;justify-content:center;transform: scale(1.1);border: none;border-radius: 50%;background: conic-gradient(var(--cap-spinner-color) 0%, var(--cap-spinner-color) var(--progress, 0%), var(--cap-spinner-background-color) var(--progress, 0%), var(--cap-spinner-background-color) 100%);position: relative;}.captcha[data-state=verifying] .checkbox::after {content: "";background-color: var(--cap-background);width: calc(100% - var(--cap-spinner-thickness));height: calc(100% - var(--cap-spinner-thickness));border-radius: 50%;margin:calc(var(--cap-spinner-thickness) / 2)}.captcha[data-state=done] .checkbox{border:1px solid transparent;background-image:var(--cap-checkmark);background-size:cover}.captcha[data-state=error] .checkbox{border:1px solid transparent;background-image:var(--cap-error-cross);background-size:cover}.captcha[disabled]{\ncursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{position:absolute;bottom:10px;right:10px;font-size:var(--cap-credits-font-size);color:var(--cap-color);opacity:var(--cap-opacity-hover)}.captcha .credits span{display:none;text-decoration:underline}.captcha .credits:hover span{display:inline-block}</style>',this.#n.appendChild(this.#o)}addEventListeners(){this.#o.querySelector("a").addEventListener("click",(e=>{e.stopPropagation(),e.preventDefault(),window.open("#","_blank")})),this.#o.addEventListener("click",(()=>{this.#o.hasAttribute("disabled")||this.solve()})),this.addEventListener("progress",this.boundHandleProgress),this.addEventListener("solve",this.boundHandleSolve),this.addEventListener("error",this.boundHandleError),this.addEventListener("reset",this.boundHandleReset)}async solve(){if(!this.#d)try{this.#d=!0,this.updateUI("verifying","Verifying...",!0);return await this.#i.solve()}finally{this.#d=!1}}updateUI(e,t,r=!1){this.#o.setAttribute("data-state",e),this.#o.querySelector("p").innerText=t,r?this.#o.setAttribute("disabled","true"):this.#o.removeAttribute("disabled")}handleProgress(e){const t=this.#o.querySelector("p");t&&(this.#o.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)}reset(){this.#i.reset()}get token(){return this.#i.getToken()}disconnectedCallback(){this.removeEventListener("progress",this.boundHandleProgress),this.removeEventListener("solve",this.boundHandleSolve),this.removeEventListener("error",this.boundHandleError),this.removeEventListener("reset",this.boundHandleReset),this.eventHandlers.forEach(((e,t)=>{this.removeEventListener(t.slice(2),e)})),this.eventHandlers.clear(),this.#n&&(this.#n.innerHTML=""),this.#i.reset(),this.#i.cleanup()}}class s{constructor(e,t={}){let a=new r,s=e||document.createElement("div");e||(s.style.display="none"),Object.entries(t).forEach((([e,t])=>{s.setAttribute(e,t)})),t.apiEndpoint&&s.setAttribute("data-cap-api-endpoint",t.apiEndpoint),a.setElement(s),a.setWorkersCount(t.workers||navigator.hardwareConcurrency||8),a.initialize(),this.solve=async function(){return await a.solve()},this.reset=function(){a.reset()},this.addEventListener=function(e,t){s.addEventListener(e,t)},Object.defineProperty(this,"token",{get:()=>a.getToken(),configurable:!0,enumerable:!0})}}const i=new CSSStyleSheet;i.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(i);const n=function(){let e;self.onmessage=async({data:{salt:t,target:r}})=>{e||(e=await hashwasm.createSHA256());let a=0;let s=0;const i=new Uint8Array(128),n=new TextEncoder;for(;;)try{for(let s=0;s<5e4;s++){const s=t+a.toString(),o=n.encode(s);i.set(o),e.init(),e.update(i.subarray(0,o.length));if(e.digest("hex").startsWith(r))return void self.postMessage({nonce:a,found:!0});a++}s+=5e4,s>=5e5&&(self.postMessage({nonce:a,found:!1}),s=0)}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()+n.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",a),"object"==typeof exports&&"undefined"!=typeof module?module.exports=s:"function"==typeof define&&define.amd&&define([],(function(){return s})),"undefined"!=typeof exports&&(exports.default=s)}();
|
|
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='<input type="hidden" name="cap-token">'}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({challenge:t,target:r,token:s}),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({challenge:e,target:t}){const r=e.length;let s=0;const a=Array(this.#r).fill(null).map((()=>new Worker(this.#e))),i=([e,t],i)=>new Promise(((n,o)=>{const c=a[i],d=setTimeout((()=>{c.terminate(),a[i]=new Worker(this.#e),o(new Error("Worker timeout"))}),3e4);c.onmessage=({data:a})=>{a.found&&(clearTimeout(d),s++,this.dispatchEvent("progress",{progress:Math.round(s/r*100)}),n([e,t,a.nonce]))},c.onerror=e=>{clearTimeout(d),this.error(`Error in worker: ${e}`),o(e)},c.postMessage({salt:e,target:t})})),n=[];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)=>i(e,t))));n.push(...s)}}finally{a.forEach((e=>e.terminate()))}return n}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='<div class="checkbox"></div><p>I\'m a human</p><a href="#" class="credits" target="_blank"><span>Secured by </span>Cap</a>',this.#a.innerHTML='<style>.captcha{background-color:var(--cap-background);border:1px solid var(--cap-border-color);border-radius:var(--cap-border-radius);width:var(--cap-widget-width);display:flex;align-items:center;padding:var(--cap-widget-padding);gap:var(--cap-gap);cursor:pointer;transition:filter var(--cap-transition-duration),transform var(--cap-transition-duration);position:relative;-webkit-tap-highlight-color:rgba(255,255,255,0);overflow:hidden;color:var(--cap-color)}.captcha:hover{filter:var(--cap-hover-filter)}.captcha:not([disabled]):active{transform:scale(var(--cap-active-scale))}.checkbox{width:var(--cap-checkbox-size);height:var(--cap-checkbox-size);border:var(--cap-checkbox-border);border-radius:var(--cap-checkbox-border-radius);background-color:var(--cap-checkbox-background);transition:opacity var(--cap-transition-duration);margin-top:var(--cap-checkbox-margin);margin-bottom:var(--cap-checkbox-margin)}.captcha *{font-family:var(--cap-font)}.captcha p{margin:0;font-weight:500;font-size:15px;user-select:none;transition:opacity var(--cap-transition-duration)}.captcha[data-state=verifying] .checkbox{background: none;display:flex;align-items:center;justify-content:center;transform: scale(1.1);border: none;border-radius: 50%;background: conic-gradient(var(--cap-spinner-color) 0%, var(--cap-spinner-color) var(--progress, 0%), var(--cap-spinner-background-color) var(--progress, 0%), var(--cap-spinner-background-color) 100%);position: relative;}.captcha[data-state=verifying] .checkbox::after {content: "";background-color: var(--cap-background);width: calc(100% - var(--cap-spinner-thickness));height: calc(100% - var(--cap-spinner-thickness));border-radius: 50%;margin:calc(var(--cap-spinner-thickness) / 2)}.captcha[data-state=done] .checkbox{border:1px solid transparent;background-image:var(--cap-checkmark);background-size:cover}.captcha[data-state=error] .checkbox{border:1px solid transparent;background-image:var(--cap-error-cross);background-size:cover}.captcha[disabled]{cursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{position:absolute;bottom:10px;right:10px;font-size:var(--cap-credits-font-size);color:var(--cap-color);opacity:var(--cap-opacity-hover)}.captcha .credits span{display:none;text-decoration:underline}.captcha .credits:hover span{display:inline-block}</style>',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=r.solve,this.reset=r.reset,this.addEventListener=r.addEventListener,Object.defineProperty(this,"token",{get:()=>r.getToken(),configurable:!0,enumerable:!0})}}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}})=>{e||(e=await hashwasm.createSHA256());let s=0;let a=0;const i=new Uint8Array(128),n=new TextEncoder;for(;;)try{for(let a=0;a<5e4;a++){const a=t+s.toString(),o=n.encode(a);i.set(o),e.init(),e.update(i.subarray(0,o.length));if(e.digest("hex").startsWith(r))return void self.postMessage({nonce:s,found:!0});s++}a+=5e4,a>=5e5&&(self.postMessage({nonce:s,found:!1}),a=0)}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)}();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap.js/widget",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "Cap widget",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"captcha",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"type": "git",
|
|
22
22
|
"url": "git+https://github.com/tiagorangel1/cap.git"
|
|
23
23
|
},
|
|
24
|
-
"license": "AGPL-3.0",
|
|
24
|
+
"license": "AGPL-3.0-only",
|
|
25
25
|
"author": "Tiago Rangel",
|
|
26
26
|
"type": "commonjs",
|
|
27
27
|
"main": "cap.min.js",
|
package/src/cap.js
CHANGED
|
@@ -19,12 +19,35 @@
|
|
|
19
19
|
});
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
class
|
|
22
|
+
class CapWidget extends HTMLElement {
|
|
23
23
|
#workerUrl = "";
|
|
24
|
-
#el = null;
|
|
25
24
|
#resetTimer = null;
|
|
26
25
|
#workersCount = navigator.hardwareConcurrency || 8;
|
|
27
26
|
#token = null;
|
|
27
|
+
#shadow;
|
|
28
|
+
#div;
|
|
29
|
+
#host;
|
|
30
|
+
#solving = false;
|
|
31
|
+
#eventHandlers;
|
|
32
|
+
|
|
33
|
+
static get observedAttributes() {
|
|
34
|
+
return ["onsolve", "onprogress", "onreset", "onerror", "workers"];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
if (this.#eventHandlers) {
|
|
40
|
+
this.#eventHandlers.forEach((handler, eventName) => {
|
|
41
|
+
this.removeEventListener(eventName.slice(2), handler);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.#eventHandlers = new Map();
|
|
46
|
+
this.boundHandleProgress = this.handleProgress.bind(this);
|
|
47
|
+
this.boundHandleSolve = this.handleSolve.bind(this);
|
|
48
|
+
this.boundHandleError = this.handleError.bind(this);
|
|
49
|
+
this.boundHandleReset = this.handleReset.bind(this);
|
|
50
|
+
}
|
|
28
51
|
|
|
29
52
|
async initialize() {
|
|
30
53
|
if (this.#workerUrl) {
|
|
@@ -44,60 +67,104 @@
|
|
|
44
67
|
}
|
|
45
68
|
}
|
|
46
69
|
|
|
70
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
71
|
+
if (name.startsWith("on")) {
|
|
72
|
+
const eventName = name.slice(2);
|
|
73
|
+
const oldHandler = this.#eventHandlers.get(name);
|
|
74
|
+
if (oldHandler) {
|
|
75
|
+
this.removeEventListener(eventName, oldHandler);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (newValue) {
|
|
79
|
+
const handler = (event) => {
|
|
80
|
+
const callback = this.getAttribute(name);
|
|
81
|
+
if (typeof window[callback] === "function") {
|
|
82
|
+
window[callback].call(this, event);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
this.#eventHandlers.set(name, handler);
|
|
86
|
+
this.addEventListener(eventName, handler);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async connectedCallback() {
|
|
92
|
+
this.#host = this;
|
|
93
|
+
this.#shadow = this.attachShadow({ mode: "open" });
|
|
94
|
+
this.#div = document.createElement("div");
|
|
95
|
+
this.createUI();
|
|
96
|
+
this.addEventListeners();
|
|
97
|
+
await this.initialize();
|
|
98
|
+
this.#div.removeAttribute("disabled");
|
|
99
|
+
|
|
100
|
+
const workers = this.getAttribute("data-cap-worker-count");
|
|
101
|
+
this.setWorkersCount(
|
|
102
|
+
parseInt(workers)
|
|
103
|
+
? parseInt(workers, 10)
|
|
104
|
+
: navigator.hardwareConcurrency || 8
|
|
105
|
+
);
|
|
106
|
+
this.#host.innerHTML = `<input type="hidden" name="cap-token">`;
|
|
107
|
+
}
|
|
108
|
+
|
|
47
109
|
async solve() {
|
|
48
|
-
|
|
49
|
-
|
|
110
|
+
if (this.#solving) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
50
113
|
|
|
51
114
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
115
|
+
this.#solving = true;
|
|
116
|
+
this.updateUI("verifying", "Verifying...", true);
|
|
54
117
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
).json();
|
|
63
|
-
const solutions = await this.solveChallenges({
|
|
64
|
-
challenge,
|
|
65
|
-
target,
|
|
66
|
-
token,
|
|
67
|
-
});
|
|
118
|
+
await until(() => !!this.#workerUrl);
|
|
119
|
+
this.dispatchEvent("progress", { progress: 0 });
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const apiEndpoint = this.getAttribute("data-cap-api-endpoint");
|
|
123
|
+
if (!apiEndpoint) throw new Error("Missing API endpoint");
|
|
68
124
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
125
|
+
const { challenge, target, token } = await (
|
|
126
|
+
await fetch(`${apiEndpoint}challenge`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
})
|
|
129
|
+
).json();
|
|
130
|
+
const solutions = await this.solveChallenges({
|
|
131
|
+
challenge,
|
|
132
|
+
target,
|
|
133
|
+
token,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const resp = await (
|
|
137
|
+
await fetch(`${apiEndpoint}redeem`, {
|
|
73
138
|
method: "POST",
|
|
74
139
|
body: JSON.stringify({ token, solutions }),
|
|
75
140
|
headers: { "Content-Type": "application/json" },
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
).json();
|
|
141
|
+
})
|
|
142
|
+
).json();
|
|
79
143
|
|
|
80
|
-
|
|
144
|
+
if (!resp.success) throw new Error("Invalid solution");
|
|
81
145
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
146
|
+
this.dispatchEvent("progress", { progress: 100 });
|
|
147
|
+
this.dispatchEvent("solve", { token: resp.token });
|
|
148
|
+
this.#token = resp.token;
|
|
149
|
+
if (this.querySelector("input[name='cap-token']")) {
|
|
150
|
+
this.querySelector("input[name='cap-token']").value = resp.token;
|
|
151
|
+
}
|
|
88
152
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
153
|
+
if (this.#resetTimer) clearTimeout(this.#resetTimer);
|
|
154
|
+
const expiresIn = new Date(resp.expires).getTime() - Date.now();
|
|
155
|
+
if (expiresIn > 0 && expiresIn < 24 * 60 * 60 * 1000) {
|
|
156
|
+
this.#resetTimer = setTimeout(() => this.reset(), expiresIn);
|
|
157
|
+
} else {
|
|
158
|
+
this.error("Invalid expiration time");
|
|
159
|
+
}
|
|
96
160
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
161
|
+
return { success: true, token: this.#token };
|
|
162
|
+
} catch (err) {
|
|
163
|
+
this.error(err.message);
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
} finally {
|
|
167
|
+
this.#solving = false;
|
|
101
168
|
}
|
|
102
169
|
}
|
|
103
170
|
|
|
@@ -156,36 +223,6 @@
|
|
|
156
223
|
return results;
|
|
157
224
|
}
|
|
158
225
|
|
|
159
|
-
reset() {
|
|
160
|
-
if (this.#resetTimer) {
|
|
161
|
-
clearTimeout(this.#resetTimer);
|
|
162
|
-
this.#resetTimer = null;
|
|
163
|
-
}
|
|
164
|
-
this.dispatchEvent("reset");
|
|
165
|
-
this.#token = null;
|
|
166
|
-
if (this.#el.querySelector("input[name='cap-token']")) {
|
|
167
|
-
this.#el.querySelector("input[name='cap-token']").value = "";
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
error(message = "Unknown error") {
|
|
172
|
-
console.error("[Cap] Error:", message);
|
|
173
|
-
this.dispatchEvent("error", { isCap: true, message });
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
dispatchEvent(eventName, detail = {}) {
|
|
177
|
-
const event = new CustomEvent(eventName, {
|
|
178
|
-
bubbles: true,
|
|
179
|
-
composed: true,
|
|
180
|
-
detail,
|
|
181
|
-
});
|
|
182
|
-
this.#el.dispatchEvent(event);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
setElement(el) {
|
|
186
|
-
this.#el = el;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
226
|
setWorkersCount(workers) {
|
|
190
227
|
const parsedWorkers = parseInt(workers, 10);
|
|
191
228
|
const maxWorkers = Math.min(navigator.hardwareConcurrency || 8, 16);
|
|
@@ -197,89 +234,6 @@
|
|
|
197
234
|
: navigator.hardwareConcurrency || 8;
|
|
198
235
|
}
|
|
199
236
|
|
|
200
|
-
getToken() {
|
|
201
|
-
return this.#token;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
cleanup() {
|
|
205
|
-
if (this.#resetTimer) {
|
|
206
|
-
clearTimeout(this.#resetTimer);
|
|
207
|
-
this.#resetTimer = null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (this.#workerUrl) {
|
|
211
|
-
URL.revokeObjectURL(this.#workerUrl);
|
|
212
|
-
this.#workerUrl = "";
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
class CapWidget extends HTMLElement {
|
|
218
|
-
#capBase = new CapBase();
|
|
219
|
-
#shadow;
|
|
220
|
-
#div;
|
|
221
|
-
#host;
|
|
222
|
-
#solving = false;
|
|
223
|
-
eventHandlers;
|
|
224
|
-
|
|
225
|
-
static get observedAttributes() {
|
|
226
|
-
return ["onsolve", "onprogress", "onreset", "onerror", "workers"];
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
constructor() {
|
|
230
|
-
super();
|
|
231
|
-
if (this.eventHandlers) {
|
|
232
|
-
this.eventHandlers.forEach((handler, eventName) => {
|
|
233
|
-
this.removeEventListener(eventName.slice(2), handler);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
this.eventHandlers = new Map();
|
|
237
|
-
this.boundHandleProgress = this.handleProgress.bind(this);
|
|
238
|
-
this.boundHandleSolve = this.handleSolve.bind(this);
|
|
239
|
-
this.boundHandleError = this.handleError.bind(this);
|
|
240
|
-
this.boundHandleReset = this.handleReset.bind(this);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
244
|
-
if (name.startsWith("on")) {
|
|
245
|
-
const eventName = name.slice(2);
|
|
246
|
-
const oldHandler = this.eventHandlers.get(name);
|
|
247
|
-
if (oldHandler) {
|
|
248
|
-
this.removeEventListener(eventName, oldHandler);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (newValue) {
|
|
252
|
-
const handler = (event) => {
|
|
253
|
-
const callback = this.getAttribute(name);
|
|
254
|
-
if (typeof window[callback] === "function") {
|
|
255
|
-
window[callback].call(this, event);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
this.eventHandlers.set(name, handler);
|
|
259
|
-
this.addEventListener(eventName, handler);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async connectedCallback() {
|
|
265
|
-
this.#host = this;
|
|
266
|
-
this.#shadow = this.attachShadow({ mode: "open" });
|
|
267
|
-
this.#capBase.setElement(this);
|
|
268
|
-
this.#div = document.createElement("div");
|
|
269
|
-
this.createUI();
|
|
270
|
-
this.addEventListeners();
|
|
271
|
-
await this.#capBase.initialize();
|
|
272
|
-
this.#div.removeAttribute("disabled");
|
|
273
|
-
const workers = this.getAttribute("data-cap-worker-count");
|
|
274
|
-
|
|
275
|
-
this.#capBase.setWorkersCount(
|
|
276
|
-
parseInt(workers)
|
|
277
|
-
? parseInt(workers, 10)
|
|
278
|
-
: navigator.hardwareConcurrency || 8
|
|
279
|
-
);
|
|
280
|
-
this.#host.innerHTML = `<input type="hidden" name="cap-token">`;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
237
|
createUI() {
|
|
284
238
|
this.#div.classList.add("captcha");
|
|
285
239
|
this.#div.setAttribute("role", "button");
|
|
@@ -309,29 +263,14 @@
|
|
|
309
263
|
this.addEventListener("reset", this.boundHandleReset);
|
|
310
264
|
}
|
|
311
265
|
|
|
312
|
-
async solve() {
|
|
313
|
-
if (this.#solving) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
this.#solving = true;
|
|
319
|
-
this.updateUI("verifying", "Verifying...", true);
|
|
320
|
-
const result = await this.#capBase.solve();
|
|
321
|
-
return result;
|
|
322
|
-
} finally {
|
|
323
|
-
this.#solving = false;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
266
|
updateUI(state, text, disabled = false) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
267
|
+
this.#div.setAttribute("data-state", state);
|
|
268
|
+
this.#div.querySelector("p").innerText = text;
|
|
269
|
+
if (disabled) {
|
|
270
|
+
this.#div.setAttribute("disabled", "true");
|
|
271
|
+
} else {
|
|
272
|
+
this.#div.removeAttribute("disabled");
|
|
273
|
+
}
|
|
335
274
|
}
|
|
336
275
|
|
|
337
276
|
handleProgress(event) {
|
|
@@ -369,12 +308,34 @@
|
|
|
369
308
|
func.call(this, event);
|
|
370
309
|
}
|
|
371
310
|
|
|
311
|
+
error(message = "Unknown error") {
|
|
312
|
+
console.error("[Cap] Error:", message);
|
|
313
|
+
this.dispatchEvent("error", { isCap: true, message });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
dispatchEvent(eventName, detail = {}) {
|
|
317
|
+
const event = new CustomEvent(eventName, {
|
|
318
|
+
bubbles: true,
|
|
319
|
+
composed: true,
|
|
320
|
+
detail,
|
|
321
|
+
});
|
|
322
|
+
super.dispatchEvent(event);
|
|
323
|
+
}
|
|
324
|
+
|
|
372
325
|
reset() {
|
|
373
|
-
this.#
|
|
326
|
+
if (this.#resetTimer) {
|
|
327
|
+
clearTimeout(this.#resetTimer);
|
|
328
|
+
this.#resetTimer = null;
|
|
329
|
+
}
|
|
330
|
+
this.dispatchEvent("reset");
|
|
331
|
+
this.#token = null;
|
|
332
|
+
if (this.querySelector("input[name='cap-token']")) {
|
|
333
|
+
this.querySelector("input[name='cap-token']").value = "";
|
|
334
|
+
}
|
|
374
335
|
}
|
|
375
336
|
|
|
376
337
|
get token() {
|
|
377
|
-
return this.#
|
|
338
|
+
return this.#token;
|
|
378
339
|
}
|
|
379
340
|
|
|
380
341
|
disconnectedCallback() {
|
|
@@ -383,57 +344,63 @@
|
|
|
383
344
|
this.removeEventListener("error", this.boundHandleError);
|
|
384
345
|
this.removeEventListener("reset", this.boundHandleReset);
|
|
385
346
|
|
|
386
|
-
this
|
|
347
|
+
this.#eventHandlers.forEach((handler, eventName) => {
|
|
387
348
|
this.removeEventListener(eventName.slice(2), handler);
|
|
388
349
|
});
|
|
389
|
-
this
|
|
350
|
+
this.#eventHandlers.clear();
|
|
390
351
|
|
|
391
352
|
if (this.#shadow) {
|
|
392
353
|
this.#shadow.innerHTML = "";
|
|
393
354
|
}
|
|
394
355
|
|
|
395
|
-
this
|
|
396
|
-
this
|
|
356
|
+
this.reset();
|
|
357
|
+
this.cleanup();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
cleanup() {
|
|
361
|
+
if (this.#resetTimer) {
|
|
362
|
+
clearTimeout(this.#resetTimer);
|
|
363
|
+
this.#resetTimer = null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (this.#workerUrl) {
|
|
367
|
+
URL.revokeObjectURL(this.#workerUrl);
|
|
368
|
+
this.#workerUrl = "";
|
|
369
|
+
}
|
|
397
370
|
}
|
|
398
371
|
}
|
|
399
372
|
|
|
400
373
|
class Cap {
|
|
401
|
-
constructor(
|
|
402
|
-
let
|
|
403
|
-
let element = el || document.createElement("div");
|
|
374
|
+
constructor(config = {}, el) {
|
|
375
|
+
let widget = el || document.createElement("cap-widget");
|
|
404
376
|
|
|
405
|
-
if (!el) element.style.display = "none";
|
|
406
377
|
Object.entries(config).forEach(([a, b]) => {
|
|
407
|
-
|
|
378
|
+
widget.setAttribute(a, b);
|
|
408
379
|
});
|
|
409
380
|
|
|
410
381
|
if (config.apiEndpoint) {
|
|
411
|
-
|
|
382
|
+
console.log(config);
|
|
383
|
+
widget.setAttribute("data-cap-api-endpoint", config.apiEndpoint);
|
|
384
|
+
} else {
|
|
385
|
+
widget.remove();
|
|
386
|
+
throw new Error("Missing API endpoint");
|
|
412
387
|
}
|
|
413
388
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
);
|
|
418
|
-
capBase.initialize();
|
|
419
|
-
|
|
420
|
-
this.solve = async function () {
|
|
421
|
-
return await capBase.solve();
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
this.reset = function () {
|
|
425
|
-
capBase.reset();
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
this.addEventListener = function (event, callback) {
|
|
429
|
-
element.addEventListener(event, callback);
|
|
430
|
-
};
|
|
389
|
+
this.widget = widget;
|
|
390
|
+
this.solve = this.widget.solve.bind(this.widget);
|
|
391
|
+
this.reset = this.widget.reset.bind(this.widget);
|
|
392
|
+
this.addEventListener = this.widget.addEventListener.bind(this.widget);
|
|
431
393
|
|
|
432
394
|
Object.defineProperty(this, "token", {
|
|
433
|
-
get: () =>
|
|
395
|
+
get: () => widget.getToken(),
|
|
434
396
|
configurable: true,
|
|
435
397
|
enumerable: true,
|
|
436
398
|
});
|
|
399
|
+
|
|
400
|
+
if (!el) {
|
|
401
|
+
widget.style.display = "none";
|
|
402
|
+
document.documentElement.appendChild(widget);
|
|
403
|
+
}
|
|
437
404
|
}
|
|
438
405
|
}
|
|
439
406
|
|
|
@@ -493,7 +460,11 @@
|
|
|
493
460
|
|
|
494
461
|
setTimeout(async function () {
|
|
495
462
|
workerScript =
|
|
496
|
-
(await (
|
|
463
|
+
(await (
|
|
464
|
+
await fetch(
|
|
465
|
+
"https://cdn.jsdelivr.net/npm/@cap.js/widget/wasm-hashes.min.js"
|
|
466
|
+
)
|
|
467
|
+
).text()) +
|
|
497
468
|
workerFunct
|
|
498
469
|
.toString()
|
|
499
470
|
.replace(/^function\s*\([^\)]*\)\s*{|\}$/g, "")
|