@cap.js/widget 0.1.40 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cap.d.ts +1 -0
- package/cap.min.js +1 -1
- package/package.json +1 -1
- package/src/cap.js +324 -307
package/cap.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ interface CapConfig {
|
|
|
56
56
|
"data-cap-i18n-verified-aria-label"?: string;
|
|
57
57
|
"data-cap-i18n-error-aria-label"?: string;
|
|
58
58
|
"data-cap-i18n-wasm-disabled"?: string;
|
|
59
|
+
"data-cap-troubleshooting-url"?: string;
|
|
59
60
|
onsolve?: string;
|
|
60
61
|
onprogress?: string;
|
|
61
62
|
onreset?: string;
|
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 r(e,t){let r=function(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);return t>>>0}(e),n="";function s(){return r^=r<<13,r^=r>>>17,r^=r<<5,r>>>0}for(;n.length<t;){n+=s().toString(16).padStart(8,"0")}return n.substring(0,t)}async function n(e){var t=(e=>{const t=atob(e),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r})(e);const r=await new Promise((e,r)=>{try{var n=new DecompressionStream("deflate-raw"),s=n.writable.getWriter(),i=n.readable.getReader(),o=[];i.read().then(function t(n){if(n.done){for(var s=0,a=0,l=0;l<o.length;l++)s+=o[l].length;var c=new Uint8Array(s);for(l=0;l<o.length;l++)c.set(o[l],a),a+=o[l].length;e((new TextDecoder).decode(c))}else o.push(n.value),i.read().then(t).catch(r)}).catch(r),s.write(t).then(()=>{s.close()}).catch(r)}catch(e){r(e)}});return new Promise(e=>{var t=setTimeout(()=>{i(),e({__timeout:!0})},2e4),n=document.createElement("iframe");n.setAttribute("sandbox","allow-scripts"),n.setAttribute("aria-hidden","true"),n.style.cssText="position:absolute;width:1px;height:1px;top:-9999px;left:-9999px;border:none;opacity:0;pointer-events:none;";var s=!1;function i(){s||(s=!0,clearTimeout(t),window.removeEventListener("message",o),n.parentNode&&n.parentNode.removeChild(n))}function o(t){var r=t.data;r&&"object"==typeof r&&("cap:instr"===r.type?(i(),r.blocked?e({__blocked:!0,blockReason:r.blockReason||"automated_browser"}):r.result?e(r.result):e({__timeout:!0})):"cap:error"===r.type&&(i(),e({__timeout:!0})))}window.addEventListener("message",o),n.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script>'+r+"\n<\/script></body></html>",document.body.appendChild(n)})}let s=null;const i=()=>{if(s)return s;const e=window.CAP_CUSTOM_WASM_URL||"https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.6/browser/cap_wasm_bg.wasm";return s=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 s=null,e}),s};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&i().catch(()=>{});const o={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)}};function a(){o.state="idle",o.challengeResp=null,o.challenges=null,o.results=[],o.completedCount=0,o.solvePromise=null,o.promoteFn=null,o.pendingPromotion=null,o._listeners=[],o.token=null,o.tokenExpires=null,h()}let l=null;let c=null;function d(){c&&(window.removeEventListener("mousemove",c),window.removeEventListener("touchstart",c),window.removeEventListener("keydown",c),c=null)}function h(){d();const e=()=>{d(),"idle"===o.state&&(o.state="waiting",l=setTimeout(()=>{p()},2500))};c=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}async function p(){if("waiting"!==o.state)return;o.state="fetching";const e=document.querySelector("cap-widget");if(!e)return void(o.state="idle");let s=e.getAttribute("data-cap-api-endpoint");if(!s&&window?.CAP_CUSTOM_FETCH&&(s="/"),s){s.endsWith("/")||(s+="/");try{const e=await t(`${s}challenge`,{method:"POST"});let a;try{a=await e.json()}catch{throw new Error("Failed to parse speculative challenge response")}if(a.error)throw new Error(a.error);a._apiEndpoint=s,o.challengeResp=a;const{challenge:l,token:c}=a;let d=l;if(!Array.isArray(d)){let e=0;d=Array.from({length:l.c},()=>(e++,[r(`${c}${e}`,l.s),r(`${c}${e}d`,l.d)]))}o.challenges=d,o.state="solving",o.solvePromise=async function(e){g();let r=null;try{r=await i()}catch{}v.setWasm(r);const s=e.length,a=new Array(s);let l=1,c=!1;o.promoteFn=e=>{c||(c=!0,l=e,v._size=e,v._ensureSize(e))},null!==o.pendingPromotion&&(o.promoteFn(o.pendingPromotion),o.pendingPromotion=null);let d=0;for(;d<s;){const t=l,r=[],n=[];for(let i=0;i<t&&d<s;i++)n.push(d),r.push(e[d]),d++;v._ensureSize(Math.max(l,t));const i=await Promise.all(r.map(e=>v.run(e[0],e[1]).then(e=>(o.completedCount++,e))));for(let e=0;e<n.length;e++)a[n[e]]=i[e];!c&&d<s&&await new Promise(e=>setTimeout(e,120))}return o.results=a,o.state="redeeming",async function(e){try{const r=o.challengeResp,s=r._apiEndpoint;if(!s)throw new Error("[cap] speculative redeem: missing apiEndpoint");let i=null;if(r.instrumentation&&(i=await n(r.instrumentation),i?.__timeout||i?.__blocked))return o.state="done",void o.notify();const a=await t(`${s}redeem`,{method:"POST",body:JSON.stringify({token:r.token,solutions:e,...i&&{instr:i}}),headers:{"Content-Type":"application/json"}});let l;try{l=await a.json()}catch{throw new Error("Failed to parse speculative redeem response")}if(!l.success)throw new Error(l.error||"Speculative redeem failed");o.token=l.token,o.tokenExpires=new Date(l.expires).getTime(),o.state="done",o.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),o.state="done",o.notify()}}(a),a}(d)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),o.state="error",o.notify()}}else o.state="idle"}h();let u=null;function g(){return u||(u=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"})),u)}class m{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=g(),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 r=this._idle.indexOf(e);-1!==r&&this._idle.splice(r,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((r,n)=>{this._queue.push({salt:e,target:t,resolve:r,reject:n}),this._dispatch()})}_dispatch(){for(;this._idle.length>0&&this._queue.length>0;){const e=this._idle.shift(),{salt:t,target:r,resolve:n,reject:s}=this._queue.shift();let i=!1;const o=({data:t})=>{i||(i=!0,e.removeEventListener("message",o),e.removeEventListener("error",a),this._spawnFailures=0,this._idle.push(e),t.found?n(t.nonce):s(new Error(t.error||"worker failed")),this._dispatch())},a=t=>{if(i)return;i=!0,e.removeEventListener("message",o),e.removeEventListener("error",a);const r=this._replaceWorker(e);s(t),r&&this._dispatch()};e.addEventListener("message",o),e.addEventListener("error",a),this._wasmModule?e.postMessage({salt:t,target:r,wasmModule:this._wasmModule},[]):e.postMessage({salt:t,target:r})}}terminate(){for(const e of this._workers)try{e.terminate()}catch{}this._workers=[],this._idle=[],this._queue=[]}}const v=new m(1);v._spawn();class b extends HTMLElement{#e=null;#t=navigator.hardwareConcurrency||8;token=null;#r;#n;#s;#i=!1;#o;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"]}constructor(){super(),this.#o&&this.#o.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#o=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)}initialize(){g()}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),n=this.#o.get(e);if(n&&this.removeEventListener(t,n),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.#o.set(e,r),this.addEventListener(t,r)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(r,10)),"data-cap-i18n-initial-state"===e&&this.#n&&this.#n?.querySelector(".label.active")&&this.animateLabel(this.getI18nText("initial-state","Verify you're human"))}async connectedCallback(){this.#s=this,this.#r=this.attachShadow({mode:"open"}),this.#n=document.createElement("div"),this.createUI(),this.addEventListeners(),this.initialize(),this.#n.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const r=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#s.innerHTML=`<input type="hidden" name="${r}">`,"idle"!==o.state&&"waiting"!==o.state||(l&&(clearTimeout(l),l=null),o.state="waiting",l=setTimeout(()=>p(),2500))}async solve(){if(!this.#i)try{this.#i=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#n.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let s,i,c=this.getAttribute("data-cap-api-endpoint");if(!c&&window?.CAP_CUSTOM_FETCH)c="/";else if(!c)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");if(c.endsWith("/")||(c+="/"),"done"===o.state&&o.token&&o.tokenExpires&&Date.now()<o.tokenExpires){this.dispatchEvent("progress",{progress:100});const t=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${t}']`)&&(this.querySelector(`input[name='${t}']`).value=o.token),this.dispatchEvent("solve",{token:o.token}),this.token=o.token;const r=o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),r),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),e&&navigator.vibrate([10,50,20,30,40]),a(),this.#i=!1,{success:!0,token:this.token}}if("done"===o.state)s=o.results,i=o.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===o.state||"redeeming"===o.state||"fetching"===o.state||"waiting"===o.state){"waiting"===o.state&&(l&&(clearTimeout(l),l=null),o.state="waiting",p()),o.pendingPromotion=this.#t,o.promoteFn&&o.promoteFn(this.#t);const t=setInterval(()=>{if("solving"!==o.state&&"redeeming"!==o.state)return void clearInterval(t);const e=o.challenges?o.challenges.length:1,r=o.completedCount,n="redeeming"===o.state?99:Math.min(98,Math.round(r/e*100));this.dispatchEvent("progress",{progress:n})},150);if(await new Promise(e=>o.onSettled(e)),clearInterval(t),"done"!==o.state)throw new Error("Speculative solve failed – please try again");if(o.token&&o.tokenExpires&&Date.now()<o.tokenExpires){this.dispatchEvent("progress",{progress:100});const t=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${t}']`)&&(this.querySelector(`input[name='${t}']`).value=o.token),this.dispatchEvent("solve",{token:o.token}),this.token=o.token;const r=o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),r),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),e&&navigator.vibrate([10,50,20,30,40]),a(),this.#i=!1,{success:!0,token:this.token}}s=o.results,i=o.challengeResp,this.dispatchEvent("progress",{progress:100})}else{const e=await t(`${c}challenge`,{method:"POST"});try{i=await e.json()}catch{throw new Error("Failed to parse challenge response from server")}if(i.error)throw new Error(i.error);const{challenge:n,token:o}=i;let a=n;if(!Array.isArray(a)){let e=0;a=Array.from({length:n.c},()=>(e++,[r(`${o}${e}`,n.s),r(`${o}${e}d`,n.d)]))}s=await this.solveChallenges(a)}const d=i.instrumentation?n(i.instrumentation):Promise.resolve(null),h=await d;if(h?.__timeout||h?.__blocked){this.updateUIBlocked(this.getI18nText("error-label","Error"),h?.__blocked),this.#n.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.#i=!1)}const{token:u}=i,g=await t(`${c}redeem`,{method:"POST",body:JSON.stringify({token:u,solutions:s,...h&&{instr:h}}),headers:{"Content-Type":"application/json"}});let m;try{m=await g.json()}catch{throw new Error("Failed to parse server response")}if(this.dispatchEvent("progress",{progress:100}),!m.success)throw new Error(m.error||"Invalid solution");const v=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${v}']`)&&(this.querySelector(`input[name='${v}']`).value=m.token),this.dispatchEvent("solve",{token:m.token}),this.token=m.token,a(),this.#e&&clearTimeout(this.#e);const b=new Date(m.expires).getTime()-Date.now();return b>0&&b<864e5?this.#e=setTimeout(()=>this.reset(),b):this.error("Invalid expiration time"),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),e&&navigator.vibrate([10,50,20,30,40]),{success:!0,token:this.token}}catch(e){throw this.#n.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#i=!1}}async solveChallenges(e){const t=e.length;let r=0;let n=null;const s="object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate;if(s)try{n=await i()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}if(!s&&!this.#r.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.#r.insertBefore(e,this.#r.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}const o=new m(this.#t);o.setWasm(n),o._ensureSize(this.#t);const a=[];try{for(let n=0;n<e.length;n+=this.#t){const s=e.slice(n,Math.min(n+this.#t,e.length)),i=await Promise.all(s.map(([e,n])=>o.run(e,n).then(e=>{r++;const n=Math.min(99,Math.round((0+r)/t*100));return this.dispatchEvent("progress",{progress:n}),e})));a.push(...i)}}finally{o.terminate()}return a}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#t=!Number.isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}createUI(){this.#n.classList.add("captcha"),this.#n.setAttribute("role","button"),this.#n.setAttribute("tabindex","0"),this.#n.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#n.setAttribute("aria-live","polite"),this.#n.setAttribute("disabled","true"),this.#n.innerHTML=`<div class="checkbox" part="checkbox"><svg class="progress-ring" viewBox="0 0 32 32"><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><a part="attribution" aria-label="Secured by Cap" href="https://capjs.js.org/" class="credits" target="_blank" rel="follow noopener" title="Secured by Cap: Self-hosted CAPTCHA for the modern web.">Cap</a>`,this.#r.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>@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{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;height:var(--cap-widget-height,58px);width:var(--cap-widget-width,260px);padding:var(--cap-widget-padding,14px);align-items:center;gap:var(--cap-gap,15px);cursor:pointer;-webkit-tap-highlight-color:#fff0;color:var(--cap-color,#212121);transition:filter .2s,transform .2s;display:flex;position:relative;overflow:hidden}.captcha:not([data-state]):active{transform:scale(.97)}.captcha:hover{filter:brightness(98%)}.captcha *{font-family:var(--cap-font,system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif)}.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);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;left: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[disabled]{cursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{color:var(--cap-color,#212121);opacity:.8;text-underline-offset:.18em;z-index:6;font-size:12px;text-decoration-thickness:.08em;position:absolute;bottom:10px;right:10px}.cap-troubleshoot-link{color:#06c;text-underline-offset:.15em;cursor:pointer;font-weight:500;font-size:inherit;text-decoration:underline;text-decoration-thickness:.08em}.cap-troubleshoot-link:hover{opacity:.8}</style>`,this.#r.appendChild(this.#n)}addEventListeners(){this.#n&&(this.#n.querySelector("a").addEventListener("click",e=>{e.stopPropagation(),e.preventDefault(),window.open("https://capjs.js.org","_blank")}),this.#n.addEventListener("click",()=>{this.#n.hasAttribute("disabled")||this.solve()}),this.#n.addEventListener("mousedown",()=>{!this.#n.hasAttribute("disabled")&&e&&navigator.vibrate(5)}),this.#n.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||this.#n.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.#n)return;const t=this.#n.querySelector(".label-wrapper");if(!t)return;if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches){const r=t.querySelector(".label.active");if(r)r.textContent=e;else{const r=document.createElement("span");r.className="label active",r.textContent=e,t.appendChild(r)}return}const r=t.querySelector(".label.active"),n=document.createElement("span");n.className="label",n.textContent=e,t.appendChild(n),n.offsetWidth,n.classList.add("active"),r&&(r.classList.remove("active"),r.classList.add("exit"),r.addEventListener("transitionend",()=>r.remove(),{once:!0}))}updateUI(e,t,r=!1){this.#n&&(this.#n.setAttribute("data-state",e),this.animateLabel(t),r?this.#n.setAttribute("disabled","true"):this.#n.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(!this.#n)return;this.#n.setAttribute("data-state","error"),this.#n.removeAttribute("disabled");const r=this.#n.querySelector(".label-wrapper");if(!r)return;const n=this.getAttribute("data-cap-troubleshooting-url")||"https://capjs.js.org/guide/troubleshooting/instrumentation.html",s=r.querySelector(".label.active"),i=document.createElement("span");i.className="label",i.innerHTML=t?`${e} · <a class="cap-troubleshoot-link" href="${n}" target="_blank" rel="noopener">${this.getI18nText("troubleshooting-label","Troubleshoot")}</a>`:e,r.appendChild(i),i.offsetWidth,i.classList.add("active"),s&&(s.classList.remove("active"),s.classList.add("exit"),s.addEventListener("transitionend",()=>s.remove(),{once:!0}));const o=i.querySelector(".cap-troubleshoot-link");o&&(console.log("linkblud"),o.addEventListener("click",e=>{e.stopPropagation()}))}handleProgress(e){if(!this.#n)return;const t=this.#n.querySelector(".progress-ring-circle");if(t){const r=2*Math.PI*14,n=r-e.detail.progress/100*r;t.style.strokeDashoffset=n}const r=this.#n.querySelector(".label-wrapper");if(r){const e=r.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)}handleError(t){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",t),e&&navigator.vibrate([10,40,10])}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e)}executeAttributeCode(e,t){const r=this.getAttribute(e);r&&(console.error("[cap] using `onxxx='…'` is strongly discouraged and will be deprecated soon. please use `addEventListener` callbacks instead."),new Function("event",r).call(this,t))}error(e="Unknown error"){console.error("[cap]",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const r=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(r)}reset(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.dispatchEvent("reset"),this.token=null;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.#o.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#o.clear(),this.#r&&(this.#r.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null)}}class f{constructor(e={},t){const r=t||document.createElement("cap-widget");if(Object.entries(e).forEach(([e,t])=>{r.setAttribute(e,t)}),!e.apiEndpoint&&!window?.CAP_CUSTOM_FETCH)throw r.remove(),new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");e.apiEndpoint&&r.setAttribute("data-cap-api-endpoint",e.apiEndpoint),this.widget=r,this.solve=this.widget.solve.bind(this.widget),this.reset=this.widget.reset.bind(this.widget),this.addEventListener=this.widget.addEventListener.bind(this.widget),Object.defineProperty(this,"token",{get:()=>r.token,configurable:!0,enumerable:!0}),t||(r.style.display="none",document.documentElement.appendChild(r))}}window.Cap=f,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",b),"object"==typeof exports&&"undefined"!=typeof module?module.exports=f:"function"==typeof define&&define.amd&&define([],()=>f),"undefined"!=typeof exports&&(exports.default=f)})();
|
|
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)}async function s(e){var 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);const i=await new Promise((e,i)=>{try{var s=new DecompressionStream("deflate-raw"),r=s.writable.getWriter(),a=s.readable.getReader(),n=[];a.read().then(function t(s){if(s.done){for(var r=0,o=0,l=0;l<n.length;l++)r+=n[l].length;var c=new Uint8Array(r);for(l=0;l<n.length;l++)c.set(n[l],o),o+=n[l].length;e((new TextDecoder).decode(c))}else n.push(s.value),a.read().then(t).catch(i)}).catch(i),r.write(t).then(()=>{r.close()}).catch(i)}catch(e){i(e)}});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),s.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script>'+i+"\n<\/script></body></html>",document.body.appendChild(s)})}let r=null;const a=()=>{if(r)return r;const e=window.CAP_CUSTOM_WASM_URL||"https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.6/browser/cap_wasm_bg.wasm";return r=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 r=null,e}),r};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&a().catch(()=>{});let n=null;function o(){return n||(n=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"})),n)}class l{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=o(),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 c extends HTMLElement{#e=null;#t=navigator.hardwareConcurrency||8;token=null;#i;#s;#r;#a=!1;#n;#o=null;#l=null;#c=null;#h=null;get#d(){return e&&!window.CAP_DISABLE_HAPTICS&&!this.hasAttribute("data-cap-disable-haptics")}#p(){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)}}}#u(){this.#o=this.#p(),this.#v()}#g(){this.#h&&(window.removeEventListener("mousemove",this.#h),window.removeEventListener("touchstart",this.#h),window.removeEventListener("keydown",this.#h),this.#h=null)}#v(){this.#g();const e=()=>{this.#g(),this.#m()};this.#h=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}#b(){return"function"==typeof this.checkVisibility?this.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0}):!!(this.offsetParent||this.getClientRects().length>0)}#m(){"idle"===this.#o.state&&this.#b()&&(this.#o.state="waiting",this.#l=setTimeout(()=>{this.#w()},2500))}async#w(){if("waiting"!==this.#o.state)return;this.#o.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.#o.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.#o.challenges=o,this.#o.state="solving",this.#o.solvePromise=this.#f(o)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),this.#o.state="error",this.#o.notify()}}else this.#o.state="idle"}async#f(e){o();let t=null;try{t=await a()}catch{}this.#c||(this.#c=new l(1),this.#c._spawn()),this.#c.setWasm(t);const i=e.length,s=new Array(i);let r=1,n=!1;this.#o.promoteFn=e=>{n||(n=!0,r=e,this.#c._size=e,this.#c._ensureSize(e))},null!==this.#o.pendingPromotion&&(this.#o.promoteFn(this.#o.pendingPromotion),this.#o.pendingPromotion=null);let c=0;for(;c<i;){const t=r,a=[],o=[];for(let s=0;s<t&&c<i;s++)o.push(c),a.push(e[c]),c++;this.#c._ensureSize(Math.max(r,t));const l=await Promise.all(a.map(e=>this.#c.run(e[0],e[1]).then(e=>(this.#o.completedCount++,e))));for(let e=0;e<o.length;e++)s[o[e]]=l[e];!n&&c<i&&await new Promise(e=>setTimeout(e,120))}return this.#o.results=s,this.#o.state="redeeming",this.#y(s),s}async#y(e){try{const i=this.#o.challengeResp,r=i._apiEndpoint;if(!r)throw new Error("[cap] speculative redeem: missing apiEndpoint");let a=null;if(i.instrumentation&&(a=await s(i.instrumentation),a?.__timeout||a?.__blocked))return this.#o.state="done",void this.#o.notify();const n=await t(`${r}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.#o.token=o.token,this.#o.tokenExpires=new Date(o.expires).getTime(),this.#o.state="done",this.#o.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),this.#o.state="done",this.#o.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"]}constructor(){super(),this.#n&&this.#n.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#n=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)}initialize(){o(),this.#o||(this.#o=this.#p()),this.#c||(this.#c=new l(1),this.#c._spawn())}attributeChangedCallback(e,t,i){if(e.startsWith("on")){const t=e.slice(2),s=this.#n.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.#n.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"))}async connectedCallback(){this.#r=this,this.#i=this.attachShadow({mode:"open"}),this.#s=document.createElement("div"),this.createUI(),this.addEventListeners(),this.initialize(),this.#s.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.#r.innerHTML=`<input type="hidden" name="${i}">`,this.#v()}async solve(){if(!this.#a)try{this.#a=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#s.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let e,r,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.#o.state&&this.#o.token&&this.#o.tokenExpires&&Date.now()<this.#o.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.#o.token),this.dispatchEvent("solve",{token:this.#o.token}),this.token=this.#o.token;const t=this.#o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#s.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#d&&navigator.vibrate([10,50,20,30,40]),this.#u(),this.#a=!1,{success:!0,token:this.token}}if("done"===this.#o.state)e=this.#o.results,r=this.#o.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===this.#o.state||"redeeming"===this.#o.state||"fetching"===this.#o.state||"waiting"===this.#o.state){"waiting"===this.#o.state&&(this.#l&&(clearTimeout(this.#l),this.#l=null),this.#o.state="waiting",this.#w()),this.#o.pendingPromotion=this.#t,this.#o.promoteFn&&this.#o.promoteFn(this.#t);const t=setInterval(()=>{if("solving"!==this.#o.state&&"redeeming"!==this.#o.state)return void clearInterval(t);const e=this.#o.challenges?this.#o.challenges.length:1,i=this.#o.completedCount,s="redeeming"===this.#o.state?99:Math.min(98,Math.round(i/e*100));this.dispatchEvent("progress",{progress:s})},150);if(await new Promise(e=>this.#o.onSettled(e)),clearInterval(t),"done"!==this.#o.state)throw new Error("Speculative solve failed – please try again");if(this.#o.token&&this.#o.tokenExpires&&Date.now()<this.#o.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.#o.token),this.dispatchEvent("solve",{token:this.#o.token}),this.token=this.#o.token;const t=this.#o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#s.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#d&&navigator.vibrate([10,50,20,30,40]),this.#u(),this.#a=!1,{success:!0,token:this.token}}e=this.#o.results,r=this.#o.challengeResp,this.dispatchEvent("progress",{progress:100})}else{const s=await t(`${a}challenge`,{method:"POST"});try{r=await s.json()}catch{throw new Error("Failed to parse challenge response from server")}if(r.error)throw new Error(r.error);const{challenge:n,token:o}=r;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=r.instrumentation?s(r.instrumentation):Promise.resolve(null),o=await n;if(o?.__timeout||o?.__blocked){this.updateUIBlocked(this.getI18nText("error-label","Error"),o?.__blocked),this.#s.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.#a=!1)}const{token:l}=r,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.#u(),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.#s.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#d&&navigator.vibrate([10,50,20,30,40]),{success:!0,token:this.token}}catch(e){throw this.#s.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#a=!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 a()}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 n=new l(this.#t);n.setWasm(s),n._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)),a=await Promise.all(r.map(([e,s])=>n.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(...a)}}finally{n.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","button"),this.#s.setAttribute("tabindex","0"),this.#s.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#s.setAttribute("aria-live","polite"),this.#s.setAttribute("disabled","true"),this.#s.innerHTML=`<div class="checkbox" part="checkbox"><svg class="progress-ring" viewBox="0 0 32 32"><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><a part="attribution" aria-label="Secured by Cap" href="https://capjs.js.org/" class="credits" target="_blank" rel="follow noopener" title="Secured by Cap: Self-hosted CAPTCHA for the modern web.">Cap</a>`,this.#i.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>@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{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;height:var(--cap-widget-height,58px);width:var(--cap-widget-width,260px);padding:var(--cap-widget-padding,14px);align-items:center;gap:var(--cap-gap,15px);cursor:pointer;-webkit-tap-highlight-color:#fff0;color:var(--cap-color,#212121);transition:filter .2s,transform .2s;display:flex;position:relative;overflow:hidden}.captcha:not([data-state]):active{transform:scale(.97)}.captcha:hover{filter:brightness(98%)}.captcha *{font-family:var(--cap-font,system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif)}.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);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;left: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[disabled]{cursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{color:var(--cap-color,#212121);opacity:.8;text-underline-offset:.18em;z-index:6;font-size:12px;text-decoration-thickness:.08em;position:absolute;bottom:10px;right:10px}.cap-troubleshoot-link{color:#06c;text-underline-offset:.15em;cursor:pointer;font-weight:500;font-size:inherit;text-decoration:underline;text-decoration-thickness:.08em}.cap-troubleshoot-link:hover{opacity:.8}</style>`,this.#i.appendChild(this.#s)}addEventListeners(){this.#s&&(this.#s.querySelector("a").addEventListener("click",e=>{e.stopPropagation(),e.preventDefault(),window.open("https://capjs.js.org","_blank")}),this.#s.addEventListener("click",()=>{this.#s.hasAttribute("disabled")||this.solve()}),this.#s.addEventListener("mousedown",()=>{!this.#s.hasAttribute("disabled")&&this.#d&&navigator.vibrate(5)}),this.#s.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||this.#s.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.#s)return;const t=this.#s.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.#s.setAttribute("data-state",e),this.animateLabel(t),i?this.#s.setAttribute("disabled","true"):this.#s.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(!this.#s)return;this.#s.setAttribute("data-state","error"),this.#s.removeAttribute("disabled");const i=this.#s.querySelector(".label-wrapper");if(!i)return;const s=this.getAttribute("data-cap-troubleshooting-url")||"https://capjs.js.org/guide/troubleshooting/instrumentation.html",r=i.querySelector(".label.active"),a=document.createElement("span");a.className="label",a.innerHTML=t?`${e} · <a class="cap-troubleshoot-link" href="${s}" target="_blank" rel="noopener">${this.getI18nText("troubleshooting-label","Troubleshoot")}</a>`:e,i.appendChild(a),a.offsetWidth,a.classList.add("active"),r&&(r.classList.remove("active"),r.classList.add("exit"),r.addEventListener("transitionend",()=>r.remove(),{once:!0}));const n=a.querySelector(".cap-troubleshoot-link");n&&n.addEventListener("click",e=>{e.stopPropagation()})}handleProgress(e){if(!this.#s)return;const t=this.#s.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.#s.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)}handleError(e){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",e),this.#d&&navigator.vibrate([10,40,10])}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e)}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.dispatchEvent("reset"),this.token=null;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.#n.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#n.clear(),this.#i&&(this.#i.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.#g(),this.#l&&(clearTimeout(this.#l),this.#l=null),this.#c&&(this.#c.terminate(),this.#c=null),this.#o&&(this.#o.state="error",this.#o.notify(),this.#o=null)}}class h{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=h,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",c),"object"==typeof exports&&"undefined"!=typeof module?module.exports=h:"function"==typeof define&&define.amd&&define([],()=>h),"undefined"!=typeof exports&&(exports.default=h)})();
|
package/package.json
CHANGED
package/src/cap.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
const WASM_VERSION = "0.0.6";
|
|
3
|
-
const
|
|
3
|
+
const _browserHasHaptics =
|
|
4
4
|
"vibrate" in navigator && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
5
5
|
|
|
6
6
|
if (typeof window === "undefined") {
|
|
@@ -175,253 +175,6 @@
|
|
|
175
175
|
const SPECULATIVE_WORKERS = 1;
|
|
176
176
|
const SPECULATIVE_YIELD_MS = 120;
|
|
177
177
|
|
|
178
|
-
const speculative = {
|
|
179
|
-
state: "idle",
|
|
180
|
-
challengeResp: null,
|
|
181
|
-
challenges: null,
|
|
182
|
-
results: [],
|
|
183
|
-
completedCount: 0,
|
|
184
|
-
solvePromise: null,
|
|
185
|
-
promoteFn: null,
|
|
186
|
-
_listeners: [],
|
|
187
|
-
pendingPromotion: null,
|
|
188
|
-
token: null,
|
|
189
|
-
tokenExpires: null,
|
|
190
|
-
|
|
191
|
-
notify() {
|
|
192
|
-
for (const fn of this._listeners) fn();
|
|
193
|
-
this._listeners = [];
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
onSettled(fn) {
|
|
197
|
-
if (this.state === "done" || this.state === "error") {
|
|
198
|
-
fn();
|
|
199
|
-
} else {
|
|
200
|
-
this._listeners.push(fn);
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
function _resetSpeculativeState() {
|
|
206
|
-
speculative.state = "idle";
|
|
207
|
-
speculative.challengeResp = null;
|
|
208
|
-
speculative.challenges = null;
|
|
209
|
-
speculative.results = [];
|
|
210
|
-
speculative.completedCount = 0;
|
|
211
|
-
speculative.solvePromise = null;
|
|
212
|
-
speculative.promoteFn = null;
|
|
213
|
-
speculative.pendingPromotion = null;
|
|
214
|
-
speculative._listeners = [];
|
|
215
|
-
speculative.token = null;
|
|
216
|
-
speculative.tokenExpires = null;
|
|
217
|
-
|
|
218
|
-
_attachInteractionListeners();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let _speculativeTimer = null;
|
|
222
|
-
|
|
223
|
-
function _onFirstInteraction() {
|
|
224
|
-
if (speculative.state !== "idle") return;
|
|
225
|
-
speculative.state = "waiting";
|
|
226
|
-
|
|
227
|
-
_speculativeTimer = setTimeout(() => {
|
|
228
|
-
_beginSpeculativeSolve();
|
|
229
|
-
}, SPECULATIVE_DELAY_MS);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let _currentInteractionHandler = null;
|
|
233
|
-
|
|
234
|
-
function _detachInteractionListeners() {
|
|
235
|
-
if (_currentInteractionHandler) {
|
|
236
|
-
window.removeEventListener("mousemove", _currentInteractionHandler);
|
|
237
|
-
window.removeEventListener("touchstart", _currentInteractionHandler);
|
|
238
|
-
window.removeEventListener("keydown", _currentInteractionHandler);
|
|
239
|
-
_currentInteractionHandler = null;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function _attachInteractionListeners() {
|
|
244
|
-
_detachInteractionListeners();
|
|
245
|
-
|
|
246
|
-
const handler = () => {
|
|
247
|
-
_detachInteractionListeners();
|
|
248
|
-
_onFirstInteraction();
|
|
249
|
-
};
|
|
250
|
-
_currentInteractionHandler = handler;
|
|
251
|
-
window.addEventListener("mousemove", handler, { passive: true });
|
|
252
|
-
window.addEventListener("touchstart", handler, { passive: true });
|
|
253
|
-
window.addEventListener("keydown", handler, { passive: true });
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
_attachInteractionListeners();
|
|
257
|
-
|
|
258
|
-
async function _beginSpeculativeSolve() {
|
|
259
|
-
if (speculative.state !== "waiting") return;
|
|
260
|
-
speculative.state = "fetching";
|
|
261
|
-
|
|
262
|
-
const widget = document.querySelector("cap-widget");
|
|
263
|
-
if (!widget) {
|
|
264
|
-
speculative.state = "idle";
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
let apiEndpoint = widget.getAttribute("data-cap-api-endpoint");
|
|
269
|
-
if (!apiEndpoint && window?.CAP_CUSTOM_FETCH) {
|
|
270
|
-
apiEndpoint = "/";
|
|
271
|
-
}
|
|
272
|
-
if (!apiEndpoint) {
|
|
273
|
-
speculative.state = "idle";
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
if (!apiEndpoint.endsWith("/")) apiEndpoint += "/";
|
|
277
|
-
|
|
278
|
-
try {
|
|
279
|
-
const raw = await capFetch(`${apiEndpoint}challenge`, { method: "POST" });
|
|
280
|
-
let resp;
|
|
281
|
-
try {
|
|
282
|
-
resp = await raw.json();
|
|
283
|
-
} catch {
|
|
284
|
-
throw new Error("Failed to parse speculative challenge response");
|
|
285
|
-
}
|
|
286
|
-
if (resp.error) throw new Error(resp.error);
|
|
287
|
-
|
|
288
|
-
resp._apiEndpoint = apiEndpoint;
|
|
289
|
-
speculative.challengeResp = resp;
|
|
290
|
-
|
|
291
|
-
const { challenge, token } = resp;
|
|
292
|
-
let challenges = challenge;
|
|
293
|
-
if (!Array.isArray(challenges)) {
|
|
294
|
-
let i = 0;
|
|
295
|
-
challenges = Array.from({ length: challenge.c }, () => {
|
|
296
|
-
i++;
|
|
297
|
-
return [prng(`${token}${i}`, challenge.s), prng(`${token}${i}d`, challenge.d)];
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
speculative.challenges = challenges;
|
|
301
|
-
speculative.state = "solving";
|
|
302
|
-
|
|
303
|
-
speculative.solvePromise = _speculativeSolveAll(challenges);
|
|
304
|
-
} catch (e) {
|
|
305
|
-
console.warn("[cap] speculative challenge fetch failed:", e);
|
|
306
|
-
speculative.state = "error";
|
|
307
|
-
speculative.notify();
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async function _speculativeSolveAll(challenges) {
|
|
312
|
-
_getSharedWorkerUrl();
|
|
313
|
-
|
|
314
|
-
let wasmModule = null;
|
|
315
|
-
try {
|
|
316
|
-
wasmModule = await getWasmModule();
|
|
317
|
-
} catch {}
|
|
318
|
-
|
|
319
|
-
_speculativePool.setWasm(wasmModule);
|
|
320
|
-
|
|
321
|
-
const total = challenges.length;
|
|
322
|
-
const results = new Array(total);
|
|
323
|
-
|
|
324
|
-
let concurrency = SPECULATIVE_WORKERS;
|
|
325
|
-
let promoted = false;
|
|
326
|
-
|
|
327
|
-
speculative.promoteFn = (fullCount) => {
|
|
328
|
-
if (promoted) return;
|
|
329
|
-
promoted = true;
|
|
330
|
-
concurrency = fullCount;
|
|
331
|
-
_speculativePool._size = fullCount;
|
|
332
|
-
_speculativePool._ensureSize(fullCount);
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
if (speculative.pendingPromotion !== null) {
|
|
336
|
-
speculative.promoteFn(speculative.pendingPromotion);
|
|
337
|
-
speculative.pendingPromotion = null;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
let nextIndex = 0;
|
|
341
|
-
|
|
342
|
-
while (nextIndex < total) {
|
|
343
|
-
const batchSize = concurrency;
|
|
344
|
-
const batch = [];
|
|
345
|
-
const batchIndices = [];
|
|
346
|
-
|
|
347
|
-
for (let i = 0; i < batchSize && nextIndex < total; i++) {
|
|
348
|
-
batchIndices.push(nextIndex);
|
|
349
|
-
batch.push(challenges[nextIndex]);
|
|
350
|
-
nextIndex++;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
_speculativePool._ensureSize(Math.max(concurrency, batchSize));
|
|
354
|
-
|
|
355
|
-
const batchResults = await Promise.all(
|
|
356
|
-
batch.map((challenge) =>
|
|
357
|
-
_speculativePool.run(challenge[0], challenge[1]).then((nonce) => {
|
|
358
|
-
speculative.completedCount++;
|
|
359
|
-
return nonce;
|
|
360
|
-
}),
|
|
361
|
-
),
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
for (let i = 0; i < batchIndices.length; i++) {
|
|
365
|
-
results[batchIndices[i]] = batchResults[i];
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (!promoted && nextIndex < total) {
|
|
369
|
-
await new Promise((resolve) => setTimeout(resolve, SPECULATIVE_YIELD_MS));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
speculative.results = results;
|
|
374
|
-
speculative.state = "redeeming";
|
|
375
|
-
_speculativeRedeem(results);
|
|
376
|
-
return results;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function _speculativeRedeem(solutions) {
|
|
380
|
-
try {
|
|
381
|
-
const challengeResp = speculative.challengeResp;
|
|
382
|
-
const apiEndpoint = challengeResp._apiEndpoint;
|
|
383
|
-
if (!apiEndpoint) throw new Error("[cap] speculative redeem: missing apiEndpoint");
|
|
384
|
-
|
|
385
|
-
let instrOut = null;
|
|
386
|
-
if (challengeResp.instrumentation) {
|
|
387
|
-
instrOut = await runInstrumentationChallenge(challengeResp.instrumentation);
|
|
388
|
-
if (instrOut?.__timeout || instrOut?.__blocked) {
|
|
389
|
-
speculative.state = "done";
|
|
390
|
-
speculative.notify();
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const redeemRaw = await capFetch(`${apiEndpoint}redeem`, {
|
|
396
|
-
method: "POST",
|
|
397
|
-
body: JSON.stringify({
|
|
398
|
-
token: challengeResp.token,
|
|
399
|
-
solutions,
|
|
400
|
-
...(instrOut && { instr: instrOut }),
|
|
401
|
-
}),
|
|
402
|
-
headers: { "Content-Type": "application/json" },
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
let resp;
|
|
406
|
-
try {
|
|
407
|
-
resp = await redeemRaw.json();
|
|
408
|
-
} catch {
|
|
409
|
-
throw new Error("Failed to parse speculative redeem response");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (!resp.success) throw new Error(resp.error || "Speculative redeem failed");
|
|
413
|
-
|
|
414
|
-
speculative.token = resp.token;
|
|
415
|
-
speculative.tokenExpires = new Date(resp.expires).getTime();
|
|
416
|
-
speculative.state = "done";
|
|
417
|
-
speculative.notify();
|
|
418
|
-
} catch (e) {
|
|
419
|
-
console.warn("[cap] speculative redeem failed (will redo on click):", e);
|
|
420
|
-
speculative.state = "done";
|
|
421
|
-
speculative.notify();
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
178
|
let _sharedWorkerUrl = null;
|
|
426
179
|
|
|
427
180
|
function _getSharedWorkerUrl() {
|
|
@@ -543,9 +296,6 @@
|
|
|
543
296
|
}
|
|
544
297
|
}
|
|
545
298
|
|
|
546
|
-
const _speculativePool = new WorkerPool(1);
|
|
547
|
-
_speculativePool._spawn();
|
|
548
|
-
|
|
549
299
|
class CapWidget extends HTMLElement {
|
|
550
300
|
#resetTimer = null;
|
|
551
301
|
#workersCount = navigator.hardwareConcurrency || 8;
|
|
@@ -556,6 +306,252 @@
|
|
|
556
306
|
#solving = false;
|
|
557
307
|
#eventHandlers;
|
|
558
308
|
|
|
309
|
+
#speculative = null;
|
|
310
|
+
#speculativeTimer = null;
|
|
311
|
+
#speculativePool = null;
|
|
312
|
+
#interactionHandler = null;
|
|
313
|
+
|
|
314
|
+
get #hasHaptics() {
|
|
315
|
+
return _browserHasHaptics && !window.CAP_DISABLE_HAPTICS && !this.hasAttribute("data-cap-disable-haptics");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#makeSpeculativeState() {
|
|
319
|
+
return {
|
|
320
|
+
state: "idle",
|
|
321
|
+
challengeResp: null,
|
|
322
|
+
challenges: null,
|
|
323
|
+
results: [],
|
|
324
|
+
completedCount: 0,
|
|
325
|
+
solvePromise: null,
|
|
326
|
+
promoteFn: null,
|
|
327
|
+
_listeners: [],
|
|
328
|
+
pendingPromotion: null,
|
|
329
|
+
token: null,
|
|
330
|
+
tokenExpires: null,
|
|
331
|
+
|
|
332
|
+
notify() {
|
|
333
|
+
for (const fn of this._listeners) fn();
|
|
334
|
+
this._listeners = [];
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
onSettled(fn) {
|
|
338
|
+
if (this.state === "done" || this.state === "error") {
|
|
339
|
+
fn();
|
|
340
|
+
} else {
|
|
341
|
+
this._listeners.push(fn);
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#resetSpeculativeState() {
|
|
348
|
+
this.#speculative = this.#makeSpeculativeState();
|
|
349
|
+
this.#attachInteractionListeners();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#detachInteractionListeners() {
|
|
353
|
+
if (this.#interactionHandler) {
|
|
354
|
+
window.removeEventListener("mousemove", this.#interactionHandler);
|
|
355
|
+
window.removeEventListener("touchstart", this.#interactionHandler);
|
|
356
|
+
window.removeEventListener("keydown", this.#interactionHandler);
|
|
357
|
+
this.#interactionHandler = null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
#attachInteractionListeners() {
|
|
362
|
+
this.#detachInteractionListeners();
|
|
363
|
+
const handler = () => {
|
|
364
|
+
this.#detachInteractionListeners();
|
|
365
|
+
this.#onFirstInteraction();
|
|
366
|
+
};
|
|
367
|
+
this.#interactionHandler = handler;
|
|
368
|
+
window.addEventListener("mousemove", handler, { passive: true });
|
|
369
|
+
window.addEventListener("touchstart", handler, { passive: true });
|
|
370
|
+
window.addEventListener("keydown", handler, { passive: true });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#isVisible() {
|
|
374
|
+
if (typeof this.checkVisibility === "function") {
|
|
375
|
+
return this.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true });
|
|
376
|
+
}
|
|
377
|
+
// Fallback: offsetParent is null for display:none; also check the style directly
|
|
378
|
+
return !!(this.offsetParent || this.getClientRects().length > 0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#onFirstInteraction() {
|
|
382
|
+
if (this.#speculative.state !== "idle") return;
|
|
383
|
+
if (!this.#isVisible()) return;
|
|
384
|
+
this.#speculative.state = "waiting";
|
|
385
|
+
this.#speculativeTimer = setTimeout(() => {
|
|
386
|
+
this.#beginSpeculativeSolve();
|
|
387
|
+
}, SPECULATIVE_DELAY_MS);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async #beginSpeculativeSolve() {
|
|
391
|
+
if (this.#speculative.state !== "waiting") return;
|
|
392
|
+
this.#speculative.state = "fetching";
|
|
393
|
+
|
|
394
|
+
let apiEndpoint = this.getAttribute("data-cap-api-endpoint");
|
|
395
|
+
if (!apiEndpoint && window?.CAP_CUSTOM_FETCH) {
|
|
396
|
+
apiEndpoint = "/";
|
|
397
|
+
}
|
|
398
|
+
if (!apiEndpoint) {
|
|
399
|
+
this.#speculative.state = "idle";
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (!apiEndpoint.endsWith("/")) apiEndpoint += "/";
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const raw = await capFetch(`${apiEndpoint}challenge`, { method: "POST" });
|
|
406
|
+
let resp;
|
|
407
|
+
try {
|
|
408
|
+
resp = await raw.json();
|
|
409
|
+
} catch {
|
|
410
|
+
throw new Error("Failed to parse speculative challenge response");
|
|
411
|
+
}
|
|
412
|
+
if (resp.error) throw new Error(resp.error);
|
|
413
|
+
|
|
414
|
+
resp._apiEndpoint = apiEndpoint;
|
|
415
|
+
this.#speculative.challengeResp = resp;
|
|
416
|
+
|
|
417
|
+
const { challenge, token } = resp;
|
|
418
|
+
let challenges = challenge;
|
|
419
|
+
if (!Array.isArray(challenges)) {
|
|
420
|
+
let i = 0;
|
|
421
|
+
challenges = Array.from({ length: challenge.c }, () => {
|
|
422
|
+
i++;
|
|
423
|
+
return [prng(`${token}${i}`, challenge.s), prng(`${token}${i}d`, challenge.d)];
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
this.#speculative.challenges = challenges;
|
|
427
|
+
this.#speculative.state = "solving";
|
|
428
|
+
|
|
429
|
+
this.#speculative.solvePromise = this.#speculativeSolveAll(challenges);
|
|
430
|
+
} catch (e) {
|
|
431
|
+
console.warn("[cap] speculative challenge fetch failed:", e);
|
|
432
|
+
this.#speculative.state = "error";
|
|
433
|
+
this.#speculative.notify();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async #speculativeSolveAll(challenges) {
|
|
438
|
+
_getSharedWorkerUrl();
|
|
439
|
+
|
|
440
|
+
let wasmModule = null;
|
|
441
|
+
try {
|
|
442
|
+
wasmModule = await getWasmModule();
|
|
443
|
+
} catch {}
|
|
444
|
+
|
|
445
|
+
if (!this.#speculativePool) {
|
|
446
|
+
this.#speculativePool = new WorkerPool(1);
|
|
447
|
+
this.#speculativePool._spawn();
|
|
448
|
+
}
|
|
449
|
+
this.#speculativePool.setWasm(wasmModule);
|
|
450
|
+
|
|
451
|
+
const total = challenges.length;
|
|
452
|
+
const results = new Array(total);
|
|
453
|
+
|
|
454
|
+
let concurrency = SPECULATIVE_WORKERS;
|
|
455
|
+
let promoted = false;
|
|
456
|
+
|
|
457
|
+
this.#speculative.promoteFn = (fullCount) => {
|
|
458
|
+
if (promoted) return;
|
|
459
|
+
promoted = true;
|
|
460
|
+
concurrency = fullCount;
|
|
461
|
+
this.#speculativePool._size = fullCount;
|
|
462
|
+
this.#speculativePool._ensureSize(fullCount);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
if (this.#speculative.pendingPromotion !== null) {
|
|
466
|
+
this.#speculative.promoteFn(this.#speculative.pendingPromotion);
|
|
467
|
+
this.#speculative.pendingPromotion = null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let nextIndex = 0;
|
|
471
|
+
|
|
472
|
+
while (nextIndex < total) {
|
|
473
|
+
const batchSize = concurrency;
|
|
474
|
+
const batch = [];
|
|
475
|
+
const batchIndices = [];
|
|
476
|
+
|
|
477
|
+
for (let i = 0; i < batchSize && nextIndex < total; i++) {
|
|
478
|
+
batchIndices.push(nextIndex);
|
|
479
|
+
batch.push(challenges[nextIndex]);
|
|
480
|
+
nextIndex++;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
this.#speculativePool._ensureSize(Math.max(concurrency, batchSize));
|
|
484
|
+
|
|
485
|
+
const batchResults = await Promise.all(
|
|
486
|
+
batch.map((challenge) =>
|
|
487
|
+
this.#speculativePool.run(challenge[0], challenge[1]).then((nonce) => {
|
|
488
|
+
this.#speculative.completedCount++;
|
|
489
|
+
return nonce;
|
|
490
|
+
}),
|
|
491
|
+
),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
for (let i = 0; i < batchIndices.length; i++) {
|
|
495
|
+
results[batchIndices[i]] = batchResults[i];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (!promoted && nextIndex < total) {
|
|
499
|
+
await new Promise((resolve) => setTimeout(resolve, SPECULATIVE_YIELD_MS));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
this.#speculative.results = results;
|
|
504
|
+
this.#speculative.state = "redeeming";
|
|
505
|
+
this.#speculativeRedeem(results);
|
|
506
|
+
return results;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async #speculativeRedeem(solutions) {
|
|
510
|
+
try {
|
|
511
|
+
const challengeResp = this.#speculative.challengeResp;
|
|
512
|
+
const apiEndpoint = challengeResp._apiEndpoint;
|
|
513
|
+
if (!apiEndpoint) throw new Error("[cap] speculative redeem: missing apiEndpoint");
|
|
514
|
+
|
|
515
|
+
let instrOut = null;
|
|
516
|
+
if (challengeResp.instrumentation) {
|
|
517
|
+
instrOut = await runInstrumentationChallenge(challengeResp.instrumentation);
|
|
518
|
+
if (instrOut?.__timeout || instrOut?.__blocked) {
|
|
519
|
+
this.#speculative.state = "done";
|
|
520
|
+
this.#speculative.notify();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const redeemRaw = await capFetch(`${apiEndpoint}redeem`, {
|
|
526
|
+
method: "POST",
|
|
527
|
+
body: JSON.stringify({
|
|
528
|
+
token: challengeResp.token,
|
|
529
|
+
solutions,
|
|
530
|
+
...(instrOut && { instr: instrOut }),
|
|
531
|
+
}),
|
|
532
|
+
headers: { "Content-Type": "application/json" },
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
let resp;
|
|
536
|
+
try {
|
|
537
|
+
resp = await redeemRaw.json();
|
|
538
|
+
} catch {
|
|
539
|
+
throw new Error("Failed to parse speculative redeem response");
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (!resp.success) throw new Error(resp.error || "Speculative redeem failed");
|
|
543
|
+
|
|
544
|
+
this.#speculative.token = resp.token;
|
|
545
|
+
this.#speculative.tokenExpires = new Date(resp.expires).getTime();
|
|
546
|
+
this.#speculative.state = "done";
|
|
547
|
+
this.#speculative.notify();
|
|
548
|
+
} catch (e) {
|
|
549
|
+
console.warn("[cap] speculative redeem failed (will redo on click):", e);
|
|
550
|
+
this.#speculative.state = "done";
|
|
551
|
+
this.#speculative.notify();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
559
555
|
getI18nText(key, defaultValue) {
|
|
560
556
|
return this.getAttribute(`data-cap-i18n-${key}`) || defaultValue;
|
|
561
557
|
}
|
|
@@ -588,6 +584,13 @@
|
|
|
588
584
|
|
|
589
585
|
initialize() {
|
|
590
586
|
_getSharedWorkerUrl();
|
|
587
|
+
if (!this.#speculative) {
|
|
588
|
+
this.#speculative = this.#makeSpeculativeState();
|
|
589
|
+
}
|
|
590
|
+
if (!this.#speculativePool) {
|
|
591
|
+
this.#speculativePool = new WorkerPool(1);
|
|
592
|
+
this.#speculativePool._spawn();
|
|
593
|
+
}
|
|
591
594
|
}
|
|
592
595
|
|
|
593
596
|
attributeChangedCallback(name, _, value) {
|
|
@@ -638,14 +641,7 @@
|
|
|
638
641
|
const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
|
|
639
642
|
this.#host.innerHTML = `<input type="hidden" name="${fieldName}">`;
|
|
640
643
|
|
|
641
|
-
|
|
642
|
-
if (_speculativeTimer) {
|
|
643
|
-
clearTimeout(_speculativeTimer);
|
|
644
|
-
_speculativeTimer = null;
|
|
645
|
-
}
|
|
646
|
-
speculative.state = "waiting";
|
|
647
|
-
_speculativeTimer = setTimeout(() => _beginSpeculativeSolve(), SPECULATIVE_DELAY_MS);
|
|
648
|
-
}
|
|
644
|
+
this.#attachInteractionListeners();
|
|
649
645
|
}
|
|
650
646
|
|
|
651
647
|
async solve() {
|
|
@@ -677,21 +673,21 @@
|
|
|
677
673
|
let challengeResp;
|
|
678
674
|
|
|
679
675
|
if (
|
|
680
|
-
speculative.state === "done" &&
|
|
681
|
-
speculative.token &&
|
|
682
|
-
speculative.tokenExpires &&
|
|
683
|
-
Date.now() < speculative.tokenExpires
|
|
676
|
+
this.#speculative.state === "done" &&
|
|
677
|
+
this.#speculative.token &&
|
|
678
|
+
this.#speculative.tokenExpires &&
|
|
679
|
+
Date.now() < this.#speculative.tokenExpires
|
|
684
680
|
) {
|
|
685
681
|
this.dispatchEvent("progress", { progress: 100 });
|
|
686
682
|
|
|
687
683
|
const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
|
|
688
684
|
if (this.querySelector(`input[name='${fieldName}']`)) {
|
|
689
|
-
this.querySelector(`input[name='${fieldName}']`).value = speculative.token;
|
|
685
|
+
this.querySelector(`input[name='${fieldName}']`).value = this.#speculative.token;
|
|
690
686
|
}
|
|
691
|
-
this.dispatchEvent("solve", { token: speculative.token });
|
|
692
|
-
this.token = speculative.token;
|
|
687
|
+
this.dispatchEvent("solve", { token: this.#speculative.token });
|
|
688
|
+
this.token = this.#speculative.token;
|
|
693
689
|
|
|
694
|
-
const expiresIn = speculative.tokenExpires - Date.now();
|
|
690
|
+
const expiresIn = this.#speculative.tokenExpires - Date.now();
|
|
695
691
|
if (this.#resetTimer) clearTimeout(this.#resetTimer);
|
|
696
692
|
this.#resetTimer = setTimeout(() => this.reset(), expiresIn);
|
|
697
693
|
|
|
@@ -702,73 +698,76 @@
|
|
|
702
698
|
"We have verified you're a human, you may now continue",
|
|
703
699
|
),
|
|
704
700
|
);
|
|
705
|
-
if (hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
|
|
701
|
+
if (this.#hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
|
|
706
702
|
|
|
707
|
-
|
|
703
|
+
this.#resetSpeculativeState();
|
|
708
704
|
this.#solving = false;
|
|
709
705
|
return { success: true, token: this.token };
|
|
710
706
|
}
|
|
711
707
|
|
|
712
|
-
if (speculative.state === "done") {
|
|
713
|
-
solutions = speculative.results;
|
|
714
|
-
challengeResp = speculative.challengeResp;
|
|
708
|
+
if (this.#speculative.state === "done") {
|
|
709
|
+
solutions = this.#speculative.results;
|
|
710
|
+
challengeResp = this.#speculative.challengeResp;
|
|
715
711
|
this.dispatchEvent("progress", { progress: 100 });
|
|
716
712
|
} else if (
|
|
717
|
-
speculative.state === "solving" ||
|
|
718
|
-
speculative.state === "redeeming" ||
|
|
719
|
-
speculative.state === "fetching" ||
|
|
720
|
-
speculative.state === "waiting"
|
|
713
|
+
this.#speculative.state === "solving" ||
|
|
714
|
+
this.#speculative.state === "redeeming" ||
|
|
715
|
+
this.#speculative.state === "fetching" ||
|
|
716
|
+
this.#speculative.state === "waiting"
|
|
721
717
|
) {
|
|
722
|
-
if (speculative.state === "waiting") {
|
|
723
|
-
if (
|
|
724
|
-
clearTimeout(
|
|
725
|
-
|
|
718
|
+
if (this.#speculative.state === "waiting") {
|
|
719
|
+
if (this.#speculativeTimer) {
|
|
720
|
+
clearTimeout(this.#speculativeTimer);
|
|
721
|
+
this.#speculativeTimer = null;
|
|
726
722
|
}
|
|
727
|
-
speculative.state = "waiting";
|
|
728
|
-
|
|
723
|
+
this.#speculative.state = "waiting";
|
|
724
|
+
this.#beginSpeculativeSolve();
|
|
729
725
|
}
|
|
730
726
|
|
|
731
|
-
speculative.pendingPromotion = this.#workersCount;
|
|
732
|
-
if (speculative.promoteFn) {
|
|
733
|
-
speculative.promoteFn(this.#workersCount);
|
|
727
|
+
this.#speculative.pendingPromotion = this.#workersCount;
|
|
728
|
+
if (this.#speculative.promoteFn) {
|
|
729
|
+
this.#speculative.promoteFn(this.#workersCount);
|
|
734
730
|
}
|
|
735
731
|
|
|
736
732
|
const progressInterval = setInterval(() => {
|
|
737
|
-
if (
|
|
733
|
+
if (
|
|
734
|
+
this.#speculative.state !== "solving" &&
|
|
735
|
+
this.#speculative.state !== "redeeming"
|
|
736
|
+
) {
|
|
738
737
|
clearInterval(progressInterval);
|
|
739
738
|
return;
|
|
740
739
|
}
|
|
741
|
-
const total = speculative.challenges ? speculative.challenges.length : 1;
|
|
742
|
-
const done = speculative.completedCount;
|
|
740
|
+
const total = this.#speculative.challenges ? this.#speculative.challenges.length : 1;
|
|
741
|
+
const done = this.#speculative.completedCount;
|
|
743
742
|
const visual =
|
|
744
|
-
speculative.state === "redeeming"
|
|
743
|
+
this.#speculative.state === "redeeming"
|
|
745
744
|
? 99
|
|
746
745
|
: Math.min(98, Math.round((done / total) * 100));
|
|
747
746
|
this.dispatchEvent("progress", { progress: visual });
|
|
748
747
|
}, 150);
|
|
749
748
|
|
|
750
|
-
await new Promise((resolve) => speculative.onSettled(resolve));
|
|
749
|
+
await new Promise((resolve) => this.#speculative.onSettled(resolve));
|
|
751
750
|
clearInterval(progressInterval);
|
|
752
751
|
|
|
753
|
-
if (speculative.state !== "done") {
|
|
752
|
+
if (this.#speculative.state !== "done") {
|
|
754
753
|
throw new Error("Speculative solve failed – please try again");
|
|
755
754
|
}
|
|
756
755
|
|
|
757
756
|
if (
|
|
758
|
-
speculative.token &&
|
|
759
|
-
speculative.tokenExpires &&
|
|
760
|
-
Date.now() < speculative.tokenExpires
|
|
757
|
+
this.#speculative.token &&
|
|
758
|
+
this.#speculative.tokenExpires &&
|
|
759
|
+
Date.now() < this.#speculative.tokenExpires
|
|
761
760
|
) {
|
|
762
761
|
this.dispatchEvent("progress", { progress: 100 });
|
|
763
762
|
|
|
764
763
|
const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
|
|
765
764
|
if (this.querySelector(`input[name='${fieldName}']`)) {
|
|
766
|
-
this.querySelector(`input[name='${fieldName}']`).value = speculative.token;
|
|
765
|
+
this.querySelector(`input[name='${fieldName}']`).value = this.#speculative.token;
|
|
767
766
|
}
|
|
768
|
-
this.dispatchEvent("solve", { token: speculative.token });
|
|
769
|
-
this.token = speculative.token;
|
|
767
|
+
this.dispatchEvent("solve", { token: this.#speculative.token });
|
|
768
|
+
this.token = this.#speculative.token;
|
|
770
769
|
|
|
771
|
-
const expiresIn = speculative.tokenExpires - Date.now();
|
|
770
|
+
const expiresIn = this.#speculative.tokenExpires - Date.now();
|
|
772
771
|
if (this.#resetTimer) clearTimeout(this.#resetTimer);
|
|
773
772
|
this.#resetTimer = setTimeout(() => this.reset(), expiresIn);
|
|
774
773
|
|
|
@@ -779,15 +778,15 @@
|
|
|
779
778
|
"We have verified you're a human, you may now continue",
|
|
780
779
|
),
|
|
781
780
|
);
|
|
782
|
-
if (hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
|
|
781
|
+
if (this.#hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
|
|
783
782
|
|
|
784
|
-
|
|
783
|
+
this.#resetSpeculativeState();
|
|
785
784
|
this.#solving = false;
|
|
786
785
|
return { success: true, token: this.token };
|
|
787
786
|
}
|
|
788
787
|
|
|
789
|
-
solutions = speculative.results;
|
|
790
|
-
challengeResp = speculative.challengeResp;
|
|
788
|
+
solutions = this.#speculative.results;
|
|
789
|
+
challengeResp = this.#speculative.challengeResp;
|
|
791
790
|
this.dispatchEvent("progress", { progress: 100 });
|
|
792
791
|
} else {
|
|
793
792
|
const challengeRaw = await capFetch(`${apiEndpoint}challenge`, {
|
|
@@ -869,7 +868,7 @@
|
|
|
869
868
|
this.dispatchEvent("solve", { token: resp.token });
|
|
870
869
|
this.token = resp.token;
|
|
871
870
|
|
|
872
|
-
|
|
871
|
+
this.#resetSpeculativeState();
|
|
873
872
|
|
|
874
873
|
if (this.#resetTimer) clearTimeout(this.#resetTimer);
|
|
875
874
|
const expiresIn = new Date(resp.expires).getTime() - Date.now();
|
|
@@ -886,7 +885,7 @@
|
|
|
886
885
|
"We have verified you're a human, you may now continue",
|
|
887
886
|
),
|
|
888
887
|
);
|
|
889
|
-
if (hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
|
|
888
|
+
if (this.#hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
|
|
890
889
|
|
|
891
890
|
return { success: true, token: this.token };
|
|
892
891
|
} catch (err) {
|
|
@@ -1009,7 +1008,7 @@
|
|
|
1009
1008
|
if (!this.#div.hasAttribute("disabled")) this.solve();
|
|
1010
1009
|
});
|
|
1011
1010
|
this.#div.addEventListener("mousedown", () => {
|
|
1012
|
-
if (!this.#div.hasAttribute("disabled") && hasHaptics) {
|
|
1011
|
+
if (!this.#div.hasAttribute("disabled") && this.#hasHaptics) {
|
|
1013
1012
|
navigator.vibrate(5);
|
|
1014
1013
|
}
|
|
1015
1014
|
});
|
|
@@ -1112,7 +1111,6 @@
|
|
|
1112
1111
|
|
|
1113
1112
|
const link = next.querySelector(".cap-troubleshoot-link");
|
|
1114
1113
|
if (link) {
|
|
1115
|
-
console.log("linkblud");
|
|
1116
1114
|
link.addEventListener("click", (e) => {
|
|
1117
1115
|
e.stopPropagation();
|
|
1118
1116
|
});
|
|
@@ -1150,7 +1148,7 @@
|
|
|
1150
1148
|
this.updateUI("error", this.getI18nText("error-label", "Error. Try again."));
|
|
1151
1149
|
this.executeAttributeCode("onerror", event);
|
|
1152
1150
|
|
|
1153
|
-
if (hasHaptics) navigator.vibrate([10, 40, 10]);
|
|
1151
|
+
if (this.#hasHaptics) navigator.vibrate([10, 40, 10]);
|
|
1154
1152
|
}
|
|
1155
1153
|
|
|
1156
1154
|
handleReset(event) {
|
|
@@ -1226,6 +1224,21 @@
|
|
|
1226
1224
|
clearTimeout(this.#resetTimer);
|
|
1227
1225
|
this.#resetTimer = null;
|
|
1228
1226
|
}
|
|
1227
|
+
|
|
1228
|
+
this.#detachInteractionListeners();
|
|
1229
|
+
if (this.#speculativeTimer) {
|
|
1230
|
+
clearTimeout(this.#speculativeTimer);
|
|
1231
|
+
this.#speculativeTimer = null;
|
|
1232
|
+
}
|
|
1233
|
+
if (this.#speculativePool) {
|
|
1234
|
+
this.#speculativePool.terminate();
|
|
1235
|
+
this.#speculativePool = null;
|
|
1236
|
+
}
|
|
1237
|
+
if (this.#speculative) {
|
|
1238
|
+
this.#speculative.state = "error";
|
|
1239
|
+
this.#speculative.notify();
|
|
1240
|
+
this.#speculative = null;
|
|
1241
|
+
}
|
|
1229
1242
|
}
|
|
1230
1243
|
}
|
|
1231
1244
|
|
|
@@ -1248,6 +1261,10 @@
|
|
|
1248
1261
|
widget.setAttribute("data-cap-api-endpoint", config.apiEndpoint);
|
|
1249
1262
|
}
|
|
1250
1263
|
|
|
1264
|
+
if (!el && !widget.hasAttribute("data-cap-disable-haptics")) {
|
|
1265
|
+
widget.setAttribute("data-cap-disable-haptics", "");
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1251
1268
|
this.widget = widget;
|
|
1252
1269
|
this.solve = this.widget.solve.bind(this.widget);
|
|
1253
1270
|
this.reset = this.widget.reset.bind(this.widget);
|