@cap.js/widget 0.0.9 → 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.
Files changed (3) hide show
  1. package/cap.min.js +1 -1
  2. package/package.json +1 -1
  3. package/src/cap.js +179 -211
package/cap.min.js CHANGED
@@ -1 +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{#e="";#t=null;#r=null;#s=navigator.hardwareConcurrency||8;#a=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: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.#a=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.#a}}catch(e){throw this.error(e.message),e}}async solveChallenges({challenge:e,target:t}){const r=e.length;let s=0;const a=Array(this.#s).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.#s){const r=e.slice(t,Math.min(t+this.#s,e.length)),s=await Promise.all(r.map(((e,t)=>i(e,t))));n.push(...s)}}finally{a.forEach((e=>e.terminate()))}return n}reset(){this.#r&&(clearTimeout(this.#r),this.#r=null),this.dispatchEvent("reset"),this.#a=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.#s=!isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}getToken(){return this.#a}cleanup(){this.#r&&(clearTimeout(this.#r),this.#r=null),this.#e&&(URL.revokeObjectURL(this.#e),this.#e="")}}class s 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),s=this.eventHandlers.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.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&nbsp;</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 a{constructor(e,t={}){let s=new r,a=e||document.createElement("div");if(e||(a.style.display="none"),Object.entries(t).forEach((([e,t])=>{a.setAttribute(e,t)})),!t.apiEndpoint)throw a.remove(),new Error("Missing API endpoint");a.setAttribute("data-cap-api-endpoint",t.apiEndpoint),s.setElement(a),s.setWorkersCount(t.workers||navigator.hardwareConcurrency||8),s.initialize(),this.solve=async function(){return await s.solve()},this.reset=function(){s.reset()},this.addEventListener=function(e,t){a.addEventListener(e,t)},Object.defineProperty(this,"token",{get:()=>s.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 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()+n.toString().replace(/^function\s*\([^\)]*\)\s*{|\}$/g,"").trim()}),1),window.Cap=a,customElements.get("cap-widget")?console.warn("The cap-widget element has already been defined. Skipping re-defining it."):customElements.define("cap-widget",s),"object"==typeof exports&&"undefined"!=typeof module?module.exports=a:"function"==typeof define&&define.amd&&define([],(function(){return a})),"undefined"!=typeof exports&&(exports.default=a)}();
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&nbsp;</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.9",
3
+ "version": "0.0.10",
4
4
  "description": "Cap widget",
5
5
  "keywords": [
6
6
  "captcha",
package/src/cap.js CHANGED
@@ -19,12 +19,35 @@
19
19
  });
20
20
  };
21
21
 
22
- class CapBase {
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
- await until(() => !!this.#workerUrl);
49
- this.dispatchEvent("progress", { progress: 0 });
110
+ if (this.#solving) {
111
+ return;
112
+ }
50
113
 
51
114
  try {
52
- const apiEndpoint = this.#el.getAttribute("data-cap-api-endpoint");
53
- if (!apiEndpoint) throw new Error("Missing API endpoint");
115
+ this.#solving = true;
116
+ this.updateUI("verifying", "Verifying...", true);
54
117
 
55
- const { challenge, target, token } = await (
56
- await fetch(
57
- `${apiEndpoint}challenge`,
58
- {
59
- method: "POST",
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
- const resp = await (
70
- await fetch(
71
- `${apiEndpoint}redeem`,
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
- if (!resp.success) throw new Error("Invalid solution");
144
+ if (!resp.success) throw new Error("Invalid solution");
81
145
 
82
- this.dispatchEvent("progress", { progress: 100 });
83
- this.dispatchEvent("solve", { token: resp.token });
84
- this.#token = resp.token;
85
- if (this.#el.querySelector("input[name='cap-token']")) {
86
- this.#el.querySelector("input[name='cap-token']").value = resp.token;
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
- if (this.#resetTimer) clearTimeout(this.#resetTimer);
90
- const expiresIn = new Date(resp.expires).getTime() - Date.now();
91
- if (expiresIn > 0 && expiresIn < 24 * 60 * 60 * 1000) {
92
- this.#resetTimer = setTimeout(() => this.reset(), expiresIn); // 24h
93
- } else {
94
- this.error("Invalid expiration time");
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
- return { success: true, token: this.#token };
98
- } catch (err) {
99
- this.error(err.message);
100
- throw err;
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
- this.#div.setAttribute("data-state", state);
329
- this.#div.querySelector("p").innerText = text;
330
- if (disabled) {
331
- this.#div.setAttribute("disabled", "true");
332
- } else {
333
- this.#div.removeAttribute("disabled");
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.#capBase.reset();
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.#capBase.getToken();
338
+ return this.#token;
378
339
  }
379
340
 
380
341
  disconnectedCallback() {
@@ -383,60 +344,63 @@
383
344
  this.removeEventListener("error", this.boundHandleError);
384
345
  this.removeEventListener("reset", this.boundHandleReset);
385
346
 
386
- this.eventHandlers.forEach((handler, eventName) => {
347
+ this.#eventHandlers.forEach((handler, eventName) => {
387
348
  this.removeEventListener(eventName.slice(2), handler);
388
349
  });
389
- this.eventHandlers.clear();
350
+ this.#eventHandlers.clear();
390
351
 
391
352
  if (this.#shadow) {
392
353
  this.#shadow.innerHTML = "";
393
354
  }
394
355
 
395
- this.#capBase.reset();
396
- this.#capBase.cleanup();
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(el, config = {}) {
402
- let capBase = new CapBase();
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
- element.setAttribute(a, b);
378
+ widget.setAttribute(a, b);
408
379
  });
409
380
 
410
381
  if (config.apiEndpoint) {
411
- element.setAttribute("data-cap-api-endpoint", config.apiEndpoint);
382
+ console.log(config);
383
+ widget.setAttribute("data-cap-api-endpoint", config.apiEndpoint);
412
384
  } else {
413
- element.remove();
385
+ widget.remove();
414
386
  throw new Error("Missing API endpoint");
415
387
  }
416
388
 
417
- capBase.setElement(element);
418
- capBase.setWorkersCount(
419
- config.workers || navigator.hardwareConcurrency || 8
420
- );
421
- capBase.initialize();
422
-
423
- this.solve = async function () {
424
- return await capBase.solve();
425
- };
426
-
427
- this.reset = function () {
428
- capBase.reset();
429
- };
430
-
431
- this.addEventListener = function (event, callback) {
432
- element.addEventListener(event, callback);
433
- };
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);
434
393
 
435
394
  Object.defineProperty(this, "token", {
436
- get: () => capBase.getToken(),
395
+ get: () => widget.getToken(),
437
396
  configurable: true,
438
397
  enumerable: true,
439
398
  });
399
+
400
+ if (!el) {
401
+ widget.style.display = "none";
402
+ document.documentElement.appendChild(widget);
403
+ }
440
404
  }
441
405
  }
442
406
 
@@ -496,7 +460,11 @@
496
460
 
497
461
  setTimeout(async function () {
498
462
  workerScript =
499
- (await (await fetch("https://cdn.jsdelivr.net/npm/@cap.js/widget/wasm-hashes.min.js")).text()) +
463
+ (await (
464
+ await fetch(
465
+ "https://cdn.jsdelivr.net/npm/@cap.js/widget/wasm-hashes.min.js"
466
+ )
467
+ ).text()) +
500
468
  workerFunct
501
469
  .toString()
502
470
  .replace(/^function\s*\([^\)]*\)\s*{|\}$/g, "")