@cap.js/widget 0.1.50 → 0.1.52

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.
@@ -1 +1 @@
1
- (()=>{const t=t=>{const e=t.getAttribute("data-cap-floating");if(!e)return;const o=document.querySelector(e);if(!document.contains(o)&&!o.solve)throw new Error(`[cap floating] "${e}" doesn't exist or isn't a Cap widget`);o.style.display="none";let n=!1;t.addEventListener("click",e=>{if(n)return void(n=!1);e.stopImmediatePropagation(),e.preventDefault(),((t,e)=>{const o=parseInt(t.getAttribute("data-cap-floating-offset"))||8,n=t.getAttribute("data-cap-floating-position")||"top";Object.assign(e.style,{display:"block",position:"absolute",zIndex:"99999",opacity:"0",transform:"scale(0.98)",marginTop:"-4px",boxShadow:"rgba(0, 0, 0, 0.05) 0px 6px 24px 0px",borderRadius:"14px",transition:"opacity 0.15s, margin-top 0.2s, transform 0.2s"}),setTimeout(()=>{e.style.transform="scale(1)",e.style.opacity="1",e.style.marginTop="0"},5);const s=t.getBoundingClientRect(),a=(e.offsetParent??document.documentElement).getBoundingClientRect(),i=s.left+(s.width-e.offsetWidth)/2,r=Math.max(2,Math.min(i,window.innerWidth-e.offsetWidth));if(e.style.left=r-a.left+"px","top"===n){const t=s.top-e.offsetHeight-o,n=Math.max(0,t);e.style.top=n-a.top+"px"}else{const t=s.bottom+o,n=Math.min(t,window.innerHeight-e.offsetHeight);e.style.top=n-a.top+"px"}e.solve()})(t,o);const s=({detail:e})=>{o.removeEventListener("solve",s),t.setAttribute("data-cap-token",e.token),t.setAttribute("data-cap-progress","done"),setTimeout(()=>{n=!0,t.click()},500),(t=>{setTimeout(()=>{t.style.transform="scale(0.98)",t.style.opacity="0",t.style.marginTop="-4px"},500),setTimeout(()=>{t.style.display="none"},700)})(o)};o.addEventListener("solve",s)},!0)},e=e=>{t(e),e.querySelectorAll("[data-cap-floating]").forEach(t)};e(document.body),new MutationObserver(t=>t.forEach(t=>t.addedNodes.forEach(t=>{t.nodeType===Node.ELEMENT_NODE&&e(t)}))).observe(document.body,{childList:!0,subtree:!0})})();
1
+ (()=>{const t=t=>{const e=t.getAttribute("data-cap-floating");if(!e)return;const o=document.querySelector(e);if(!document.contains(o)&&!o.solve)throw new Error(`[cap floating] "${e}" doesn't exist or isn't a Cap widget`);o.style.display="none";let n=!1;t.addEventListener("click",e=>{if(n)return void(n=!1);e.stopImmediatePropagation(),e.preventDefault(),((t,e)=>{const o=parseInt(t.getAttribute("data-cap-floating-offset"),10)||8,n=t.getAttribute("data-cap-floating-position")||"top";Object.assign(e.style,{display:"block",position:"absolute",zIndex:"99999",opacity:"0",transform:"scale(0.98)",marginTop:"-4px",boxShadow:"rgba(0, 0, 0, 0.05) 0px 6px 24px 0px",borderRadius:"14px",transition:"opacity 0.15s, margin-top 0.2s, transform 0.2s"}),setTimeout(()=>{e.style.transform="scale(1)",e.style.opacity="1",e.style.marginTop="0"},5);const s=t.getBoundingClientRect(),i=(e.offsetParent??document.documentElement).getBoundingClientRect(),a=s.left+(s.width-e.offsetWidth)/2,r=Math.max(2,Math.min(a,window.innerWidth-e.offsetWidth));if(e.style.left=r-i.left+"px","top"===n){const t=s.top-e.offsetHeight-o,n=Math.max(0,t);e.style.top=n-i.top+"px"}else{const t=s.bottom+o,n=Math.min(t,window.innerHeight-e.offsetHeight);e.style.top=n-i.top+"px"}e.solve()})(t,o);const s=({detail:e})=>{o.removeEventListener("solve",s),t.setAttribute("data-cap-token",e.token),t.setAttribute("data-cap-progress","done"),setTimeout(()=>{n=!0,t.click()},500),(t=>{setTimeout(()=>{t.style.transform="scale(0.98)",t.style.opacity="0",t.style.marginTop="-4px"},500),setTimeout(()=>{t.style.display="none"},700)})(o)};o.addEventListener("solve",s)},!0)},e=e=>{t(e),e.querySelectorAll("[data-cap-floating]").forEach(t)};e(document.body),new MutationObserver(t=>{for(const o of t)for(const t of o.addedNodes)t.nodeType===Node.ELEMENT_NODE&&e(t)}).observe(document.body,{childList:!0,subtree:!0})})();
package/cap.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{const e="vibrate"in navigator&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches;if("undefined"==typeof window)return;const t=(e,t={})=>window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(e,t):fetch(e,t);function i(e,t){let i=function(e){let t=2166136261;for(let i=0;i<e.length;i++)t^=e.charCodeAt(i),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);return t>>>0}(e),s="";function r(){return i^=i<<13,i^=i>>>17,i^=i<<5,i>>>0}for(;s.length<t;){s+=r().toString(16).padStart(8,"0")}return s.substring(0,t)}let s=null;async function r(e){const t=(e=>{const t=atob(e),i=new Uint8Array(t.length);for(let e=0;e<t.length;e++)i[e]=t.charCodeAt(e);return i})(e),i=(new TextDecoder).decode(await async function(e){if("undefined"!=typeof DecompressionStream)try{const t=new DecompressionStream("deflate-raw"),i=t.writable.getWriter(),s=t.readable.getReader();i.write(e).then(()=>i.close()).catch(()=>{});const r=[];for(;;){const{done:e,value:t}=await s.read();if(e)break;r.push(t)}let a=0;for(const e of r)a+=e.length;const n=new Uint8Array(a);let o=0;for(const e of r)n.set(e,o),o+=e.length;return n}catch{}return s||(s=new Promise((e,t)=>{const i=window.CAP_PAKO_URL||"https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako_inflate.min.js",r=document.createElement("script");r.src=i;const a=window.CAP_SCRIPT_NONCE||window.CAP_CSS_NONCE;a&&r.setAttribute("nonce",a),r.onload=()=>{window.pako?.inflateRaw?e(window.pako):t(new Error("[cap] pako loaded but inflateRaw is missing"))},r.onerror=()=>{s=null,t(new Error(`[cap] failed to load pako fallback from ${i}`))},document.head.appendChild(r)})),(await s).inflateRaw(e)}(t));return new Promise(e=>{var t=setTimeout(()=>{a(),e({__timeout:!0})},2e4),s=document.createElement("iframe");s.setAttribute("sandbox","allow-scripts"),s.setAttribute("aria-hidden","true"),s.style.cssText="position:absolute;width:1px;height:1px;top:-9999px;left:-9999px;border:none;opacity:0;pointer-events:none;";var r=!1;function a(){r||(r=!0,clearTimeout(t),window.removeEventListener("message",n),s.parentNode&&s.parentNode.removeChild(s))}function n(t){var i=t.data;i&&"object"==typeof i&&("cap:instr"===i.type?(a(),i.blocked?e({__blocked:!0,blockReason:i.blockReason||"automated_browser"}):i.result?e(i.result):e({__timeout:!0})):"cap:error"===i.type&&(a(),e({__timeout:!0})))}window.addEventListener("message",n);const o=window.CAP_SCRIPT_NONCE||window.CAP_CSS_NONCE,l=o?' nonce="'+String(o).replace(/"/g,"&quot;")+'"':"";s.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script'+l+">"+i+"\n<\/script></body></html>",document.body.appendChild(s)})}let a=null;const n=()=>{if(a)return a;const e=window.CAP_CUSTOM_WASM_URL||"https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.7/browser/cap_wasm_bg.wasm";return a=fetch(e).then(e=>{if(!e.ok)throw new Error(`Failed to fetch wasm: ${e.status}`);return e.arrayBuffer()}).then(e=>WebAssembly.compile(e)).catch(e=>{throw a=null,e}),a};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&n().catch(()=>{});let o=null;function l(){return o||(o=URL.createObjectURL(new Blob(['(()=>{const e=async({salt:e,target:t})=>{let n=0;const r=new TextEncoder,o=4*t.length,s=Math.floor(o/8),l=o%8,a=t.length%2==0?t:t+"0",c=a.length/2,i=new Uint8Array(c);for(let e=0;e<c;e++)i[e]=parseInt(a.substring(2*e,2*e+2),16);const f=l>0?255<<8-l&255:0;for(;;)try{for(let t=0;t<5e4;t++){const t=e+n,o=r.encode(t),a=await crypto.subtle.digest("SHA-256",o),c=new Uint8Array(a);let g=!0;for(let e=0;e<s;e++)if(c[e]!==i[e]){g=!1;break}if(g&&l>0&&(c[s]&f)!==(i[s]&f)&&(g=!1),g)return void self.postMessage({nonce:n,found:!0});n++}}catch(e){return console.error("[cap worker]",e),void self.postMessage({found:!1,error:e.message})}};if("object"!=typeof WebAssembly||"function"!=typeof WebAssembly?.instantiate)return console.warn("[cap worker] wasm not supported, falling back to alternative solver. this will be significantly slower."),void(self.onmessage=async({data:{salt:t,target:n}})=>e({salt:t,target:n}));let t=null;self.onmessage=async({data:{salt:n,target:r,wasmModule:o}})=>{if(o instanceof WebAssembly.Module&&null===t){const s=(e=>{try{let n,r=0,o=null;const s=()=>(null!==o&&0!==o.byteLength||(o=new Uint8Array(n.memory.buffer)),o),l=new TextEncoder,a=(e,t,n)=>{if(void 0===n){const n=l.encode(e),o=t(n.length,1)>>>0;return s().subarray(o,o+n.length).set(n),r=n.length,o}let o=e.length,a=t(o,1)>>>0;const c=s();let i=0;for(;i<o;i++){const t=e.charCodeAt(i);if(t>127)break;c[a+i]=t}if(i!==o){0!==i&&(e=e.slice(i)),a=n(a,o,o=i+3*e.length,1)>>>0;const t=s().subarray(a+i,a+o),{written:r}=l.encodeInto(e,t);i+=r,a=n(a,o,i,1)>>>0}return r=i,a},c={wbg:{}};c.wbg.__wbindgen_init_externref_table=()=>{const e=n.__wbindgen_export_0,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)};const i=new WebAssembly.Instance(e,c);return n=i.exports,n.__wbindgen_start&&n.__wbindgen_start(),t=(e,t)=>{const o=a(e,n.__wbindgen_malloc,n.__wbindgen_realloc),s=r,l=a(t,n.__wbindgen_malloc,n.__wbindgen_realloc),c=r;return BigInt.asUintN(64,n.solve_pow(o,s,l,c))},!0}catch(e){return console.error("[cap worker] failed to init wasm from module:",e),!1}})(o);if(!s)return console.warn("[cap worker] wasm init failed, falling back to JS solver."),e({salt:n,target:r})}if(null===t)return console.warn("[cap worker] no wasm module provided, falling back to JS solver."),e({salt:n,target:r});try{const e=performance.now(),o=t(n,r),s=performance.now();self.postMessage({nonce:Number(o),found:!0,durationMs:(s-e).toFixed(2)})}catch(e){console.error("[cap worker]",e),self.postMessage({found:!1,error:e.message||String(e)})}},self.onerror=e=>{self.postMessage({found:!1,error:e})}})();'],{type:"application/javascript"})),o)}class c{constructor(e){this._size=e,this._workers=[],this._idle=[],this._queue=[],this._wasmModule=null,this._spawnFailures=0}setWasm(e){this._wasmModule=e}_spawn(){const e=l(),t=new Worker(e);return t._busy=!1,this._workers.push(t),this._idle.push(t),t}_replaceWorker(e){const t=this._workers.indexOf(e);-1!==t&&this._workers.splice(t,1);const i=this._idle.indexOf(e);-1!==i&&this._idle.splice(i,1);try{e.terminate()}catch{}return this._spawnFailures++,this._spawnFailures>3?(console.error("[cap] worker spawn failed repeatedly, not retrying"),null):this._spawn()}_ensureSize(e){for(;this._workers.length<e;)this._spawn()}run(e,t){return new Promise((i,s)=>{this._queue.push({salt:e,target:t,resolve:i,reject:s}),this._dispatch()})}_dispatch(){for(;this._idle.length>0&&this._queue.length>0;){const e=this._idle.shift(),{salt:t,target:i,resolve:s,reject:r}=this._queue.shift();let a=!1;const n=({data:t})=>{a||(a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o),this._spawnFailures=0,this._idle.push(e),t.found?s(t.nonce):r(new Error(t.error||"worker failed")),this._dispatch())},o=t=>{if(a)return;a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o);const i=this._replaceWorker(e);r(t),i&&this._dispatch()};e.addEventListener("message",n),e.addEventListener("error",o),this._wasmModule?e.postMessage({salt:t,target:i,wasmModule:this._wasmModule},[]):e.postMessage({salt:t,target:i})}}terminate(){for(const e of this._workers)try{e.terminate()}catch{}this._workers=[],this._idle=[],this._queue=[]}}class h extends HTMLElement{static formAssociated=!0;#e=null;#t=navigator.hardwareConcurrency||8;token=null;#i;#s;#r;#a;#n;#o;#l=!1;#c;#h;#d=null;#p=null;#u=null;#g=null;get#v(){return e&&!window.CAP_DISABLE_HAPTICS&&!this.hasAttribute("data-cap-disable-haptics")}#b(){return{state:"idle",challengeResp:null,challenges:null,results:[],completedCount:0,solvePromise:null,promoteFn:null,_listeners:[],pendingPromotion:null,token:null,tokenExpires:null,notify(){for(const e of this._listeners)e();this._listeners=[]},onSettled(e){"done"===this.state||"error"===this.state?e():this._listeners.push(e)}}}#m(){this.#d=this.#b(),this.#f()}#w(){this.#g&&(window.removeEventListener("mousemove",this.#g),window.removeEventListener("touchstart",this.#g),window.removeEventListener("keydown",this.#g),this.#g=null)}#f(){this.#w();const e=()=>{this.#w(),this.#k()};this.#g=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}#y(){return"function"==typeof this.checkVisibility?this.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0}):!!(this.offsetParent||this.getClientRects().length>0)}#k(){"idle"===this.#d.state&&this.#y()&&(this.#d.state="waiting",this.#p=setTimeout(()=>{this.#x()},2500))}async#x(){if("waiting"!==this.#d.state)return;this.#d.state="fetching";let e=this.getAttribute("data-cap-api-endpoint");if(!e&&window?.CAP_CUSTOM_FETCH&&(e="/"),e){e.endsWith("/")||(e+="/");try{const s=await t(`${e}challenge`,{method:"POST"});let r;try{r=await s.json()}catch{throw new Error("Failed to parse speculative challenge response")}if(r.error)throw new Error(r.error);r._apiEndpoint=e,this.#d.challengeResp=r;const{challenge:a,token:n}=r;let o=a;if(!Array.isArray(o)){let e=0;o=Array.from({length:a.c},()=>(e++,[i(`${n}${e}`,a.s),i(`${n}${e}d`,a.d)]))}this.#d.challenges=o,this.#d.state="solving",this.#d.solvePromise=this.#E(o)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),this.#d.state="error",this.#d.notify()}}else this.#d.state="idle"}async#E(e){l();let t=null;try{t=await n()}catch{}this.#u||(this.#u=new c(1),this.#u._spawn()),this.#u.setWasm(t);const i=e.length,s=new Array(i);let r=1,a=!1;this.#d.promoteFn=e=>{a||(a=!0,r=e,this.#u._size=e,this.#u._ensureSize(e))},null!==this.#d.pendingPromotion&&(this.#d.promoteFn(this.#d.pendingPromotion),this.#d.pendingPromotion=null);let o=0;for(;o<i;){const t=r,n=[],l=[];for(let s=0;s<t&&o<i;s++)l.push(o),n.push(e[o]),o++;this.#u._ensureSize(Math.max(r,t));const c=await Promise.all(n.map(e=>this.#u.run(e[0],e[1]).then(e=>(this.#d.completedCount++,e))));for(let e=0;e<l.length;e++)s[l[e]]=c[e];!a&&o<i&&await new Promise(e=>setTimeout(e,120))}return this.#d.results=s,this.#d.state="redeeming",this.#_(s),s}async#_(e){try{const i=this.#d.challengeResp,s=i._apiEndpoint;if(!s)throw new Error("[cap] speculative redeem: missing apiEndpoint");let a=null;if(i.instrumentation&&(a=await r(i.instrumentation),a?.__timeout||a?.__blocked))return this.#d.state="done",void this.#d.notify();const n=await t(`${s}redeem`,{method:"POST",body:JSON.stringify({token:i.token,solutions:e,...a&&{instr:a}}),headers:{"Content-Type":"application/json"}});let o;try{o=await n.json()}catch{throw new Error("Failed to parse speculative redeem response")}if(!o.success)throw new Error(o.error||"Speculative redeem failed");this.#d.token=o.token,this.#d.tokenExpires=new Date(o.expires).getTime(),this.#d.state="done",this.#d.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),this.#d.state="done",this.#d.notify()}}getI18nText(e,t){return this.getAttribute(`data-cap-i18n-${e}`)||t}static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","data-cap-worker-count","data-cap-i18n-initial-state","required"]}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);try{this.#h=this.attachInternals()}catch{}}#A(){this.#h?.setValidity&&(this.hasAttribute("required")&&!this.token?this.#h.setValidity({valueMissing:!0},this.getI18nText("required-label","Please verify you're human"),this.#s||this):this.#h.setValidity({}))}initialize(){l(),this.#d||(this.#d=this.#b()),this.#u||(this.#u=new c(1),this.#u._spawn())}attributeChangedCallback(e,t,i){if(e.startsWith("on")){const t=e.slice(2),s=this.#c.get(e);if(s&&this.removeEventListener(t,s),i){const i=t=>{const i=this.getAttribute(e);"function"==typeof window[i]&&window[i].call(this,t)};this.#c.set(e,i),this.addEventListener(t,i)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(i,10)),"data-cap-i18n-initial-state"===e&&this.#s&&this.#s?.querySelector(".label.active")&&this.animateLabel(this.getI18nText("initial-state","Verify you're human")),"required"===e&&this.#A()}async connectedCallback(){this.#o=this,this.shadowRoot?this.#i=this.shadowRoot:this.#i=this.attachShadow({mode:"open"}),this.#s||(this.#s=document.createElement("div")),this.createUI(),this.addEventListeners(),this.initialize(),this.#r.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const i=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#o.innerHTML=`<input type="hidden" name="${i}">`,this.#f(),this.#A(),this.addEventListener("invalid",this.#C)}#C=()=>{if(this.#s){try{this.scrollIntoView({behavior:"smooth",block:"center"})}catch{this.scrollIntoView()}this.#s.classList.remove("invalid"),this.#s.offsetWidth,this.#s.classList.add("invalid"),setTimeout(()=>this.#s?.classList.remove("invalid"),1500)}};async solve(){if(!this.#l)try{this.#l=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#r.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let e,s,a=this.getAttribute("data-cap-api-endpoint");if(!a&&window?.CAP_CUSTOM_FETCH)a="/";else if(!a)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");if(a.endsWith("/")||(a+="/"),"done"===this.#d.state&&this.#d.token&&this.#d.tokenExpires&&Date.now()<this.#d.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#d.token),this.dispatchEvent("solve",{token:this.#d.token}),this.token=this.#d.token;const t=this.#d.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),this.#m(),this.#l=!1,{success:!0,token:this.token}}if("done"===this.#d.state)e=this.#d.results,s=this.#d.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===this.#d.state||"redeeming"===this.#d.state||"fetching"===this.#d.state||"waiting"===this.#d.state){"waiting"===this.#d.state&&(this.#p&&(clearTimeout(this.#p),this.#p=null),this.#d.state="waiting",this.#x()),this.#d.pendingPromotion=this.#t,this.#d.promoteFn&&this.#d.promoteFn(this.#t);const t=setInterval(()=>{const e=this.#d.state;if("done"===e||"error"===e)return void clearInterval(t);const i=this.#d.challenges?this.#d.challenges.length:1,s=this.#d.completedCount,r="redeeming"===e?99:"fetching"===e||"waiting"===e?0:Math.min(98,Math.round(s/i*100));this.dispatchEvent("progress",{progress:r})},150);if(await new Promise(e=>this.#d.onSettled(e)),clearInterval(t),"done"!==this.#d.state)throw new Error("Speculative solve failed – please try again");if(this.#d.token&&this.#d.tokenExpires&&Date.now()<this.#d.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#d.token),this.dispatchEvent("solve",{token:this.#d.token}),this.token=this.#d.token;const t=this.#d.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),this.#m(),this.#l=!1,{success:!0,token:this.token}}e=this.#d.results,s=this.#d.challengeResp,this.dispatchEvent("progress",{progress:100})}else{const r=await t(`${a}challenge`,{method:"POST"});try{s=await r.json()}catch{throw new Error("Failed to parse challenge response from server")}if(s.error)throw new Error(s.error);const{challenge:n,token:o}=s;let l=n;if(!Array.isArray(l)){let e=0;l=Array.from({length:n.c},()=>(e++,[i(`${o}${e}`,n.s),i(`${o}${e}d`,n.d)]))}e=await this.solveChallenges(l)}const n=s.instrumentation?r(s.instrumentation):Promise.resolve(null),o=await n;if(o?.__timeout||o?.__blocked){this.updateUIBlocked(this.getI18nText("error-label","Error"),o?.__blocked),this.#r.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.removeEventListener("error",this.boundHandleError);const e=new CustomEvent("error",{bubbles:!0,composed:!0,detail:{isCap:!0,message:"Instrumentation failed"}});return super.dispatchEvent(e),this.addEventListener("error",this.boundHandleError),this.executeAttributeCode("onerror",e),console.error("[cap]","Instrumentation failed"),void(this.#l=!1)}const{token:l}=s,c=await t(`${a}redeem`,{method:"POST",body:JSON.stringify({token:l,solutions:e,...o&&{instr:o}}),headers:{"Content-Type":"application/json"}});let h;try{h=await c.json()}catch{throw new Error("Failed to parse server response")}if(this.dispatchEvent("progress",{progress:100}),!h.success)throw new Error(h.error||"Invalid solution");const d=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${d}']`)&&(this.querySelector(`input[name='${d}']`).value=h.token),this.dispatchEvent("solve",{token:h.token}),this.token=h.token,this.#m(),this.#e&&clearTimeout(this.#e);const p=new Date(h.expires).getTime()-Date.now();return p>0&&p<864e5?this.#e=setTimeout(()=>this.reset(),p):this.error("Invalid expiration time"),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),{success:!0,token:this.token}}catch(e){throw this.#r.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#l=!1}}async solveChallenges(e){const t=e.length;let i=0;let s=null;const r="object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate;if(r)try{s=await n()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}if(!r&&!this.#i.querySelector(".warning")){const e=document.createElement("div");e.className="warning",e.style.cssText="width:var(--cap-widget-width,230px);background:rgb(237,56,46);color:white;padding:4px 6px;padding-bottom:calc(var(--cap-border-radius,14px) + 5px);font-size:10px;box-sizing:border-box;font-family:system-ui;border-top-left-radius:8px;border-top-right-radius:8px;text-align:center;user-select:none;margin-bottom:-35.5px;opacity:0;transition:margin-bottom .3s,opacity .3s;",e.innerText=this.getI18nText("wasm-disabled","Enable WASM for significantly faster solving"),this.#i.insertBefore(e,this.#i.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}const a=new c(this.#t);a.setWasm(s),a._ensureSize(this.#t);const o=[];try{for(let s=0;s<e.length;s+=this.#t){const r=e.slice(s,Math.min(s+this.#t,e.length)),n=await Promise.all(r.map(([e,s])=>a.run(e,s).then(e=>{i++;const s=Math.min(99,Math.round((0+i)/t*100));return this.dispatchEvent("progress",{progress:s}),e})));o.push(...n)}}finally{a.terminate()}return o}setWorkersCount(e){const t=parseInt(e,10),i=Math.min(navigator.hardwareConcurrency||8,16);this.#t=!Number.isNaN(t)&&t>0&&t<=i?t:navigator.hardwareConcurrency||8}createUI(){this.#s.classList.add("captcha"),this.#s.setAttribute("role","group"),this.#s.setAttribute("aria-label",this.getI18nText("group-aria-label","Cap verification")),this.#r=document.createElement("div"),this.#r.className="captcha-trigger",this.#r.setAttribute("part","trigger"),this.#r.setAttribute("role","button"),this.#r.setAttribute("tabindex","0"),this.#r.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#r.setAttribute("aria-live","polite"),this.#r.setAttribute("disabled","true"),this.#r.innerHTML=`<div class="checkbox" part="checkbox" aria-hidden="true"><svg class="progress-ring" viewBox="0 0 32 32" aria-hidden="true"><circle class="progress-ring-bg" cx="16" cy="16" r="14"></circle><circle class="progress-ring-circle" cx="16" cy="16" r="14"></circle></svg></div><p part="label" class="label-wrapper"><span class="label active">${this.getI18nText("initial-state","Verify you're human")}</span></p>`,this.#s.appendChild(this.#r),this.#n=document.createElement("a"),this.#n.className="cap-troubleshoot-link",this.#n.setAttribute("part","troubleshoot"),this.#n.setAttribute("target","_blank"),this.#n.setAttribute("rel","noopener"),this.#n.hidden=!0,this.#s.appendChild(this.#n),this.#a=document.createElement("a"),this.#a.className="credits",this.#a.setAttribute("part","attribution"),this.#a.setAttribute("aria-label","Secured by Cap"),this.#a.setAttribute("href","https://trycap.dev"),this.#a.setAttribute("target","_blank"),this.#a.setAttribute("title","Secured by Cap: The self-hosted CAPTCHA for the modern web."),this.#a.textContent="Cap",this.#s.appendChild(this.#a),this.#i.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>:host{display:inline-block}@media (prefers-reduced-motion:reduce){.captcha,.captcha *{transition:none!important;animation:none!important}.label{filter:none!important;opacity:1!important;transition:none!important;transform:none!important}.label:not(.active),.label.exit{display:none!important}}.captcha,.captcha *{box-sizing:border-box}.captcha{width:var(--cap-widget-width,260px);height:var(--cap-widget-height,58px);background-color:var(--cap-background,#fdfdfd);border:1px solid var(--cap-border-color,#dddddd8f);border-radius:var(--cap-border-radius,14px);-webkit-user-select:none;user-select:none;cursor:pointer;-webkit-tap-highlight-color:#fff0;color:var(--cap-color,#212121);font-family:var(--cap-font,system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif);transition:filter .2s,transform .2s,height .2s;display:inline-block;position:relative;overflow:hidden}.captcha.has-troubleshoot{height:calc(var(--cap-widget-height,58px) + 26px)}.captcha.has-troubleshoot .captcha-trigger{inset-block-end:26px}.captcha:not([data-state]):active{transform:scale(.97)}.captcha:hover{filter:brightness(98%)}.captcha-trigger{padding:var(--cap-widget-padding,14px);align-items:center;gap:var(--cap-gap,15px);border-radius:inherit;transition:inset .2s;display:flex;position:absolute;inset:0}.captcha-trigger:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:-3px}.checkbox{width:var(--cap-checkbox-size,25px);height:var(--cap-checkbox-size,25px);border:var(--cap-checkbox-border,1px solid #aaaaaad1);border-radius:var(--cap-checkbox-border-radius,6px);background-color:var(--cap-checkbox-background,#fafafa91);margin-top:var(--cap-checkbox-margin,2px);margin-bottom:var(--cap-checkbox-margin,2px);flex-shrink:0;transition:opacity .2s}.captcha p{-webkit-user-select:none;user-select:none;margin:0;font-size:15px;font-weight:500}.label-wrapper{flex:1;align-items:center;min-width:0;height:2em;display:flex;position:relative;overflow:hidden}.label-wrapper:before,.label-wrapper:after{content:"";pointer-events:none;z-index:1;width:100%;height:.6em;position:absolute;inset-inline-start:0}.label-wrapper:before{background:linear-gradient(to bottom, var(--cap-background,#fdfdfd), transparent);top:0}.label-wrapper:after{background:linear-gradient(to top, var(--cap-background,#fdfdfd), transparent);bottom:0}.label{white-space:nowrap;opacity:0;filter:blur(2px);transition:transform .5s cubic-bezier(.25,1,.5,1),opacity .5s,filter .5s;position:absolute;transform:translateY(100%)}.label.active{opacity:1;filter:none;transform:translateY(0)}.label.exit{opacity:0;filter:blur(2px);transform:translateY(-100%)}.checkbox .progress-ring{width:100%;height:100%;display:none;transform:rotate(-90deg)}.checkbox .progress-ring-bg{fill:none;stroke:var(--cap-spinner-background-color,#eee);stroke-width:var(--cap-spinner-thickness,3)}.checkbox .progress-ring-circle{fill:none;stroke:var(--cap-spinner-color,#000);stroke-width:var(--cap-spinner-thickness,3);stroke-linecap:round;stroke-dasharray:87.96;stroke-dashoffset:87.96px;transition:stroke-dashoffset .3s}.captcha[data-state=verifying] .checkbox{background:0 0;border:none;border-radius:50%;justify-content:center;align-items:center;display:flex;transform:scale(1.1)}.captcha[data-state=verifying] .checkbox .progress-ring{display:block}.captcha[data-state=done] .checkbox{background-image:var(--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));background-size:cover;border:1px solid #0000}.captcha[data-state=done] .checkbox .progress-ring{display:none}.captcha[data-state=error] .checkbox{background-image:var(--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%2Fsvg%3E"));background-size:cover;border:1px solid #0000}.captcha[data-state=error] .checkbox .progress-ring{display:none}.captcha.invalid{border-color:var(--cap-invalid-border-color,#f55b50);box-shadow:0 0 0 3px var(--cap-invalid-ring-color,#f55b5033);animation:.5s cubic-bezier(.36,.07,.19,.97) cap-shake}@keyframes cap-shake{10%,90%{transform:translate(-1px)}20%,80%{transform:translate(2px)}30%,50%,70%{transform:translate(-4px)}40%,60%{transform:translate(4px)}}@media (prefers-reduced-motion:reduce){.captcha.invalid{animation:none!important}}.captcha-trigger[disabled]{cursor:not-allowed}.captcha[data-state=verifying] .captcha-trigger{cursor:progress}.captcha[data-state=done] .captcha-trigger{cursor:default}.credits{color:var(--cap-color,#212121);text-underline-offset:.18em;z-index:2;border-radius:4px;justify-content:flex-end;align-items:center;min-width:24px;min-height:24px;padding:4px 6px;text-decoration:underline;position:absolute;inset-block-end:4px;inset-inline-end:4px;opacity:.8!important;font-size:12px!important;display:inline-flex!important}.credits:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:2px;opacity:1!important}.cap-troubleshoot-link{min-width:24px;min-height:24px;color:var(--cap-troubleshoot-color,#0050a0);text-underline-offset:.15em;cursor:pointer;z-index:2;border-radius:4px;align-items:center;padding:4px 6px;font-size:13px;font-weight:500;text-decoration:underline;text-decoration-thickness:.08em;display:inline-flex;position:absolute;inset-block-end:4px;inset-inline-start:14px}.cap-troubleshoot-link[hidden]{display:none}.cap-troubleshoot-link:hover{opacity:.85}.cap-troubleshoot-link:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:2px}@media (forced-colors:active){.captcha,.checkbox{border-color:canvastext}:is(.captcha-trigger:focus-visible,.credits:focus-visible,.cap-troubleshoot-link:focus-visible){outline-color:highlight}.progress-ring-bg{stroke:graytext}.progress-ring-circle{stroke:canvastext}.captcha.invalid{box-shadow:none;border-color:mark}.credits{opacity:1!important}}</style>`,this.#i.appendChild(this.#s)}addEventListeners(){this.#r&&(this.#a.addEventListener("click",e=>{e.preventDefault(),window.open(`https://trycap.dev/?${new URLSearchParams({utm_source:"cap_widget",utm_medium:"referral",utm_campaign:"widget",utm_content:window.CAP_DISABLE_WIDGET_REF?"":location.hostname,ref:window.CAP_DISABLE_WIDGET_REF?"":location.href||"",sub:window.CAP_DISABLE_WIDGET_REF?"":document.referrer||""}).toString()}`,"_blank")}),this.#r.addEventListener("click",()=>{this.#r.hasAttribute("disabled")||this.solve()}),this.#r.addEventListener("mousedown",()=>{!this.#r.hasAttribute("disabled")&&this.#v&&navigator.vibrate(5)}),this.#r.addEventListener("keydown",e=>{e.target===this.#r&&("Enter"!==e.key&&" "!==e.key||this.#r.hasAttribute("disabled")||(e.preventDefault(),e.stopPropagation(),this.solve()))}),this.addEventListener("progress",this.boundHandleProgress),this.addEventListener("solve",this.boundHandleSolve),this.addEventListener("error",this.boundHandleError),this.addEventListener("reset",this.boundHandleReset))}animateLabel(e){if(!this.#r)return;const t=this.#r.querySelector(".label-wrapper");if(!t)return;if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches){const i=t.querySelector(".label.active");if(i)i.textContent=e;else{const i=document.createElement("span");i.className="label active",i.textContent=e,t.appendChild(i)}return}const i=t.querySelector(".label.active"),s=document.createElement("span");s.className="label",s.textContent=e,t.appendChild(s),s.offsetWidth,s.classList.add("active"),i&&(i.classList.remove("active"),i.classList.add("exit"),i.addEventListener("transitionend",()=>i.remove(),{once:!0}))}updateUI(e,t,i=!1){this.#s&&this.#r&&(this.#s.setAttribute("data-state",e),this.#s.classList.remove("has-troubleshoot"),this.animateLabel(t),this.#n&&(this.#n.hidden=!0),i?this.#r.setAttribute("disabled","true"):this.#r.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(this.#s&&this.#r&&(this.#s.setAttribute("data-state","error"),this.#r.removeAttribute("disabled"),this.animateLabel(e),this.#n))if(t){const e=this.getAttribute("data-cap-troubleshooting-url")||"https://trycap.dev/guide/troubleshooting/instrumentation.html";this.#n.setAttribute("href",e),this.#n.textContent=this.getI18nText("troubleshooting-label","Troubleshoot"),this.#n.hidden=!1,this.#s.classList.add("has-troubleshoot")}else this.#n.hidden=!0,this.#s.classList.remove("has-troubleshoot")}handleProgress(e){if(!this.#r)return;const t=this.#r.querySelector(".progress-ring-circle");if(t){const i=2*Math.PI*14,s=i-e.detail.progress/100*i;t.style.strokeDashoffset=s}const i=this.#r.querySelector(".label-wrapper");if(i){const e=i.querySelector(".label.active");e&&(e.textContent=`${this.getI18nText("verifying-label","Verifying...")}`)}this.executeAttributeCode("onprogress",e)}handleSolve(e){this.updateUI("done",this.getI18nText("solved-label","You're a human"),!0),this.executeAttributeCode("onsolve",e),this.#h?.setValidity?.({}),this.#s?.classList.remove("invalid")}handleError(e){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",e),this.#v&&navigator.vibrate([10,40,10])}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e),this.#A()}executeAttributeCode(e,t){const i=this.getAttribute(e);i&&(console.error("[cap] using `onxxx='…'` is strongly discouraged and will be deprecated soon. please use `addEventListener` callbacks instead."),new Function("event",i).call(this,t))}error(e="Unknown error"){console.error("[cap]",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const i=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(i)}reset(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.token=null,this.dispatchEvent("reset");const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value="")}get tokenValue(){return this.token}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.#i&&(this.#i.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.#w(),this.#p&&(clearTimeout(this.#p),this.#p=null),this.#u&&(this.#u.terminate(),this.#u=null),this.#d&&(this.#d.state="error",this.#d.notify(),this.#d=null)}}class d{constructor(e={},t){const i=t||document.createElement("cap-widget");if(Object.entries(e).forEach(([e,t])=>{i.setAttribute(e,t)}),!e.apiEndpoint&&!window?.CAP_CUSTOM_FETCH)throw i.remove(),new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");e.apiEndpoint&&i.setAttribute("data-cap-api-endpoint",e.apiEndpoint),t||i.hasAttribute("data-cap-disable-haptics")||i.setAttribute("data-cap-disable-haptics",""),this.widget=i,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:()=>i.token,configurable:!0,enumerable:!0}),t||(i.style.display="none",document.documentElement.appendChild(i))}}window.Cap=d,customElements.get("cap-widget")||window?.CAP_DONT_SKIP_REDEFINE?customElements.get("cap-widget")&&console.warn("[cap] the cap-widget element has already been defined, skipping re-defining it.\nto prevent this, set window.CAP_DONT_SKIP_REDEFINE to true"):customElements.define("cap-widget",h),"object"==typeof exports&&"undefined"!=typeof module?module.exports=d:"function"==typeof define&&define.amd&&define([],()=>d),"undefined"!=typeof exports&&(exports.default=d)})();
1
+ (()=>{const e="vibrate"in navigator&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches;if("undefined"==typeof window)return;const t=(e,t={})=>window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(e,t):fetch(e,t);function i(e,t){let i=function(e){let t=2166136261;for(let i=0;i<e.length;i++)t^=e.charCodeAt(i),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);return t>>>0}(e),s="";function r(){return i^=i<<13,i^=i>>>17,i^=i<<5,i>>>0}for(;s.length<t;){s+=r().toString(16).padStart(8,"0")}return s.substring(0,t)}let s=null;async function r(e){const t=(e=>{const t=atob(e),i=new Uint8Array(t.length);for(let e=0;e<t.length;e++)i[e]=t.charCodeAt(e);return i})(e),i=(new TextDecoder).decode(await async function(e){if("undefined"!=typeof DecompressionStream)try{const t=new DecompressionStream("deflate-raw"),i=t.writable.getWriter(),s=t.readable.getReader();i.write(e).then(()=>i.close()).catch(()=>{});const r=[];for(;;){const{done:e,value:t}=await s.read();if(e)break;r.push(t)}let a=0;for(const e of r)a+=e.length;const n=new Uint8Array(a);let o=0;for(const e of r)n.set(e,o),o+=e.length;return n}catch{}return s||(s=new Promise((e,t)=>{const i=window.CAP_PAKO_URL||"https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako_inflate.min.js",r=document.createElement("script");r.src=i;const a=window.CAP_SCRIPT_NONCE||window.CAP_CSS_NONCE;a&&r.setAttribute("nonce",a),r.onload=()=>{window.pako?.inflateRaw?e(window.pako):t(new Error("[cap] pako loaded but inflateRaw is missing"))},r.onerror=()=>{s=null,t(new Error(`[cap] failed to load pako fallback from ${i}`))},document.head.appendChild(r)})),(await s).inflateRaw(e)}(t));return new Promise(e=>{var t=setTimeout(()=>{a(),e({__timeout:!0})},2e4),s=document.createElement("iframe");s.setAttribute("sandbox","allow-scripts"),s.setAttribute("aria-hidden","true"),s.style.cssText="position:absolute;width:1px;height:1px;top:-9999px;left:-9999px;border:none;opacity:0;pointer-events:none;";var r=!1;function a(){r||(r=!0,clearTimeout(t),window.removeEventListener("message",n),s.parentNode&&s.parentNode.removeChild(s))}function n(t){if(s.contentWindow&&t.source===s.contentWindow){var i=t.data;i&&"object"==typeof i&&("cap:instr"===i.type?(a(),i.blocked?e({__blocked:!0,blockReason:i.blockReason||"automated_browser"}):i.result?e(i.result):e({__timeout:!0})):"cap:error"===i.type&&(a(),e({__timeout:!0})))}}window.addEventListener("message",n);const o=window.CAP_SCRIPT_NONCE||window.CAP_CSS_NONCE,l=o?` nonce="${String(o).replace(/"/g,"&quot;")}"`:"";s.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script'+l+">"+i+"\n<\/script></body></html>",document.body.appendChild(s)})}let a=null;const n=()=>{if(a)return a;const e=window.CAP_CUSTOM_WASM_URL||"https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.7/browser/cap_wasm_bg.wasm";return a=fetch(e).then(e=>{if(!e.ok)throw new Error(`Failed to fetch wasm: ${e.status}`);return e.arrayBuffer()}).then(e=>WebAssembly.compile(e)).catch(e=>{throw a=null,e}),a};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&n().catch(()=>{});let o=null;function l(){return o||(o=URL.createObjectURL(new Blob(['(()=>{const e=async({salt:e,target:t})=>{let n=0;const o=new TextEncoder,r=4*t.length,s=Math.floor(r/8),a=r%8,l=t.length%2==0?t:`${t}0`,i=l.length/2,c=new Uint8Array(i);for(let e=0;e<i;e++)c[e]=parseInt(l.substring(2*e,2*e+2),16);const f=a>0?255<<8-a&255:0;for(;;)try{for(let t=0;t<5e4;t++){const t=e+n,r=o.encode(t),l=await crypto.subtle.digest("SHA-256",r),i=new Uint8Array(l);let g=!0;for(let e=0;e<s;e++)if(i[e]!==c[e]){g=!1;break}if(g&&a>0&&(i[s]&f)!==(c[s]&f)&&(g=!1),g)return void self.postMessage({nonce:n,found:!0});n++}}catch(e){return console.error("[cap worker]",e),void self.postMessage({found:!1,error:e.message})}},t=e=>{const t=BigInt("0x"+e.N);let n=BigInt("0x"+e.x);const o=0|e.t,r=Math.max(64,Math.floor(o/50)),s=performance.now();for(let e=0;e<o;e+=r){const s=Math.min(o,e+r);for(let o=e;o<s;o++)n=n*n%t;s<o&&self.postMessage({progress:s/o})}self.postMessage({found:!0,y:n.toString(16),durationMs:(performance.now()-s).toFixed(2)})};if("object"!=typeof WebAssembly||"function"!=typeof WebAssembly?.instantiate)return console.warn("[cap worker] wasm not supported, falling back to alternative solver. this will be significantly slower."),void(self.onmessage=async({data:n})=>"rsw"===n?.kind?t(n):e({salt:n.salt,target:n.target}));let n=null;self.onmessage=async({data:o})=>{if("rsw"===o?.kind)return t(o);const{salt:r,target:s,wasmModule:a}=o;if(a instanceof WebAssembly.Module&&null===n){const t=(e=>{try{let t,o=0,r=null;const s=()=>(null!==r&&0!==r.byteLength||(r=new Uint8Array(t.memory.buffer)),r),a=new TextEncoder,l=(e,t,n)=>{if(void 0===n){const n=a.encode(e),r=t(n.length,1)>>>0;return s().subarray(r,r+n.length).set(n),o=n.length,r}let r=e.length,l=t(r,1)>>>0;const i=s();let c=0;for(;c<r;c++){const t=e.charCodeAt(c);if(t>127)break;i[l+c]=t}if(c!==r){0!==c&&(e=e.slice(c));const t=c+3*e.length;l=n(l,r,t,1)>>>0,r=t;const o=s().subarray(l+c,l+r),{written:i}=a.encodeInto(e,o);c+=i,l=n(l,r,c,1)>>>0}return o=c,l},i={wbg:{}};i.wbg.__wbindgen_init_externref_table=()=>{const e=t.__wbindgen_export_0,n=e.grow(4);e.set(0,void 0),e.set(n+0,void 0),e.set(n+1,null),e.set(n+2,!0),e.set(n+3,!1)};const c=new WebAssembly.Instance(e,i);return t=c.exports,t.__wbindgen_start&&t.__wbindgen_start(),n=(e,n)=>{const r=l(e,t.__wbindgen_malloc,t.__wbindgen_realloc),s=o,a=l(n,t.__wbindgen_malloc,t.__wbindgen_realloc),i=o;return BigInt.asUintN(64,t.solve_pow(r,s,a,i))},!0}catch(e){return console.error("[cap worker] failed to init wasm from module:",e),!1}})(a);if(!t)return console.warn("[cap worker] wasm init failed, falling back to JS solver."),e({salt:r,target:s})}if(null===n)return console.warn("[cap worker] no wasm module provided, falling back to JS solver."),e({salt:r,target:s});try{const e=performance.now(),t=n(r,s),o=performance.now();self.postMessage({nonce:Number(t),found:!0,durationMs:(o-e).toFixed(2)})}catch(e){console.error("[cap worker]",e),self.postMessage({found:!1,error:e.message||String(e)})}},self.onerror=e=>{self.postMessage({found:!1,error:e})}})();'],{type:"application/javascript"})),o)}class c{constructor(e){this._size=e,this._workers=[],this._idle=[],this._queue=[],this._wasmModule=null,this._spawnFailures=0}setWasm(e){this._wasmModule=e}_spawn(){const e=l(),t=new Worker(e);return t._busy=!1,this._workers.push(t),this._idle.push(t),t}_replaceWorker(e){const t=this._workers.indexOf(e);-1!==t&&this._workers.splice(t,1);const i=this._idle.indexOf(e);-1!==i&&this._idle.splice(i,1);try{e.terminate()}catch{}return this._spawnFailures++,this._spawnFailures>3?(console.error("[cap] worker spawn failed repeatedly, not retrying"),null):this._spawn()}_ensureSize(e){for(;this._workers.length<e;)this._spawn()}run(e,t){return new Promise((i,s)=>{this._queue.push({salt:e,target:t,resolve:i,reject:s}),this._dispatch()})}runMsg(e,t){return new Promise((i,s)=>{this._queue.push({msg:e,onProgress:t,resolve:i,reject:s}),this._dispatch()})}_dispatch(){for(;this._idle.length>0&&this._queue.length>0;){const e=this._idle.shift(),t=this._queue.shift(),{resolve:i,reject:s}=t,r=void 0!==t.msg;let a=!1;const n=({data:l})=>{if(!a)if(!l||"number"!=typeof l.progress||l.found)a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o),this._spawnFailures=0,this._idle.push(e),l.found?i(r?l:l.nonce):s(new Error(l.error||"worker failed")),this._dispatch();else if(t.onProgress)try{t.onProgress(l.progress)}catch{}},o=t=>{if(a)return;a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o);const i=this._replaceWorker(e);s(t),i&&this._dispatch()};e.addEventListener("message",n),e.addEventListener("error",o),r?e.postMessage(t.msg):this._wasmModule?e.postMessage({salt:t.salt,target:t.target,wasmModule:this._wasmModule},[]):e.postMessage({salt:t.salt,target:t.target})}}terminate(){for(const e of this._workers)try{e.terminate()}catch{}this._workers=[],this._idle=[],this._queue=[]}}class h extends HTMLElement{static formAssociated=!0;#e=null;#t=navigator.hardwareConcurrency||8;token=null;#i;#s;#r;#a;#n;#o;#l=!1;#c;#h;#d=null;#p=null;#u=null;#g=null;get#v(){return e&&!window.CAP_DISABLE_HAPTICS&&!this.hasAttribute("data-cap-disable-haptics")}#m(){return{state:"idle",challengeResp:null,challenges:null,results:[],completedCount:0,solvePromise:null,promoteFn:null,_listeners:[],pendingPromotion:null,token:null,tokenExpires:null,notify(){for(const e of this._listeners)e();this._listeners=[]},onSettled(e){"done"===this.state||"error"===this.state?e():this._listeners.push(e)}}}#b(){this.#d=this.#m(),this.#f()}#w(){this.#g&&(window.removeEventListener("mousemove",this.#g),window.removeEventListener("touchstart",this.#g),window.removeEventListener("keydown",this.#g),this.#g=null)}#f(){this.#w();const e=()=>{this.#w(),this.#y()};this.#g=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}#k(){return"function"==typeof this.checkVisibility?this.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0}):!!(this.offsetParent||this.getClientRects().length>0)}#y(){"idle"===this.#d.state&&this.#k()&&(this.#d.state="waiting",this.#p=setTimeout(()=>{this.#x()},2500))}async#x(){if("waiting"!==this.#d.state)return;this.#d.state="fetching";let e=this.getAttribute("data-cap-api-endpoint");if(!e&&window?.CAP_CUSTOM_FETCH&&(e="/"),e){e.endsWith("/")||(e+="/");try{const s=await t(`${e}challenge`,{method:"POST"});let r;try{r=await s.json()}catch{throw new Error("Failed to parse speculative challenge response")}if(r.error)throw new Error(r.error);if(r._apiEndpoint=e,this.#d.challengeResp=r,2===r.format&&Array.isArray(r.challenges))return this.#d.state="idle",void this.#d.notify();const{challenge:a,token:n}=r;let o=a;if(!Array.isArray(o)){let e=0;o=Array.from({length:a.c},()=>(e++,[i(`${n}${e}`,a.s),i(`${n}${e}d`,a.d)]))}this.#d.challenges=o,this.#d.state="solving",this.#d.solvePromise=this.#_(o)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),this.#d.state="error",this.#d.notify()}}else this.#d.state="idle"}async#_(e){l();let t=null;try{t=await n()}catch{}this.#u||(this.#u=new c(1),this.#u._spawn()),this.#u.setWasm(t);const i=e.length,s=new Array(i);let r=1,a=!1;this.#d.promoteFn=e=>{a||(a=!0,r=e,this.#u._size=e,this.#u._ensureSize(e))},null!==this.#d.pendingPromotion&&(this.#d.promoteFn(this.#d.pendingPromotion),this.#d.pendingPromotion=null);let o=0;for(;o<i;){const t=r,n=[],l=[];for(let s=0;s<t&&o<i;s++)l.push(o),n.push(e[o]),o++;this.#u._ensureSize(Math.max(r,t));const c=await Promise.all(n.map(e=>this.#u.run(e[0],e[1]).then(e=>(this.#d.completedCount++,e))));for(let e=0;e<l.length;e++)s[l[e]]=c[e];!a&&o<i&&await new Promise(e=>setTimeout(e,120))}return this.#d.results=s,this.#d.state="redeeming",this.#E(s),s}async#E(e){try{const i=this.#d.challengeResp,s=i._apiEndpoint;if(!s)throw new Error("[cap] speculative redeem: missing apiEndpoint");let a=null;if(i.instrumentation&&(a=await r(i.instrumentation),a?.__timeout||a?.__blocked))return this.#d.state="done",void this.#d.notify();const n=await t(`${s}redeem`,{method:"POST",body:JSON.stringify({token:i.token,solutions:e,...a&&{instr:a}}),headers:{"Content-Type":"application/json"}});let o;try{o=await n.json()}catch{throw new Error("Failed to parse speculative redeem response")}if(!o.success)throw new Error(o.error||"Speculative redeem failed");this.#d.token=o.token,this.#d.tokenExpires=new Date(o.expires).getTime(),this.#d.state="done",this.#d.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),this.#d.state="done",this.#d.notify()}}getI18nText(e,t){return this.getAttribute(`data-cap-i18n-${e}`)||t}static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","data-cap-worker-count","data-cap-i18n-initial-state","required"]}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);try{this.#h=this.attachInternals()}catch{}}#A(){this.#h?.setValidity&&(this.hasAttribute("required")&&!this.token?this.#h.setValidity({valueMissing:!0},this.getI18nText("required-label","Please verify you're human"),this.#s||this):this.#h.setValidity({}))}initialize(){l(),this.#d||(this.#d=this.#m()),this.#u||(this.#u=new c(1),this.#u._spawn())}attributeChangedCallback(e,t,i){if(e.startsWith("on")){const t=e.slice(2),s=this.#c.get(e);if(s&&this.removeEventListener(t,s),i){const i=t=>{const i=this.getAttribute(e);"function"==typeof window[i]&&window[i].call(this,t)};this.#c.set(e,i),this.addEventListener(t,i)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(i,10)),"data-cap-i18n-initial-state"===e&&this.#s&&this.#s?.querySelector(".label.active")&&this.animateLabel(this.getI18nText("initial-state","Verify you're human")),"required"===e&&this.#A()}async connectedCallback(){this.#o=this,this.shadowRoot?this.#i=this.shadowRoot:this.#i=this.attachShadow({mode:"open"}),this.#s||(this.#s=document.createElement("div")),this.createUI(),this.addEventListeners(),this.initialize(),this.#r.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const i=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#o.innerHTML=`<input type="hidden" name="${i}">`,this.#f(),this.#A(),this.addEventListener("invalid",this.#C)}#C=()=>{if(this.#s){try{this.scrollIntoView({behavior:"smooth",block:"center"})}catch{this.scrollIntoView()}this.#s.classList.remove("invalid"),this.#s.offsetWidth,this.#s.classList.add("invalid"),setTimeout(()=>this.#s?.classList.remove("invalid"),1500)}};async solve(){if(!this.#l)try{this.#l=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#r.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let e,s,a=this.getAttribute("data-cap-api-endpoint");if(!a&&window?.CAP_CUSTOM_FETCH)a="/";else if(!a)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");if(a.endsWith("/")||(a+="/"),"done"===this.#d.state&&this.#d.token&&this.#d.tokenExpires&&Date.now()<this.#d.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#d.token),this.dispatchEvent("solve",{token:this.#d.token}),this.token=this.#d.token;const t=this.#d.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),this.#b(),this.#l=!1,{success:!0,token:this.token}}if("done"===this.#d.state)e=this.#d.results,s=this.#d.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===this.#d.state||"redeeming"===this.#d.state||"fetching"===this.#d.state||"waiting"===this.#d.state){"waiting"===this.#d.state&&(this.#p&&(clearTimeout(this.#p),this.#p=null),this.#d.state="waiting",this.#x()),this.#d.pendingPromotion=this.#t,this.#d.promoteFn&&this.#d.promoteFn(this.#t);const t=setInterval(()=>{const e=this.#d.state;if("done"===e||"error"===e)return void clearInterval(t);const i=this.#d.challenges?this.#d.challenges.length:1,s=this.#d.completedCount,r="redeeming"===e?99:"fetching"===e||"waiting"===e?0:Math.min(98,Math.round(s/i*100));this.dispatchEvent("progress",{progress:r})},150);if(await new Promise(e=>this.#d.onSettled(e)),clearInterval(t),"idle"===this.#d.state&&2===this.#d.challengeResp?.format&&Array.isArray(this.#d.challengeResp.challenges))s=this.#d.challengeResp,this.#d.challengeResp=null,e=await this.solveChallengesV2(s.challenges);else{if("done"!==this.#d.state)throw new Error("Speculative solve failed – please try again");if(this.#d.token&&this.#d.tokenExpires&&Date.now()<this.#d.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#d.token),this.dispatchEvent("solve",{token:this.#d.token}),this.token=this.#d.token;const t=this.#d.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),this.#b(),this.#l=!1,{success:!0,token:this.token}}e=this.#d.results,s=this.#d.challengeResp,this.dispatchEvent("progress",{progress:100})}}else{const r=this.#d.challengeResp;if(2===r?.format&&Array.isArray(r.challenges))s=r,this.#d.challengeResp=null;else{const e=await t(`${a}challenge`,{method:"POST"});try{s=await e.json()}catch{throw new Error("Failed to parse challenge response from server")}if(s.error)throw new Error(s.error)}if(2===s.format&&Array.isArray(s.challenges))e=await this.solveChallengesV2(s.challenges);else{const{challenge:t,token:r}=s;let a=t;if(!Array.isArray(a)){let e=0;a=Array.from({length:t.c},()=>(e++,[i(`${r}${e}`,t.s),i(`${r}${e}d`,t.d)]))}e=await this.solveChallenges(a)}}const n=s.instrumentation?r(s.instrumentation):Promise.resolve(null),o=await n;if(o?.__timeout||o?.__blocked){t(`${a}redeem`,{method:"POST",body:JSON.stringify({token:s.token,solutions:e,...o.__blocked&&{instr_blocked:!0},...o.__timeout&&{instr_timeout:!0}}),headers:{"Content-Type":"application/json"}}).catch(()=>{}),this.updateUIBlocked(this.getI18nText("error-label","Error"),o?.__blocked),this.#r.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.removeEventListener("error",this.boundHandleError);const i=new CustomEvent("error",{bubbles:!0,composed:!0,detail:{isCap:!0,message:"Instrumentation failed"}});return super.dispatchEvent(i),this.addEventListener("error",this.boundHandleError),this.executeAttributeCode("onerror",i),console.error("[cap]","Instrumentation failed"),void(this.#l=!1)}const{token:l}=s,c=await t(`${a}redeem`,{method:"POST",body:JSON.stringify({token:l,solutions:e,...o&&{instr:o}}),headers:{"Content-Type":"application/json"}});let h;try{h=await c.json()}catch{throw new Error("Failed to parse server response")}if(this.dispatchEvent("progress",{progress:100}),!h.success)throw new Error(h.error||"Invalid solution");const d=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${d}']`)&&(this.querySelector(`input[name='${d}']`).value=h.token),this.dispatchEvent("solve",{token:h.token}),this.token=h.token,this.#b(),this.#e&&clearTimeout(this.#e);const p=new Date(h.expires).getTime()-Date.now();return p>0&&p<864e5?this.#e=setTimeout(()=>this.reset(),p):this.error("Invalid expiration time"),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),{success:!0,token:this.token}}catch(e){throw this.#r.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#l=!1}}async solveChallengesV2(e){const t=e.length;let i=0;const s=new Array(e.length);for(let t=0;t<e.length;t++){const i=e[t];if(!i||"object"!=typeof i||"sha256-pow"!==i.protocol&&"rsw"!==i.protocol&&"instrumentation"!==i.protocol)throw new Error(`[cap] unsupported format-2 protocol '${i?.protocol}'`)}let a=null;if("object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate)try{a=await n()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}const o=Math.max(1,Math.min(this.#t,e.length)),l=new c(o);l.setWasm(a),l._ensureSize(o);const h=(e,t)=>Promise.race([e,new Promise((e,i)=>setTimeout(()=>i(new Error(`[cap] ${t} timed out after 60000ms`)),6e4))]),d=new Array(e.length).fill(0),p=()=>{const e=d.reduce((e,t)=>e+t,0)+i,s=Math.min(99,Math.round(e/t*100));this.dispatchEvent("progress",{progress:s})};try{await Promise.all(e.map((e,t)=>"sha256-pow"===e.protocol?h(l.run(e.payload.salt,e.payload.target),`sha256-pow worker #${t}`).then(e=>{s[t]={nonce:Number(e)},d[t]=0,i++,p()}):"rsw"===e.protocol?h(l.runMsg({kind:"rsw",N:e.payload.N,x:e.payload.x,t:0|e.payload.t},e=>{d[t]=e,p()}),`rsw worker #${t}`).then(e=>{s[t]={y:e.y},d[t]=0,i++,p()}):r(e.payload.blob).then(e=>{s[t]=e?.__timeout?{timeout:!0}:e?.__blocked?{blocked:!0}:{instr:e},d[t]=0,i++,p()})))}finally{l.terminate()}return s}async solveChallenges(e){const t=e.length;let i=0;let s=null;const r="object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate;if(r)try{s=await n()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}if(!r&&!this.#i.querySelector(".warning")){const e=document.createElement("div");e.className="warning",e.style.cssText="width:var(--cap-widget-width,230px);background:rgb(237,56,46);color:white;padding:4px 6px;padding-bottom:calc(var(--cap-border-radius,14px) + 5px);font-size:10px;box-sizing:border-box;font-family:system-ui;border-top-left-radius:8px;border-top-right-radius:8px;text-align:center;user-select:none;margin-bottom:-35.5px;opacity:0;transition:margin-bottom .3s,opacity .3s;",e.innerText=this.getI18nText("wasm-disabled","Enable WASM for significantly faster solving"),this.#i.insertBefore(e,this.#i.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}const a=new c(this.#t);a.setWasm(s),a._ensureSize(this.#t);const o=[];try{for(let s=0;s<e.length;s+=this.#t){const r=e.slice(s,Math.min(s+this.#t,e.length)),n=await Promise.all(r.map(([e,s])=>a.run(e,s).then(e=>{i++;const s=Math.min(99,Math.round((0+i)/t*100));return this.dispatchEvent("progress",{progress:s}),e})));o.push(...n)}}finally{a.terminate()}return o}setWorkersCount(e){const t=parseInt(e,10),i=Math.min(navigator.hardwareConcurrency||8,16);this.#t=!Number.isNaN(t)&&t>0&&t<=i?t:navigator.hardwareConcurrency||8}createUI(){this.#s.classList.add("captcha"),this.#s.setAttribute("role","group"),this.#s.setAttribute("aria-label",this.getI18nText("group-aria-label","Cap verification")),this.#r=document.createElement("div"),this.#r.className="captcha-trigger",this.#r.setAttribute("part","trigger"),this.#r.setAttribute("role","button"),this.#r.setAttribute("tabindex","0"),this.#r.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#r.setAttribute("aria-live","polite"),this.#r.setAttribute("disabled","true"),this.#r.innerHTML=`<div class="checkbox" part="checkbox" aria-hidden="true"><svg class="progress-ring" viewBox="0 0 32 32" aria-hidden="true"><circle class="progress-ring-bg" cx="16" cy="16" r="14"></circle><circle class="progress-ring-circle" cx="16" cy="16" r="14"></circle></svg></div><p part="label" class="label-wrapper"><span class="label active">${this.getI18nText("initial-state","Verify you're human")}</span></p>`,this.#s.appendChild(this.#r),this.#n=document.createElement("a"),this.#n.className="cap-troubleshoot-link",this.#n.setAttribute("part","troubleshoot"),this.#n.setAttribute("target","_blank"),this.#n.setAttribute("rel","noopener"),this.#n.hidden=!0,this.#s.appendChild(this.#n),this.#a=document.createElement("a"),this.#a.className="credits",this.#a.setAttribute("part","attribution"),this.#a.setAttribute("aria-label","Secured by Cap"),this.#a.setAttribute("href","https://trycap.dev"),this.#a.setAttribute("target","_blank"),this.#a.setAttribute("title","Secured by Cap: The self-hosted CAPTCHA for the modern web."),this.#a.textContent="Cap",this.#s.appendChild(this.#a),this.#i.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>:host{display:inline-block}@media (prefers-reduced-motion:reduce){.captcha,.captcha *{transition:none!important;animation:none!important}.label{filter:none!important;opacity:1!important;transition:none!important;transform:none!important}.label:not(.active),.label.exit{display:none!important}}.captcha,.captcha *{box-sizing:border-box}.captcha{width:var(--cap-widget-width,260px);height:var(--cap-widget-height,58px);background-color:var(--cap-background,#fdfdfd);border:1px solid var(--cap-border-color,#dddddd8f);border-radius:var(--cap-border-radius,14px);-webkit-user-select:none;user-select:none;cursor:pointer;-webkit-tap-highlight-color:#fff0;color:var(--cap-color,#212121);font-family:var(--cap-font,system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif);transition:filter .2s,transform .2s,height .2s;display:inline-block;position:relative;overflow:hidden}.captcha.has-troubleshoot{height:calc(var(--cap-widget-height,58px) + 26px)}.captcha.has-troubleshoot .captcha-trigger{inset-block-end:26px}.captcha:not([data-state]):active{transform:scale(.97)}.captcha:hover{filter:brightness(98%)}.captcha-trigger{padding:var(--cap-widget-padding,14px);align-items:center;gap:var(--cap-gap,15px);border-radius:inherit;transition:inset .2s;display:flex;position:absolute;inset:0}.captcha-trigger:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:-3px}.checkbox{width:var(--cap-checkbox-size,25px);height:var(--cap-checkbox-size,25px);border:var(--cap-checkbox-border,1px solid #aaaaaad1);border-radius:var(--cap-checkbox-border-radius,6px);background-color:var(--cap-checkbox-background,#fafafa91);margin-top:var(--cap-checkbox-margin,2px);margin-bottom:var(--cap-checkbox-margin,2px);flex-shrink:0;transition:opacity .2s}.captcha p{-webkit-user-select:none;user-select:none;margin:0;font-size:15px;font-weight:500}.label-wrapper{flex:1;align-items:center;min-width:0;height:2em;display:flex;position:relative;overflow:hidden}.label-wrapper:before,.label-wrapper:after{content:"";pointer-events:none;z-index:1;width:100%;height:.6em;position:absolute;inset-inline-start:0}.label-wrapper:before{background:linear-gradient(to bottom, var(--cap-background,#fdfdfd), transparent);top:0}.label-wrapper:after{background:linear-gradient(to top, var(--cap-background,#fdfdfd), transparent);bottom:0}.label{white-space:nowrap;opacity:0;filter:blur(2px);transition:transform .5s cubic-bezier(.25,1,.5,1),opacity .5s,filter .5s;position:absolute;transform:translateY(100%)}.label.active{opacity:1;filter:none;transform:translateY(0)}.label.exit{opacity:0;filter:blur(2px);transform:translateY(-100%)}.checkbox .progress-ring{width:100%;height:100%;display:none;transform:rotate(-90deg)}.checkbox .progress-ring-bg{fill:none;stroke:var(--cap-spinner-background-color,#eee);stroke-width:var(--cap-spinner-thickness,3)}.checkbox .progress-ring-circle{fill:none;stroke:var(--cap-spinner-color,#000);stroke-width:var(--cap-spinner-thickness,3);stroke-linecap:round;stroke-dasharray:87.96;stroke-dashoffset:87.96px;transition:stroke-dashoffset .3s}.captcha[data-state=verifying] .checkbox{background:0 0;border:none;border-radius:50%;justify-content:center;align-items:center;display:flex;transform:scale(1.1)}.captcha[data-state=verifying] .checkbox .progress-ring{display:block}.captcha[data-state=done] .checkbox{background-image:var(--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));background-size:cover;border:1px solid #0000}.captcha[data-state=done] .checkbox .progress-ring{display:none}.captcha[data-state=error] .checkbox{background-image:var(--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%2Fsvg%3E"));background-size:cover;border:1px solid #0000}.captcha[data-state=error] .checkbox .progress-ring{display:none}.captcha.invalid{border-color:var(--cap-invalid-border-color,#f55b50);box-shadow:0 0 0 3px var(--cap-invalid-ring-color,#f55b5033);animation:.5s cubic-bezier(.36,.07,.19,.97) cap-shake}@keyframes cap-shake{10%,90%{transform:translate(-1px)}20%,80%{transform:translate(2px)}30%,50%,70%{transform:translate(-4px)}40%,60%{transform:translate(4px)}}@media (prefers-reduced-motion:reduce){.captcha.invalid{animation:none!important}}.captcha-trigger[disabled]{cursor:not-allowed}.captcha[data-state=verifying] .captcha-trigger{cursor:progress}.captcha[data-state=done] .captcha-trigger{cursor:default}.credits{color:var(--cap-color,#212121);text-underline-offset:.18em;z-index:2;border-radius:4px;justify-content:flex-end;align-items:center;min-width:24px;min-height:24px;padding:4px 6px;text-decoration:underline;text-decoration-thickness:.08em;position:absolute;inset-block-end:4px;inset-inline-end:4px;opacity:.8!important;font-size:12px!important;display:inline-flex!important}.captcha .credits[part=attribution]{visibility:visible!important;pointer-events:all!important;display:inline-flex!important}.credits:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:2px;opacity:1!important}.cap-troubleshoot-link{min-width:24px;min-height:24px;color:var(--cap-troubleshoot-color,#0050a0);text-underline-offset:.15em;cursor:pointer;z-index:2;border-radius:4px;align-items:center;padding:4px 6px;font-size:13px;font-weight:500;text-decoration:underline;text-decoration-thickness:.08em;display:inline-flex;position:absolute;inset-block-end:4px;inset-inline-start:14px}.cap-troubleshoot-link[hidden]{display:none}.cap-troubleshoot-link:hover{opacity:.85}.cap-troubleshoot-link:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:2px}@media (forced-colors:active){.captcha,.checkbox{border-color:canvastext}:is(.captcha-trigger:focus-visible,.credits:focus-visible,.cap-troubleshoot-link:focus-visible){outline-color:highlight}.progress-ring-bg{stroke:graytext}.progress-ring-circle{stroke:canvastext}.captcha.invalid{box-shadow:none;border-color:mark}.credits{opacity:1!important}}</style>`,this.#i.appendChild(this.#s)}addEventListeners(){this.#r&&(this.#a.addEventListener("click",e=>{e.preventDefault(),window.open(`https://trycap.dev/?${new URLSearchParams({utm_source:"cap_widget",utm_medium:"referral",utm_campaign:"widget",utm_content:window.CAP_DISABLE_WIDGET_REF?"":location.hostname,ref:window.CAP_DISABLE_WIDGET_REF?"":location.href||"",sub:window.CAP_DISABLE_WIDGET_REF?"":document.referrer||""}).toString()}`,"_blank")}),this.#r.addEventListener("click",()=>{this.#r.hasAttribute("disabled")||this.solve()}),this.#r.addEventListener("mousedown",()=>{!this.#r.hasAttribute("disabled")&&this.#v&&navigator.vibrate(5)}),this.#r.addEventListener("keydown",e=>{e.target===this.#r&&("Enter"!==e.key&&" "!==e.key||this.#r.hasAttribute("disabled")||(e.preventDefault(),e.stopPropagation(),this.solve()))}),this.addEventListener("progress",this.boundHandleProgress),this.addEventListener("solve",this.boundHandleSolve),this.addEventListener("error",this.boundHandleError),this.addEventListener("reset",this.boundHandleReset))}animateLabel(e){if(!this.#r)return;const t=this.#r.querySelector(".label-wrapper");if(!t)return;if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches){const i=t.querySelector(".label.active");if(i)i.textContent=e;else{const i=document.createElement("span");i.className="label active",i.textContent=e,t.appendChild(i)}return}const i=t.querySelector(".label.active"),s=document.createElement("span");s.className="label",s.textContent=e,t.appendChild(s),s.offsetWidth,s.classList.add("active"),i&&(i.classList.remove("active"),i.classList.add("exit"),i.addEventListener("transitionend",()=>i.remove(),{once:!0}))}updateUI(e,t,i=!1){this.#s&&this.#r&&(this.#s.setAttribute("data-state",e),this.#s.classList.remove("has-troubleshoot"),this.animateLabel(t),this.#n&&(this.#n.hidden=!0),i?this.#r.setAttribute("disabled","true"):this.#r.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(this.#s&&this.#r&&(this.#s.setAttribute("data-state","error"),this.#r.removeAttribute("disabled"),this.animateLabel(e),this.#n))if(t){const e=this.getAttribute("data-cap-troubleshooting-url")||"https://trycap.dev/guide/troubleshooting/instrumentation.html";this.#n.setAttribute("href",e),this.#n.textContent=this.getI18nText("troubleshooting-label","Troubleshoot"),this.#n.hidden=!1,this.#s.classList.add("has-troubleshoot")}else this.#n.hidden=!0,this.#s.classList.remove("has-troubleshoot")}handleProgress(e){if(!this.#r)return;const t=this.#r.querySelector(".progress-ring-circle");if(t){const i=2*Math.PI*14,s=i-e.detail.progress/100*i;t.style.strokeDashoffset=s}const i=this.#r.querySelector(".label-wrapper");if(i){const e=i.querySelector(".label.active");e&&(e.textContent=`${this.getI18nText("verifying-label","Verifying...")}`)}this.executeAttributeCode("onprogress",e)}handleSolve(e){this.updateUI("done",this.getI18nText("solved-label","You're a human"),!0),this.executeAttributeCode("onsolve",e),this.#h?.setValidity?.({}),this.#s?.classList.remove("invalid")}handleError(e){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",e),this.#v&&navigator.vibrate([10,40,10])}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e),this.#A()}executeAttributeCode(e,t){const i=this.getAttribute(e);i&&(console.error("[cap] using `onxxx='…'` is strongly discouraged and will be deprecated soon. please use `addEventListener` callbacks instead."),new Function("event",i).call(this,t))}error(e="Unknown error"){console.error("[cap]",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const i=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(i)}reset(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.token=null,this.dispatchEvent("reset");const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value="")}get tokenValue(){return this.token}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.#i&&(this.#i.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.#w(),this.#p&&(clearTimeout(this.#p),this.#p=null),this.#u&&(this.#u.terminate(),this.#u=null),this.#d&&(this.#d.state="error",this.#d.notify(),this.#d=null)}}class d{constructor(e={},t){const i=t||document.createElement("cap-widget");if(Object.entries(e).forEach(([e,t])=>{i.setAttribute(e,t)}),!e.apiEndpoint&&!window?.CAP_CUSTOM_FETCH)throw i.remove(),new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");e.apiEndpoint&&i.setAttribute("data-cap-api-endpoint",e.apiEndpoint),t||i.hasAttribute("data-cap-disable-haptics")||i.setAttribute("data-cap-disable-haptics",""),this.widget=i,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:()=>i.token,configurable:!0,enumerable:!0}),t||(i.style.display="none",document.documentElement.appendChild(i))}}window.Cap=d,customElements.get("cap-widget")||window?.CAP_DONT_SKIP_REDEFINE?customElements.get("cap-widget")&&console.warn("[cap] the cap-widget element has already been defined, skipping re-defining it.\nto prevent this, set window.CAP_DONT_SKIP_REDEFINE to true"):customElements.define("cap-widget",h),"object"==typeof exports&&"undefined"!=typeof module?module.exports=d:"function"==typeof define&&define.amd&&define([],()=>d),"undefined"!=typeof exports&&(exports.default=d)})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap.js/widget",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "description": "The self-hosted CAPTCHA for the modern web.",
5
5
  "keywords": [
6
6
  "algorithm",
@@ -1,7 +1,9 @@
1
1
  (() => {
2
2
  const showWidget = (element, capWidget) => {
3
- const offset = parseInt(element.getAttribute("data-cap-floating-offset")) || 8;
4
- const position = element.getAttribute("data-cap-floating-position") || "top";
3
+ const offset =
4
+ parseInt(element.getAttribute("data-cap-floating-offset"), 10) || 8;
5
+ const position =
6
+ element.getAttribute("data-cap-floating-position") || "top";
5
7
 
6
8
  Object.assign(capWidget.style, {
7
9
  display: "block",
@@ -24,10 +26,16 @@
24
26
  // `position: absolute` is resolved against the nearest positioned ancestor (capWidget.offsetParent),
25
27
  // but getBoundingClientRect() returns viewport-relative coords, convert to container-relative coords.
26
28
  const triggerRect = element.getBoundingClientRect();
27
- const containerRect = (capWidget.offsetParent ?? document.documentElement).getBoundingClientRect();
28
-
29
- const centeredLeft = triggerRect.left + (triggerRect.width - capWidget.offsetWidth) / 2;
30
- const clampedLeft = Math.max(2, Math.min(centeredLeft, window.innerWidth - capWidget.offsetWidth));
29
+ const containerRect = (
30
+ capWidget.offsetParent ?? document.documentElement
31
+ ).getBoundingClientRect();
32
+
33
+ const centeredLeft =
34
+ triggerRect.left + (triggerRect.width - capWidget.offsetWidth) / 2;
35
+ const clampedLeft = Math.max(
36
+ 2,
37
+ Math.min(centeredLeft, window.innerWidth - capWidget.offsetWidth),
38
+ );
31
39
  capWidget.style.left = `${clampedLeft - containerRect.left}px`;
32
40
 
33
41
  if (position === "top") {
@@ -36,7 +44,10 @@
36
44
  capWidget.style.top = `${clampedTop - containerRect.top}px`;
37
45
  } else {
38
46
  const idealTop = triggerRect.bottom + offset;
39
- const clampedTop = Math.min(idealTop, window.innerHeight - capWidget.offsetHeight);
47
+ const clampedTop = Math.min(
48
+ idealTop,
49
+ window.innerHeight - capWidget.offsetHeight,
50
+ );
40
51
  capWidget.style.top = `${clampedTop - containerRect.top}px`;
41
52
  }
42
53
 
@@ -61,7 +72,9 @@
61
72
 
62
73
  const capWidget = document.querySelector(capWidgetSelector);
63
74
  if (!document.contains(capWidget) && !capWidget.solve) {
64
- throw new Error(`[cap floating] "${capWidgetSelector}" doesn't exist or isn't a Cap widget`);
75
+ throw new Error(
76
+ `[cap floating] "${capWidgetSelector}" doesn't exist or isn't a Cap widget`,
77
+ );
65
78
  }
66
79
 
67
80
  capWidget.style.display = "none";
@@ -106,11 +119,11 @@
106
119
 
107
120
  init(document.body);
108
121
 
109
- new MutationObserver((mutations) =>
110
- mutations.forEach((mutation) =>
111
- mutation.addedNodes.forEach((node) => {
122
+ new MutationObserver((mutations) => {
123
+ for (const mutation of mutations) {
124
+ for (const node of mutation.addedNodes) {
112
125
  if (node.nodeType === Node.ELEMENT_NODE) init(node);
113
- }),
114
- ),
115
- ).observe(document.body, { childList: true, subtree: true });
126
+ }
127
+ }
128
+ }).observe(document.body, { childList: true, subtree: true });
116
129
  })();
package/src/cap.css CHANGED
@@ -138,12 +138,20 @@
138
138
 
139
139
  .label-wrapper::before {
140
140
  top: 0;
141
- background: linear-gradient(to bottom, var(--cap-background, #fdfdfd), transparent);
141
+ background: linear-gradient(
142
+ to bottom,
143
+ var(--cap-background, #fdfdfd),
144
+ transparent
145
+ );
142
146
  }
143
147
 
144
148
  .label-wrapper::after {
145
149
  bottom: 0;
146
- background: linear-gradient(to top, var(--cap-background, #fdfdfd), transparent);
150
+ background: linear-gradient(
151
+ to top,
152
+ var(--cap-background, #fdfdfd),
153
+ transparent
154
+ );
147
155
  }
148
156
 
149
157
  .label {
@@ -241,10 +249,23 @@
241
249
  }
242
250
 
243
251
  @keyframes cap-shake {
244
- 10%, 90% { transform: translateX(-1px); }
245
- 20%, 80% { transform: translateX(2px); }
246
- 30%, 50%, 70% { transform: translateX(-4px); }
247
- 40%, 60% { transform: translateX(4px); }
252
+ 10%,
253
+ 90% {
254
+ transform: translateX(-1px);
255
+ }
256
+ 20%,
257
+ 80% {
258
+ transform: translateX(2px);
259
+ }
260
+ 30%,
261
+ 50%,
262
+ 70% {
263
+ transform: translateX(-4px);
264
+ }
265
+ 40%,
266
+ 60% {
267
+ transform: translateX(4px);
268
+ }
248
269
  }
249
270
 
250
271
  @media (prefers-reduced-motion: reduce) {
@@ -272,6 +293,7 @@
272
293
  font-size: 12px !important;
273
294
  color: var(--cap-color, #212121);
274
295
  opacity: 0.8 !important;
296
+ text-decoration: underline;
275
297
  text-underline-offset: 0.18em;
276
298
  text-decoration-thickness: 0.08em;
277
299
  z-index: 2;
@@ -282,7 +304,12 @@
282
304
  align-items: center;
283
305
  justify-content: flex-end;
284
306
  border-radius: 4px;
285
- text-decoration: underline;
307
+ }
308
+
309
+ .captcha .credits[part="attribution"] {
310
+ display: inline-flex !important;
311
+ visibility: visible !important;
312
+ pointer-events: all !important;
286
313
  }
287
314
 
288
315
  .credits:focus-visible {
package/src/cap.js CHANGED
@@ -1,7 +1,8 @@
1
1
  (() => {
2
2
  const WASM_VERSION = "0.0.7";
3
3
  const _browserHasHaptics =
4
- "vibrate" in navigator && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
4
+ "vibrate" in navigator &&
5
+ !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
5
6
 
6
7
  if (typeof window === "undefined") {
7
8
  return;
@@ -20,7 +21,8 @@
20
21
  let hash = 2166136261;
21
22
  for (let i = 0; i < str.length; i++) {
22
23
  hash ^= str.charCodeAt(i);
23
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
24
+ hash +=
25
+ (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
24
26
  }
25
27
  return hash >>> 0;
26
28
  }
@@ -128,6 +130,7 @@
128
130
  }
129
131
 
130
132
  function handler(ev) {
133
+ if (!iframe.contentWindow || ev.source !== iframe.contentWindow) return;
131
134
  var d = ev.data;
132
135
  if (!d || typeof d !== "object") return;
133
136
  if (d.type === "cap:instr") {
@@ -152,7 +155,7 @@
152
155
 
153
156
  const scriptNonce = window.CAP_SCRIPT_NONCE || window.CAP_CSS_NONCE;
154
157
  const nonceAttr = scriptNonce
155
- ? ' nonce="' + String(scriptNonce).replace(/"/g, "&quot;") + '"'
158
+ ? ` nonce="${String(scriptNonce).replace(/"/g, "&quot;")}"`
156
159
  : "";
157
160
  iframe.srcdoc =
158
161
  '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script' +
@@ -189,7 +192,10 @@
189
192
  return wasmModulePromise;
190
193
  };
191
194
 
192
- if (typeof WebAssembly === "object" && typeof WebAssembly.compile === "function") {
195
+ if (
196
+ typeof WebAssembly === "object" &&
197
+ typeof WebAssembly.compile === "function"
198
+ ) {
193
199
  getWasmModule().catch(() => {});
194
200
  }
195
201
 
@@ -206,7 +212,7 @@
206
212
  if (_sharedWorkerUrl) return _sharedWorkerUrl;
207
213
 
208
214
  _sharedWorkerUrl = URL.createObjectURL(
209
- new Blob([`%%workerScript%%`], { type: "application/javascript" }),
215
+ new Blob([%%workerScript%%], { type: "application/javascript" }),
210
216
  );
211
217
  return _sharedWorkerUrl;
212
218
  }
@@ -264,15 +270,31 @@
264
270
  });
265
271
  }
266
272
 
273
+ runMsg(msg, onProgress) {
274
+ return new Promise((resolve, reject) => {
275
+ this._queue.push({ msg, onProgress, resolve, reject });
276
+ this._dispatch();
277
+ });
278
+ }
279
+
267
280
  _dispatch() {
268
281
  while (this._idle.length > 0 && this._queue.length > 0) {
269
282
  const worker = this._idle.shift();
270
- const { salt, target, resolve, reject } = this._queue.shift();
283
+ const task = this._queue.shift();
284
+ const { resolve, reject } = task;
285
+ const isMsg = task.msg !== undefined;
271
286
 
272
287
  let settled = false;
273
288
 
274
289
  const onMessage = ({ data }) => {
275
290
  if (settled) return;
291
+
292
+ if (data && typeof data.progress === "number" && !data.found) {
293
+ if (task.onProgress) {
294
+ try { task.onProgress(data.progress); } catch {}
295
+ }
296
+ return;
297
+ }
276
298
  settled = true;
277
299
  worker.removeEventListener("message", onMessage);
278
300
  worker.removeEventListener("error", onError);
@@ -281,7 +303,7 @@
281
303
  if (!data.found) {
282
304
  reject(new Error(data.error || "worker failed"));
283
305
  } else {
284
- resolve(data.nonce);
306
+ resolve(isMsg ? data : data.nonce);
285
307
  }
286
308
  this._dispatch();
287
309
  };
@@ -301,10 +323,15 @@
301
323
  worker.addEventListener("message", onMessage);
302
324
  worker.addEventListener("error", onError);
303
325
 
304
- if (this._wasmModule) {
305
- worker.postMessage({ salt, target, wasmModule: this._wasmModule }, []);
326
+ if (isMsg) {
327
+ worker.postMessage(task.msg);
328
+ } else if (this._wasmModule) {
329
+ worker.postMessage(
330
+ { salt: task.salt, target: task.target, wasmModule: this._wasmModule },
331
+ [],
332
+ );
306
333
  } else {
307
- worker.postMessage({ salt, target });
334
+ worker.postMessage({ salt: task.salt, target: task.target });
308
335
  }
309
336
  }
310
337
  }
@@ -406,7 +433,10 @@
406
433
 
407
434
  #isVisible() {
408
435
  if (typeof this.checkVisibility === "function") {
409
- return this.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true });
436
+ return this.checkVisibility({
437
+ checkOpacity: true,
438
+ checkVisibilityCSS: true,
439
+ });
410
440
  }
411
441
  // Fallback: offsetParent is null for display:none; also check the style directly
412
442
  return !!(this.offsetParent || this.getClientRects().length > 0);
@@ -436,7 +466,9 @@
436
466
  if (!apiEndpoint.endsWith("/")) apiEndpoint += "/";
437
467
 
438
468
  try {
439
- const raw = await capFetch(`${apiEndpoint}challenge`, { method: "POST" });
469
+ const raw = await capFetch(`${apiEndpoint}challenge`, {
470
+ method: "POST",
471
+ });
440
472
  let resp;
441
473
  try {
442
474
  resp = await raw.json();
@@ -448,13 +480,22 @@
448
480
  resp._apiEndpoint = apiEndpoint;
449
481
  this.#speculative.challengeResp = resp;
450
482
 
483
+ if (resp.format === 2 && Array.isArray(resp.challenges)) {
484
+ this.#speculative.state = "idle";
485
+ this.#speculative.notify();
486
+ return;
487
+ }
488
+
451
489
  const { challenge, token } = resp;
452
490
  let challenges = challenge;
453
491
  if (!Array.isArray(challenges)) {
454
492
  let i = 0;
455
493
  challenges = Array.from({ length: challenge.c }, () => {
456
494
  i++;
457
- return [prng(`${token}${i}`, challenge.s), prng(`${token}${i}d`, challenge.d)];
495
+ return [
496
+ prng(`${token}${i}`, challenge.s),
497
+ prng(`${token}${i}d`, challenge.d),
498
+ ];
458
499
  });
459
500
  }
460
501
  this.#speculative.challenges = challenges;
@@ -518,10 +559,12 @@
518
559
 
519
560
  const batchResults = await Promise.all(
520
561
  batch.map((challenge) =>
521
- this.#speculativePool.run(challenge[0], challenge[1]).then((nonce) => {
522
- this.#speculative.completedCount++;
523
- return nonce;
524
- }),
562
+ this.#speculativePool
563
+ .run(challenge[0], challenge[1])
564
+ .then((nonce) => {
565
+ this.#speculative.completedCount++;
566
+ return nonce;
567
+ }),
525
568
  ),
526
569
  );
527
570
 
@@ -530,7 +573,9 @@
530
573
  }
531
574
 
532
575
  if (!promoted && nextIndex < total) {
533
- await new Promise((resolve) => setTimeout(resolve, SPECULATIVE_YIELD_MS));
576
+ await new Promise((resolve) =>
577
+ setTimeout(resolve, SPECULATIVE_YIELD_MS),
578
+ );
534
579
  }
535
580
  }
536
581
 
@@ -544,11 +589,14 @@
544
589
  try {
545
590
  const challengeResp = this.#speculative.challengeResp;
546
591
  const apiEndpoint = challengeResp._apiEndpoint;
547
- if (!apiEndpoint) throw new Error("[cap] speculative redeem: missing apiEndpoint");
592
+ if (!apiEndpoint)
593
+ throw new Error("[cap] speculative redeem: missing apiEndpoint");
548
594
 
549
595
  let instrOut = null;
550
596
  if (challengeResp.instrumentation) {
551
- instrOut = await runInstrumentationChallenge(challengeResp.instrumentation);
597
+ instrOut = await runInstrumentationChallenge(
598
+ challengeResp.instrumentation,
599
+ );
552
600
  if (instrOut?.__timeout || instrOut?.__blocked) {
553
601
  this.#speculative.state = "done";
554
602
  this.#speculative.notify();
@@ -573,14 +621,18 @@
573
621
  throw new Error("Failed to parse speculative redeem response");
574
622
  }
575
623
 
576
- if (!resp.success) throw new Error(resp.error || "Speculative redeem failed");
624
+ if (!resp.success)
625
+ throw new Error(resp.error || "Speculative redeem failed");
577
626
 
578
627
  this.#speculative.token = resp.token;
579
628
  this.#speculative.tokenExpires = new Date(resp.expires).getTime();
580
629
  this.#speculative.state = "done";
581
630
  this.#speculative.notify();
582
631
  } catch (e) {
583
- console.warn("[cap] speculative redeem failed (will redo on click):", e);
632
+ console.warn(
633
+ "[cap] speculative redeem failed (will redo on click):",
634
+ e,
635
+ );
584
636
  this.#speculative.state = "done";
585
637
  this.#speculative.notify();
586
638
  }
@@ -674,7 +726,9 @@
674
726
  this.#div &&
675
727
  this.#div?.querySelector(".label.active")
676
728
  ) {
677
- this.animateLabel(this.getI18nText("initial-state", "Verify you're human"));
729
+ this.animateLabel(
730
+ this.getI18nText("initial-state", "Verify you're human"),
731
+ );
678
732
  }
679
733
 
680
734
  if (name === "required") {
@@ -700,7 +754,8 @@
700
754
  const workers = this.getAttribute("data-cap-worker-count");
701
755
  const parsedWorkers = workers ? parseInt(workers, 10) : null;
702
756
  this.setWorkersCount(parsedWorkers || navigator.hardwareConcurrency || 8);
703
- const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
757
+ const fieldName =
758
+ this.getAttribute("data-cap-hidden-field-name") || "cap-token";
704
759
  this.#host.innerHTML = `<input type="hidden" name="${fieldName}">`;
705
760
 
706
761
  this.#attachInteractionListeners();
@@ -729,10 +784,17 @@
729
784
 
730
785
  try {
731
786
  this.#solving = true;
732
- this.updateUI("verifying", this.getI18nText("verifying-label", "Verifying..."), true);
787
+ this.updateUI(
788
+ "verifying",
789
+ this.getI18nText("verifying-label", "Verifying..."),
790
+ true,
791
+ );
733
792
  this.#trigger.setAttribute(
734
793
  "aria-label",
735
- this.getI18nText("verifying-aria-label", "Verifying you're a human, please wait"),
794
+ this.getI18nText(
795
+ "verifying-aria-label",
796
+ "Verifying you're a human, please wait",
797
+ ),
736
798
  );
737
799
  this.dispatchEvent("progress", { progress: 0 });
738
800
 
@@ -758,9 +820,11 @@
758
820
  ) {
759
821
  this.dispatchEvent("progress", { progress: 100 });
760
822
 
761
- const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
823
+ const fieldName =
824
+ this.getAttribute("data-cap-hidden-field-name") || "cap-token";
762
825
  if (this.querySelector(`input[name='${fieldName}']`)) {
763
- this.querySelector(`input[name='${fieldName}']`).value = this.#speculative.token;
826
+ this.querySelector(`input[name='${fieldName}']`).value =
827
+ this.#speculative.token;
764
828
  }
765
829
  this.dispatchEvent("solve", { token: this.#speculative.token });
766
830
  this.token = this.#speculative.token;
@@ -813,7 +877,9 @@
813
877
  clearInterval(progressInterval);
814
878
  return;
815
879
  }
816
- const total = this.#speculative.challenges ? this.#speculative.challenges.length : 1;
880
+ const total = this.#speculative.challenges
881
+ ? this.#speculative.challenges.length
882
+ : 1;
817
883
  const done = this.#speculative.completedCount;
818
884
  const visual =
819
885
  st === "redeeming"
@@ -824,12 +890,23 @@
824
890
  this.dispatchEvent("progress", { progress: visual });
825
891
  }, 150);
826
892
 
827
- await new Promise((resolve) => this.#speculative.onSettled(resolve));
893
+ await new Promise((resolve) =>
894
+ this.#speculative.onSettled(resolve),
895
+ );
828
896
  clearInterval(progressInterval);
829
897
 
830
- if (this.#speculative.state !== "done") {
831
- throw new Error("Speculative solve failed – please try again");
832
- }
898
+ if (
899
+ this.#speculative.state === "idle" &&
900
+ this.#speculative.challengeResp?.format === 2 &&
901
+ Array.isArray(this.#speculative.challengeResp.challenges)
902
+ ) {
903
+ challengeResp = this.#speculative.challengeResp;
904
+ this.#speculative.challengeResp = null;
905
+ solutions = await this.solveChallengesV2(challengeResp.challenges);
906
+ } else {
907
+ if (this.#speculative.state !== "done") {
908
+ throw new Error("Speculative solve failed – please try again");
909
+ }
833
910
 
834
911
  if (
835
912
  this.#speculative.token &&
@@ -838,9 +915,11 @@
838
915
  ) {
839
916
  this.dispatchEvent("progress", { progress: 100 });
840
917
 
841
- const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
918
+ const fieldName =
919
+ this.getAttribute("data-cap-hidden-field-name") || "cap-token";
842
920
  if (this.querySelector(`input[name='${fieldName}']`)) {
843
- this.querySelector(`input[name='${fieldName}']`).value = this.#speculative.token;
921
+ this.querySelector(`input[name='${fieldName}']`).value =
922
+ this.#speculative.token;
844
923
  }
845
924
  this.dispatchEvent("solve", { token: this.#speculative.token });
846
925
  this.token = this.#speculative.token;
@@ -866,28 +945,44 @@
866
945
  solutions = this.#speculative.results;
867
946
  challengeResp = this.#speculative.challengeResp;
868
947
  this.dispatchEvent("progress", { progress: 100 });
869
- } else {
870
- const challengeRaw = await capFetch(`${apiEndpoint}challenge`, {
871
- method: "POST",
872
- });
873
- try {
874
- challengeResp = await challengeRaw.json();
875
- } catch {
876
- throw new Error("Failed to parse challenge response from server");
877
948
  }
878
- if (challengeResp.error) throw new Error(challengeResp.error);
879
-
880
- const { challenge, token } = challengeResp;
881
- let challenges = challenge;
882
- if (!Array.isArray(challenges)) {
883
- let i = 0;
884
- challenges = Array.from({ length: challenge.c }, () => {
885
- i++;
886
- return [prng(`${token}${i}`, challenge.s), prng(`${token}${i}d`, challenge.d)];
949
+ } else {
950
+ const cached = this.#speculative.challengeResp;
951
+ if (cached?.format === 2 && Array.isArray(cached.challenges)) {
952
+ challengeResp = cached;
953
+ this.#speculative.challengeResp = null;
954
+ } else {
955
+ const challengeRaw = await capFetch(`${apiEndpoint}challenge`, {
956
+ method: "POST",
887
957
  });
958
+ try {
959
+ challengeResp = await challengeRaw.json();
960
+ } catch {
961
+ throw new Error("Failed to parse challenge response from server");
962
+ }
963
+ if (challengeResp.error) throw new Error(challengeResp.error);
888
964
  }
889
965
 
890
- solutions = await this.solveChallenges(challenges);
966
+ if (
967
+ challengeResp.format === 2 &&
968
+ Array.isArray(challengeResp.challenges)
969
+ ) {
970
+ solutions = await this.solveChallengesV2(challengeResp.challenges);
971
+ } else {
972
+ const { challenge, token } = challengeResp;
973
+ let challenges = challenge;
974
+ if (!Array.isArray(challenges)) {
975
+ let i = 0;
976
+ challenges = Array.from({ length: challenge.c }, () => {
977
+ i++;
978
+ return [
979
+ prng(`${token}${i}`, challenge.s),
980
+ prng(`${token}${i}d`, challenge.d),
981
+ ];
982
+ });
983
+ }
984
+ solutions = await this.solveChallenges(challenges);
985
+ }
891
986
  }
892
987
 
893
988
  const instrPromise = challengeResp.instrumentation
@@ -897,10 +992,27 @@
897
992
  const instrOut = await instrPromise;
898
993
 
899
994
  if (instrOut?.__timeout || instrOut?.__blocked) {
900
- this.updateUIBlocked(this.getI18nText("error-label", "Error"), instrOut?.__blocked);
995
+ capFetch(`${apiEndpoint}redeem`, {
996
+ method: "POST",
997
+ body: JSON.stringify({
998
+ token: challengeResp.token,
999
+ solutions,
1000
+ ...(instrOut.__blocked && { instr_blocked: true }),
1001
+ ...(instrOut.__timeout && { instr_timeout: true }),
1002
+ }),
1003
+ headers: { "Content-Type": "application/json" },
1004
+ }).catch(() => {});
1005
+
1006
+ this.updateUIBlocked(
1007
+ this.getI18nText("error-label", "Error"),
1008
+ instrOut?.__blocked,
1009
+ );
901
1010
  this.#trigger.setAttribute(
902
1011
  "aria-label",
903
- this.getI18nText("error-aria-label", "An error occurred, please try again"),
1012
+ this.getI18nText(
1013
+ "error-aria-label",
1014
+ "An error occurred, please try again",
1015
+ ),
904
1016
  );
905
1017
  this.removeEventListener("error", this.boundHandleError);
906
1018
  const errEvent = new CustomEvent("error", {
@@ -938,7 +1050,8 @@
938
1050
  this.dispatchEvent("progress", { progress: 100 });
939
1051
  if (!resp.success) throw new Error(resp.error || "Invalid solution");
940
1052
 
941
- const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
1053
+ const fieldName =
1054
+ this.getAttribute("data-cap-hidden-field-name") || "cap-token";
942
1055
  if (this.querySelector(`input[name='${fieldName}']`)) {
943
1056
  this.querySelector(`input[name='${fieldName}']`).value = resp.token;
944
1057
  }
@@ -969,7 +1082,10 @@
969
1082
  } catch (err) {
970
1083
  this.#trigger.setAttribute(
971
1084
  "aria-label",
972
- this.getI18nText("error-aria-label", "An error occurred, please try again"),
1085
+ this.getI18nText(
1086
+ "error-aria-label",
1087
+ "An error occurred, please try again",
1088
+ ),
973
1089
  );
974
1090
  this.error(err.message);
975
1091
  throw err;
@@ -979,6 +1095,105 @@
979
1095
  }
980
1096
  }
981
1097
 
1098
+ async solveChallengesV2(challenges) {
1099
+ const total = challenges.length;
1100
+ let completed = 0;
1101
+
1102
+ const solutions = new Array(challenges.length);
1103
+ for (let i = 0; i < challenges.length; i++) {
1104
+ const ch = challenges[i];
1105
+ if (
1106
+ !ch ||
1107
+ typeof ch !== "object" ||
1108
+ !(
1109
+ ch.protocol === "sha256-pow" ||
1110
+ ch.protocol === "rsw" ||
1111
+ ch.protocol === "instrumentation"
1112
+ )
1113
+ ) {
1114
+ // Unknown protocol = older widget on a newer server. Fall back to
1115
+ // erroring out -- the host can detect this and serve format-1.
1116
+ throw new Error(`[cap] unsupported format-2 protocol '${ch?.protocol}'`);
1117
+ }
1118
+ }
1119
+
1120
+ let wasmModule = null;
1121
+ const wasmSupported =
1122
+ typeof WebAssembly === "object" &&
1123
+ typeof WebAssembly.instantiate === "function";
1124
+ if (wasmSupported) {
1125
+ try { wasmModule = await getWasmModule(); }
1126
+ catch (e) { console.warn("[cap] wasm unavailable, falling back to JS solver:", e); }
1127
+ }
1128
+
1129
+ const poolSize = Math.max(1, Math.min(this.#workersCount, challenges.length));
1130
+ const pool = new WorkerPool(poolSize);
1131
+ pool.setWasm(wasmModule);
1132
+ pool._ensureSize(poolSize);
1133
+
1134
+ const TASK_TIMEOUT_MS = 60_000;
1135
+ const withTimeout = (promise, label) => Promise.race([
1136
+ promise,
1137
+ new Promise((_, rej) => setTimeout(
1138
+ () => rej(new Error(`[cap] ${label} timed out after ${TASK_TIMEOUT_MS}ms`)),
1139
+ TASK_TIMEOUT_MS,
1140
+ )),
1141
+ ]);
1142
+
1143
+ const inFlight = new Array(challenges.length).fill(0);
1144
+ const emit = () => {
1145
+ const sum = inFlight.reduce((a, b) => a + b, 0) + completed;
1146
+ const visual = Math.min(99, Math.round((sum / total) * 100));
1147
+ this.dispatchEvent("progress", { progress: visual });
1148
+ };
1149
+
1150
+ try {
1151
+ await Promise.all(
1152
+ challenges.map((ch, idx) => {
1153
+ if (ch.protocol === "sha256-pow") {
1154
+ return withTimeout(
1155
+ pool.run(ch.payload.salt, ch.payload.target),
1156
+ `sha256-pow worker #${idx}`,
1157
+ ).then((nonce) => {
1158
+ solutions[idx] = { nonce: Number(nonce) };
1159
+ inFlight[idx] = 0;
1160
+ completed++;
1161
+ emit();
1162
+ });
1163
+ }
1164
+
1165
+ if (ch.protocol === "rsw") {
1166
+ return withTimeout(
1167
+ pool.runMsg(
1168
+ { kind: "rsw", N: ch.payload.N, x: ch.payload.x, t: ch.payload.t | 0 },
1169
+ (p) => { inFlight[idx] = p; emit(); },
1170
+ ),
1171
+ `rsw worker #${idx}`,
1172
+ ).then((data) => {
1173
+ solutions[idx] = { y: data.y };
1174
+ inFlight[idx] = 0;
1175
+ completed++;
1176
+ emit();
1177
+ });
1178
+ }
1179
+
1180
+ return runInstrumentationChallenge(ch.payload.blob).then((out) => {
1181
+ if (out?.__timeout) solutions[idx] = { timeout: true };
1182
+ else if (out?.__blocked) solutions[idx] = { blocked: true };
1183
+ else solutions[idx] = { instr: out };
1184
+ inFlight[idx] = 0;
1185
+ completed++;
1186
+ emit();
1187
+ });
1188
+ }),
1189
+ );
1190
+ } finally {
1191
+ pool.terminate();
1192
+ }
1193
+
1194
+ return solutions;
1195
+ }
1196
+
982
1197
  async solveChallenges(challenges) {
983
1198
  const total = challenges.length;
984
1199
  let completed = 0;
@@ -987,7 +1202,8 @@
987
1202
 
988
1203
  let wasmModule = null;
989
1204
  const wasmSupported =
990
- typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function";
1205
+ typeof WebAssembly === "object" &&
1206
+ typeof WebAssembly.instantiate === "function";
991
1207
 
992
1208
  if (wasmSupported) {
993
1209
  try {
@@ -1021,7 +1237,10 @@
1021
1237
  const results = [];
1022
1238
  try {
1023
1239
  for (let i = 0; i < challenges.length; i += this.#workersCount) {
1024
- const chunk = challenges.slice(i, Math.min(i + this.#workersCount, challenges.length));
1240
+ const chunk = challenges.slice(
1241
+ i,
1242
+ Math.min(i + this.#workersCount, challenges.length),
1243
+ );
1025
1244
  const chunkResults = await Promise.all(
1026
1245
  chunk.map(([salt, target]) =>
1027
1246
  pool.run(salt, target).then((nonce) => {
@@ -1048,7 +1267,9 @@
1048
1267
  const parsedWorkers = parseInt(workers, 10);
1049
1268
  const maxWorkers = Math.min(navigator.hardwareConcurrency || 8, 16);
1050
1269
  this.#workersCount =
1051
- !Number.isNaN(parsedWorkers) && parsedWorkers > 0 && parsedWorkers <= maxWorkers
1270
+ !Number.isNaN(parsedWorkers) &&
1271
+ parsedWorkers > 0 &&
1272
+ parsedWorkers <= maxWorkers
1052
1273
  ? parsedWorkers
1053
1274
  : navigator.hardwareConcurrency || 8;
1054
1275
  }
@@ -1117,7 +1338,9 @@
1117
1338
  utm_source: "cap_widget",
1118
1339
  utm_medium: "referral",
1119
1340
  utm_campaign: "widget",
1120
- utm_content: window.CAP_DISABLE_WIDGET_REF ? "" : location.hostname,
1341
+ utm_content: window.CAP_DISABLE_WIDGET_REF
1342
+ ? ""
1343
+ : location.hostname,
1121
1344
  ref: window.CAP_DISABLE_WIDGET_REF ? "" : location.href || "",
1122
1345
  sub: window.CAP_DISABLE_WIDGET_REF ? "" : document.referrer || "",
1123
1346
  },
@@ -1137,7 +1360,10 @@
1137
1360
 
1138
1361
  this.#trigger.addEventListener("keydown", (e) => {
1139
1362
  if (e.target !== this.#trigger) return;
1140
- if ((e.key === "Enter" || e.key === " ") && !this.#trigger.hasAttribute("disabled")) {
1363
+ if (
1364
+ (e.key === "Enter" || e.key === " ") &&
1365
+ !this.#trigger.hasAttribute("disabled")
1366
+ ) {
1141
1367
  e.preventDefault();
1142
1368
  e.stopPropagation();
1143
1369
  this.solve();
@@ -1234,11 +1460,14 @@
1234
1460
  handleProgress(event) {
1235
1461
  if (!this.#trigger) return;
1236
1462
 
1237
- const progressCircle = this.#trigger.querySelector(".progress-ring-circle");
1463
+ const progressCircle = this.#trigger.querySelector(
1464
+ ".progress-ring-circle",
1465
+ );
1238
1466
 
1239
1467
  if (progressCircle) {
1240
1468
  const circumference = 2 * Math.PI * 14;
1241
- const offset = circumference - (event.detail.progress / 100) * circumference;
1469
+ const offset =
1470
+ circumference - (event.detail.progress / 100) * circumference;
1242
1471
  progressCircle.style.strokeDashoffset = offset;
1243
1472
  }
1244
1473
 
@@ -1254,14 +1483,21 @@
1254
1483
  }
1255
1484
 
1256
1485
  handleSolve(event) {
1257
- this.updateUI("done", this.getI18nText("solved-label", "You're a human"), true);
1486
+ this.updateUI(
1487
+ "done",
1488
+ this.getI18nText("solved-label", "You're a human"),
1489
+ true,
1490
+ );
1258
1491
  this.executeAttributeCode("onsolve", event);
1259
1492
  this.#internals?.setValidity?.({});
1260
1493
  this.#div?.classList.remove("invalid");
1261
1494
  }
1262
1495
 
1263
1496
  handleError(event) {
1264
- this.updateUI("error", this.getI18nText("error-label", "Error. Try again."));
1497
+ this.updateUI(
1498
+ "error",
1499
+ this.getI18nText("error-label", "Error. Try again."),
1500
+ );
1265
1501
  this.executeAttributeCode("onerror", event);
1266
1502
 
1267
1503
  if (this.#hasHaptics) navigator.vibrate([10, 40, 10]);
@@ -1307,7 +1543,8 @@
1307
1543
  }
1308
1544
  this.token = null;
1309
1545
  this.dispatchEvent("reset");
1310
- const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
1546
+ const fieldName =
1547
+ this.getAttribute("data-cap-hidden-field-name") || "cap-token";
1311
1548
  if (this.querySelector(`input[name='${fieldName}']`)) {
1312
1549
  this.querySelector(`input[name='${fieldName}']`).value = "";
1313
1550
  }
package/src/worker.js CHANGED
@@ -8,7 +8,7 @@
8
8
  const fullBytes = Math.floor(targetBits / 8);
9
9
  const remainingBits = targetBits % 8;
10
10
 
11
- const paddedTarget = target.length % 2 === 0 ? target : target + "0";
11
+ const paddedTarget = target.length % 2 === 0 ? target : `${target}0`;
12
12
  const targetBytesLength = paddedTarget.length / 2;
13
13
  const targetBytes = new Uint8Array(targetBytesLength);
14
14
  for (let k = 0; k < targetBytesLength; k++) {
@@ -60,13 +60,32 @@
60
60
  }
61
61
  };
62
62
 
63
+ const solveRsw = (data) => {
64
+ const N = BigInt("0x" + data.N);
65
+ let y = BigInt("0x" + data.x);
66
+ const t = data.t | 0;
67
+ const chunk = Math.max(64, Math.floor(t / 50));
68
+ const t0 = performance.now();
69
+ for (let i = 0; i < t; i += chunk) {
70
+ const end = Math.min(t, i + chunk);
71
+ for (let j = i; j < end; j++) y = (y * y) % N;
72
+ if (end < t) self.postMessage({ progress: end / t });
73
+ }
74
+ self.postMessage({
75
+ found: true,
76
+ y: y.toString(16),
77
+ durationMs: (performance.now() - t0).toFixed(2),
78
+ });
79
+ };
80
+
63
81
  if (typeof WebAssembly !== "object" || typeof WebAssembly?.instantiate !== "function") {
64
82
  console.warn(
65
83
  "[cap worker] wasm not supported, falling back to alternative solver. this will be significantly slower.",
66
84
  );
67
85
 
68
- self.onmessage = async ({ data: { salt, target } }) => {
69
- return solveFallback({ salt, target });
86
+ self.onmessage = async ({ data }) => {
87
+ if (data?.kind === "rsw") return solveRsw(data);
88
+ return solveFallback({ salt: data.salt, target: data.target });
70
89
  };
71
90
 
72
91
  return;
@@ -113,7 +132,9 @@
113
132
 
114
133
  if (offset !== len) {
115
134
  if (offset !== 0) str = str.slice(offset);
116
- ptr = realloc(ptr, len, (len = offset + str.length * 3), 1) >>> 0;
135
+ const newLen = offset + str.length * 3;
136
+ ptr = realloc(ptr, len, newLen, 1) >>> 0;
137
+ len = newLen;
117
138
  const subarray = getMemory().subarray(ptr + offset, ptr + len);
118
139
  const { written } = encoder.encodeInto(str, subarray);
119
140
  offset += written;
@@ -155,7 +176,9 @@
155
176
  }
156
177
  };
157
178
 
158
- self.onmessage = async ({ data: { salt, target, wasmModule } }) => {
179
+ self.onmessage = async ({ data }) => {
180
+ if (data?.kind === "rsw") return solveRsw(data);
181
+ const { salt, target, wasmModule } = data;
159
182
  if (wasmModule instanceof WebAssembly.Module && solve_pow_function === null) {
160
183
  const ok = initFromModule(wasmModule);
161
184
  if (!ok) {