New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@before.sh/mini

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@before.sh/mini - npm Package Compare versions

Comparing version
1.3.5
to
1.4.1
+1
-1
cap.min.js

@@ -1,1 +0,1 @@

(()=>{const e="0.0.6",t=(...e)=>window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(...e):fetch(...e);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),a="";function i(){return r^=r<<13,r^=r>>>17,r^=r<<5,r>>>0}for(;a.length<t;){a+=i().toString(16).padStart(8,"0")}return a.substring(0,t)}window.CAP_CUSTOM_WASM_URL||[`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${e}/browser/cap_wasm.min.js`,`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${e}/browser/cap_wasm_bg.wasm`].forEach(e=>{const t=document.createElement("link");t.rel="prefetch",t.href=e,t.as=e.endsWith(".wasm")?"fetch":"script",document.head.appendChild(t)});class a extends HTMLElement{#e="";#t=null;#r=navigator.hardwareConcurrency||8;token=null;#a;#i;#s;#o=!1;#n;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","darkmode","cap-label","[cap]"]}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(){this.#e=URL.createObjectURL(new Blob(['(()=>{const e=async({salt:e,target:t})=>{let r=0;const o=new TextEncoder,s=new Uint8Array(t.length/2);for(let e=0;e<s.length;e++)s[e]=parseInt(t.substring(2*e,2*e+2),16);const n=s.length;for(;;)try{for(let t=0;t<5e4;t++){const t=e+r,a=o.encode(t),l=await crypto.subtle.digest("SHA-256",a),c=new Uint8Array(l,0,n);let f=!0;for(let e=0;e<n;e++)if(c[e]!==s[e]){f=!1;break}if(f)return void self.postMessage({nonce:r,found:!0});r++}}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:r}})=>e({salt:t,target:r}));let t,r;self.onmessage=async({data:{salt:o,target:s,wasmUrl:n}})=>{let a;if(t===n||(t=n,await import(n).then(e=>e.default().then(t=>{r=(t?.exports?t.exports:e).solve_pow})).catch(t=>(console.error("[cap worker] using fallback solver due to error:",t),a=!0,e({salt:o,target:s}))),!a))try{const e=performance.now(),t=r(o,s),n=performance.now();self.postMessage({nonce:Number(t),found:!0,durationMs:(n-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"}))}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),a=this.#n.get(e);if(a&&this.removeEventListener(t,a),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.#n.set(e,r),this.addEventListener(t,r)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(r)),"data-cap-i18n-initial-state"===e&&this.#i&&this.#i?.querySelector("p")?.innerText&&(this.#i.querySelector("p").innerText=this.getI18nText("initial-state","I'm a human")),"darkmode"===e&&this.#i&&this.applyTheme(),"cap-label"===e&&this.#i&&this.updateLabel()}async connectedCallback(){this.#s=this,this.#a=this.attachShadow({mode:"open"}),this.#i=document.createElement("div"),this.createUI(),this.addEventListeners(),this.initialize(),this.#i.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}">`}async solve(){if(!this.#o)try{this.#o=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#i.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let e=this.getAttribute("data-cap-api-endpoint");if(!e&&window?.CAP_CUSTOM_FETCH)e="/";else if(!e)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");const{challenge:a,token:i}=await(await t(`${e}challenge`,{method:"POST"})).json();let s=a;if(!Array.isArray(s)){let e=0;s=Array.from({length:a.c},()=>(e+=1,[r(`${i}${e}`,a.s),r(`${i}${e}d`,a.d)]))}const o=await this.solveChallenges(s),n=await(await t(`${e}redeem`,{method:"POST",body:JSON.stringify({token:i,solutions:o}),headers:{"Content-Type":"application/json"}})).json();if(this.dispatchEvent("progress",{progress:100}),!n.success)throw new 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=n.token),this.dispatchEvent("solve",{token:n.token}),this.token=n.token,this.#t&&clearTimeout(this.#t);const c=new Date(n.expires).getTime()-Date.now();return c>0&&c<864e5?this.#t=setTimeout(()=>this.reset(),c):this.error("Invalid expiration time"),this.#i.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),{success:!0,token:this.token}}catch(e){throw this.#i.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#o=!1}}async solveChallenges(t){const r=t.length;let a=0;const i=Array(this.#r).fill(null).map(()=>{try{return new Worker(this.#e)}catch(e){throw console.error("[cap] Failed to create worker:",e),new Error("Worker creation failed")}}),s=([t,s],o)=>new Promise((n,d)=>{const c=i[o];if(!c)return void d(new Error("Worker not available"));const l=setTimeout(()=>{try{c.terminate(),i[o]=new Worker(this.#e)}catch(e){console.error("[cap] error terminating/recreating worker:",e)}d(new Error("Worker timeout"))},3e4);if(c.onmessage=({data:e})=>{e.found&&(clearTimeout(l),a++,this.dispatchEvent("progress",{progress:Math.round(a/r*100)}),n(e.nonce))},c.onerror=e=>{clearTimeout(l),this.error(`Error in worker: ${e.message||e}`),d(e)},c.postMessage({salt:t,target:s,wasmUrl:window.CAP_CUSTOM_WASM_URL||`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${e}/browser/cap_wasm.min.js`}),"object"!=typeof WebAssembly||"function"!=typeof WebAssembly?.instantiate){if(this.#a.querySelector(".warning"))return;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;padding-bottom:calc(var(--cap-border-radius,14px) + 5px);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.#a.insertBefore(e,this.#a.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}}),o=[];try{for(let e=0;e<t.length;e+=this.#r){const r=t.slice(e,Math.min(e+this.#r,t.length)),a=await Promise.all(r.map((e,t)=>s(e,t)));o.push(...a)}}finally{i.forEach(e=>{if(e)try{e.terminate()}catch(e){console.error("[cap] error terminating worker:",e)}})}return o}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#r=!Number.isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}applyTheme(){"true"===this.getAttribute("darkmode")?this.#i.setAttribute("data-theme","dark"):this.#i.removeAttribute("data-theme")}updateLabel(){const e="false"!==this.getAttribute("cap-label"),t=this.#i.querySelector(".credits"),r=this.#i.querySelector(".before-pill");e?(t&&(t.style.display=""),r&&(r.style.display="none")):(t&&(t.style.display="none"),r&&(r.style.display=""))}createUI(){this.#i.classList.add("captcha"),this.#i.setAttribute("role","button"),this.#i.setAttribute("tabindex","0"),this.#i.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#i.setAttribute("aria-live","polite"),this.#i.setAttribute("disabled","true");const e="false"!==this.getAttribute("cap-label");this.#i.innerHTML=`<div class="checkbox" part="checkbox"></div><p part="label">${this.getI18nText("initial-state","I'm a human")}</p><a part="attribution" aria-label="Secured by Cap" href="https://capjs.js.org/" class="credits" target="_blank" rel="follow noopener" style="${e?"":"display:none"}">Cap</a><a href="https://before.sh" class="before-pill" target="_blank" rel="noopener noreferrer" style="${e?"display:none":""}">by before<span class="sh-highlight">.sh</span></a>`,this.#a.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>.captcha,.captcha * {box-sizing:border-box;}.captcha{background-color:var(--cap-background,#fdfdfd);border:2px solid var(--cap-border-color,#d946ef);border-radius:var(--cap-border-radius,14px);user-select:none;height:var(--cap-widget-height, 72px);width:var(--cap-widget-width, 230px);display:flex;align-items:center;padding:var(--cap-widget-padding,14px);gap:var(--cap-gap,15px);cursor:pointer;transition:filter .2s,transform .2s;position:relative;-webkit-tap-highlight-color:rgba(255,255,255,0);overflow:hidden;color:var(--cap-color,#212121)}.captcha:hover{filter:brightness(98%)}.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,100%);background-color:var(--cap-checkbox-background,#fafafa91);transition:opacity .2s;margin-top:var(--cap-checkbox-margin,2px);margin-bottom:var(--cap-checkbox-margin,2px)}.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)}.captcha p{margin:0;font-weight:500;font-size:15px;user-select:none;transition:opacity .2s}.captcha[data-state=verifying] .checkbox{background: none;display:flex;align-items:center;justify-content:center;transform: scale(1.1);border: none;border-radius: 50%;background: conic-gradient(var(--cap-spinner-color,#000) 0%, var(--cap-spinner-color,#000) var(--progress, 0%), var(--cap-spinner-background-color,#eee) var(--progress, 0%), var(--cap-spinner-background-color,#eee) 100%);position: relative;}.captcha[data-state=verifying] .checkbox::after {content: "";background-color: var(--cap-background,#fdfdfd);width: calc(100% - var(--cap-spinner-thickness,5px));height: calc(100% - var(--cap-spinner-thickness,5px));border-radius: 50%;margin:calc(var(--cap-spinner-thickness,5px) / 2)}.captcha[data-state=done] .checkbox{border:1px solid transparent;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}.captcha[data-state=error] .checkbox{border:1px solid transparent;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/svg%3E"));background-size:cover}.captcha[disabled]{cursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{position:absolute;bottom:8px;right:8px;font-size:9px;color:var(--cap-color,#212121);opacity:0.7;text-underline-offset: 1.5px;text-decoration:none;}.before-pill{position:absolute;bottom:6px;right:6px;font-size:9px;padding:3px 7px;background:rgba(0,0,0,0.05);border-radius:10px;color:var(--cap-color,#212121);opacity:0.8;display:inline-flex;align-items:center;gap:0;font-weight:500;text-decoration:none;transition:opacity .2s;}.before-pill:hover{opacity:1;}.before-pill .sh-highlight{color:#d946ef;font-weight:600;}.captcha[data-theme="dark"]{background-color:var(--cap-background-dark,#1a1a1a);color:var(--cap-color-dark,#e5e5e5);border-color:var(--cap-border-color-dark,#d946ef);}.captcha[data-theme="dark"] .checkbox{background-color:var(--cap-checkbox-background-dark,#2a2a2a);border-color:var(--cap-checkbox-border-dark,#555);}.captcha[data-theme="dark"] .credits,.captcha[data-theme="dark"] .before-pill{color:var(--cap-color-dark,#e5e5e5);}.captcha[data-theme="dark"] .before-pill{background:rgba(255,255,255,0.08);}.captcha[data-theme="dark"] .before-pill .sh-highlight{color:#d946ef;}</style>`,this.#a.appendChild(this.#i),this.applyTheme()}addEventListeners(){this.#i&&(this.#i.querySelectorAll("a").forEach(e=>{e.addEventListener("click",e=>{e.stopPropagation()})}),this.#i.addEventListener("click",()=>{this.#i.hasAttribute("disabled")||this.solve()}),this.#i.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||this.#i.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))}updateUI(e,t,r=!1){this.#i&&(this.#i.setAttribute("data-state",e),this.#i.querySelector("p").innerText=t,r?this.#i.setAttribute("disabled","true"):this.#i.removeAttribute("disabled"))}handleProgress(e){if(!this.#i)return;const t=this.#i.querySelector("p"),r=this.#i.querySelector(".checkbox");t&&r&&(r.style.setProperty("--progress",`${e.detail.progress}%`),t.innerText=`${this.getI18nText("verifying-label","Verifying...")} ${e.detail.progress}%`),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)}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&&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.#t&&(clearTimeout(this.#t),this.#t=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.#a&&(this.#a.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#t&&(clearTimeout(this.#t),this.#t=null),this.#e&&(URL.revokeObjectURL(this.#e),this.#e="")}}class i{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=i,customElements.get("cap-widget")||window?.CAP_DONT_SKIP_REDEFINE?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",a);class s extends a{}customElements.get("cap-mini")||window?.CAP_DONT_SKIP_REDEFINE||customElements.define("cap-mini",s),"object"==typeof exports&&"undefined"!=typeof module?module.exports=i:"function"==typeof define&&define.amd&&define([],()=>i),"undefined"!=typeof exports&&(exports.default=i)})();
(()=>{const e="0.0.6",t=["https://captcha.before.sh/v1/captcha"],r=(...e)=>window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(...e):fetch(...e);async function a(e,t,a,i={},s=2){const o=[];for(let n=0;n<e.length;n++){const c=e[n];let d;for(let e=0;e<=s;e++)try{const e=`${c}/${t}/${a}`,s=new AbortController,o=setTimeout(()=>s.abort(),1e4),n=await r(e,{...i,signal:s.signal});if(clearTimeout(o),!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);return await n.json()}catch(t){if(d=t,"AbortError"===t.name)console.warn(`[cap] Request timeout for ${c} (attempt ${e+1}/${s+1})`);else{if(t.message.includes("400")||t.message.includes("401")||t.message.includes("403")){o.push({endpoint:c,error:t.message,fatal:!0});break}console.warn(`[cap] Request failed for ${c} (attempt ${e+1}/${s+1}):`,t.message)}e<s&&await new Promise(t=>setTimeout(t,Math.min(1e3*Math.pow(2,e),5e3)))}o.push({endpoint:c,error:d?.message||"Unknown error",fatal:!1})}const n=o.map(e=>`${e.endpoint}: ${e.error}`).join("; ");throw new Error(`All endpoints failed: ${n}`)}function i(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),a="";function i(){return r^=r<<13,r^=r>>>17,r^=r<<5,r>>>0}for(;a.length<t;){a+=i().toString(16).padStart(8,"0")}return a.substring(0,t)}window.CAP_CUSTOM_WASM_URL||[`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${e}/browser/cap_wasm.min.js`,`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${e}/browser/cap_wasm_bg.wasm`].forEach(e=>{const t=document.createElement("link");t.rel="prefetch",t.href=e,t.as=e.endsWith(".wasm")?"fetch":"script",document.head.appendChild(t)});class s extends HTMLElement{#e="";#t=null;#r=navigator.hardwareConcurrency||8;token=null;#a;#i;#s;#o=!1;#n;getCapAttribute(e,t=null){const r=e.startsWith("cap-")?e:`cap-${e}`;let a=this.getAttribute(r);if(null===a&&t&&(a=this.getAttribute(t)),null===a&&!e.startsWith("data-cap-")){const t=e.startsWith("cap-")?`data-${e}`:`data-cap-${e}`;a=this.getAttribute(t)}return a}getI18nText(e,t){return this.getAttribute(`cap-i18n-${e}`)||this.getAttribute(`data-cap-i18n-${e}`)||t}static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","cap-worker-count","cap-i18n-initial-state","cap-site-key","cap-endpoints","cap-api-endpoint","cap-darkmode","cap-label","data-cap-worker-count","data-cap-i18n-initial-state","data-cap-site-key","data-cap-endpoints","data-cap-api-endpoint","darkmode","[cap]"]}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(){this.#e=URL.createObjectURL(new Blob(['(()=>{const e=async({salt:e,target:t})=>{let r=0;const o=new TextEncoder,s=new Uint8Array(t.length/2);for(let e=0;e<s.length;e++)s[e]=parseInt(t.substring(2*e,2*e+2),16);const n=s.length;for(;;)try{for(let t=0;t<5e4;t++){const t=e+r,a=o.encode(t),l=await crypto.subtle.digest("SHA-256",a),c=new Uint8Array(l,0,n);let f=!0;for(let e=0;e<n;e++)if(c[e]!==s[e]){f=!1;break}if(f)return void self.postMessage({nonce:r,found:!0});r++}}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:r}})=>e({salt:t,target:r}));let t,r;self.onmessage=async({data:{salt:o,target:s,wasmUrl:n}})=>{let a;if(t===n||(t=n,await import(n).then(e=>e.default().then(t=>{r=(t?.exports?t.exports:e).solve_pow})).catch(t=>(console.error("[cap worker] using fallback solver due to error:",t),a=!0,e({salt:o,target:s}))),!a))try{const e=performance.now(),t=r(o,s),n=performance.now();self.postMessage({nonce:Number(t),found:!0,durationMs:(n-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"}))}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),a=this.#n.get(e);if(a&&this.removeEventListener(t,a),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.#n.set(e,r),this.addEventListener(t,r)}}"cap-worker-count"!==e&&"data-cap-worker-count"!==e||this.setWorkersCount(parseInt(r)),("cap-i18n-initial-state"===e||"data-cap-i18n-initial-state"===e)&&this.#i&&this.#i?.querySelector("p")?.innerText&&(this.#i.querySelector("p").innerText=this.getI18nText("initial-state","I'm a human")),"cap-darkmode"!==e&&"darkmode"!==e||!this.#i||this.applyTheme(),"cap-label"===e&&this.#i&&this.updateLabel()}async connectedCallback(){this.#s=this,this.#a=this.attachShadow({mode:"open"}),this.#i=document.createElement("div"),this.createUI(),this.addEventListeners(),this.initialize(),this.#i.removeAttribute("disabled");const e=this.getCapAttribute("worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const r=this.getCapAttribute("hidden-field-name")||"cap-token";this.#s.innerHTML=`<input type="hidden" name="${r}">`}async solve(){if(!this.#o)try{this.#o=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#i.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{const e=this.getCapAttribute("site-key"),s=this.getCapAttribute("api-endpoint"),o=this.getCapAttribute("endpoints");let n,c;if(e){const r=o?o.split(",").map(e=>e.trim()):t;console.log(`[cap] Using siteKey: ${e} with endpoints:`,r),n=await a(r,e,"challenge",{method:"GET"});const{challenge:s,token:d}=n;let l=s;if(!Array.isArray(l)){let e=0;l=Array.from({length:s.c},()=>(e+=1,[i(`${d}${e}`,s.s),i(`${d}${e}d`,s.d)]))}const h=await this.solveChallenges(l);c=await a(r,e,"redeem",{method:"POST",body:JSON.stringify({token:d,solutions:h}),headers:{"Content-Type":"application/json"}})}else{if(!s&&!window?.CAP_CUSTOM_FETCH)throw new Error("Missing configuration. Please provide either data-cap-site-key or data-cap-api-endpoint.");{let e=s;if(!e&&window?.CAP_CUSTOM_FETCH)e="/";else if(!e)throw new Error("Missing configuration. Please provide either data-cap-site-key or data-cap-api-endpoint.");console.log(`[cap] Using legacy endpoint: ${e}`);const{challenge:t,token:a}=await(await r(`${e}challenge`,{method:"POST"})).json();let o=t;if(!Array.isArray(o)){let e=0;o=Array.from({length:t.c},()=>(e+=1,[i(`${a}${e}`,t.s),i(`${a}${e}d`,t.d)]))}const n=await this.solveChallenges(o);c=await(await r(`${e}redeem`,{method:"POST",body:JSON.stringify({token:a,solutions:n}),headers:{"Content-Type":"application/json"}})).json()}}if(this.dispatchEvent("progress",{progress:100}),!c.success)throw new Error("Invalid solution");const d=this.getCapAttribute("hidden-field-name")||"cap-token";this.querySelector(`input[name='${d}']`)&&(this.querySelector(`input[name='${d}']`).value=c.token),this.dispatchEvent("solve",{token:c.token}),this.token=c.token,this.#t&&clearTimeout(this.#t);const l=new Date(c.expires).getTime()-Date.now();return l>0&&l<864e5?this.#t=setTimeout(()=>this.reset(),l):this.error("Invalid expiration time"),this.#i.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),{success:!0,token:this.token}}catch(e){this.#i.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again"));let t="Error. Try again.";throw e.message.includes("All endpoints failed")?t="Service unavailable. Try again.":e.message.includes("Invalid solution")?t="Verification failed. Try again.":(e.message.includes("timeout")||"AbortError"===e.name)&&(t="Request timed out. Try again."),this.updateUI("error",this.getI18nText("error-label",t)),this.error(e.message),e}}finally{this.#o=!1}}async solveChallenges(t){const r=t.length;let a=0;const i=Array(this.#r).fill(null).map(()=>{try{return new Worker(this.#e)}catch(e){throw console.error("[cap] Failed to create worker:",e),new Error("Worker creation failed")}}),s=([t,s],o)=>new Promise((n,c)=>{const d=i[o];if(!d)return void c(new Error("Worker not available"));const l=setTimeout(()=>{try{d.terminate(),i[o]=new Worker(this.#e)}catch(e){console.error("[cap] error terminating/recreating worker:",e)}c(new Error("Worker timeout"))},3e4);if(d.onmessage=({data:e})=>{e.found&&(clearTimeout(l),a++,this.dispatchEvent("progress",{progress:Math.round(a/r*100)}),n(e.nonce))},d.onerror=e=>{clearTimeout(l),this.error(`Error in worker: ${e.message||e}`),c(e)},d.postMessage({salt:t,target:s,wasmUrl:window.CAP_CUSTOM_WASM_URL||`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${e}/browser/cap_wasm.min.js`}),"object"!=typeof WebAssembly||"function"!=typeof WebAssembly?.instantiate){if(this.#a.querySelector(".warning"))return;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;padding-bottom:calc(var(--cap-border-radius,14px) + 5px);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.#a.insertBefore(e,this.#a.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}}),o=[];try{for(let e=0;e<t.length;e+=this.#r){const r=t.slice(e,Math.min(e+this.#r,t.length)),a=await Promise.all(r.map((e,t)=>s(e,t)));o.push(...a)}}finally{i.forEach(e=>{if(e)try{e.terminate()}catch(e){console.error("[cap] error terminating worker:",e)}})}return o}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#r=!Number.isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}applyTheme(){"true"===this.getCapAttribute("darkmode")?this.#i.setAttribute("data-theme","dark"):this.#i.removeAttribute("data-theme")}updateLabel(){const e="false"!==this.getCapAttribute("label"),t=this.#i.querySelector(".credits"),r=this.#i.querySelector(".before-pill");e?(t&&(t.style.display=""),r&&(r.style.display="none")):(t&&(t.style.display="none"),r&&(r.style.display=""))}createUI(){this.#i.classList.add("captcha"),this.#i.setAttribute("role","button"),this.#i.setAttribute("tabindex","0"),this.#i.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#i.setAttribute("aria-live","polite"),this.#i.setAttribute("disabled","true");const e="false"!==this.getCapAttribute("label");this.#i.innerHTML=`<div class="checkbox" part="checkbox"></div><p part="label">${this.getI18nText("initial-state","I'm a human")}</p><a part="attribution" aria-label="Secured by Cap" href="https://capjs.js.org/" class="credits" target="_blank" rel="follow noopener" style="${e?"":"display:none"}">Cap</a><a href="https://before.sh" class="before-pill" target="_blank" rel="noopener noreferrer" style="${e?"display:none":""}">by before<span class="sh-highlight">.sh</span></a>`,this.#a.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>.captcha,.captcha * {box-sizing:border-box;}.captcha{background-color:var(--cap-background,#fdfdfd);border:2px solid var(--cap-border-color,#d946ef);border-radius:var(--cap-border-radius,14px);user-select:none;height:var(--cap-widget-height, 72px);width:var(--cap-widget-width, 230px);display:flex;align-items:center;padding:var(--cap-widget-padding,14px);gap:var(--cap-gap,15px);cursor:pointer;transition:filter .2s,transform .2s;position:relative;-webkit-tap-highlight-color:rgba(255,255,255,0);overflow:hidden;color:var(--cap-color,#212121)}.captcha:hover{filter:brightness(98%)}.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,100%);background-color:var(--cap-checkbox-background,#fafafa91);transition:opacity .2s;margin-top:var(--cap-checkbox-margin,2px);margin-bottom:var(--cap-checkbox-margin,2px)}.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)}.captcha p{margin:0;font-weight:500;font-size:15px;user-select:none;transition:opacity .2s}.captcha[data-state=verifying] .checkbox{background: none;display:flex;align-items:center;justify-content:center;transform: scale(1.1);border: none;border-radius: 50%;background: conic-gradient(var(--cap-spinner-color,#000) 0%, var(--cap-spinner-color,#000) var(--progress, 0%), var(--cap-spinner-background-color,#eee) var(--progress, 0%), var(--cap-spinner-background-color,#eee) 100%);position: relative;}.captcha[data-state=verifying] .checkbox::after {content: "";background-color: var(--cap-background,#fdfdfd);width: calc(100% - var(--cap-spinner-thickness,5px));height: calc(100% - var(--cap-spinner-thickness,5px));border-radius: 50%;margin:calc(var(--cap-spinner-thickness,5px) / 2)}.captcha[data-state=done] .checkbox{border:1px solid transparent;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}.captcha[data-state=error] .checkbox{border:1px solid transparent;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/svg%3E"));background-size:cover}.captcha[disabled]{cursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{position:absolute;bottom:8px;right:8px;font-size:9px;color:var(--cap-color,#212121);opacity:0.7;text-underline-offset: 1.5px;text-decoration:none;}.before-pill{position:absolute;bottom:6px;right:6px;font-size:9px;padding:3px 7px;background:rgba(0,0,0,0.05);border-radius:10px;color:var(--cap-color,#212121);opacity:0.8;display:inline-flex;align-items:center;gap:0;font-weight:500;text-decoration:none;transition:opacity .2s;}.before-pill:hover{opacity:1;}.before-pill .sh-highlight{color:#d946ef;font-weight:600;}.captcha[data-theme="dark"]{background-color:var(--cap-background-dark,#1a1a1a);color:var(--cap-color-dark,#e5e5e5);border-color:var(--cap-border-color-dark,#d946ef);}.captcha[data-theme="dark"] .checkbox{background-color:var(--cap-checkbox-background-dark,#2a2a2a);border-color:var(--cap-checkbox-border-dark,#555);}.captcha[data-theme="dark"] .credits,.captcha[data-theme="dark"] .before-pill{color:var(--cap-color-dark,#e5e5e5);}.captcha[data-theme="dark"] .before-pill{background:rgba(255,255,255,0.08);}.captcha[data-theme="dark"] .before-pill .sh-highlight{color:#d946ef;}</style>`,this.#a.appendChild(this.#i),this.applyTheme()}addEventListeners(){this.#i&&(this.#i.querySelectorAll("a").forEach(e=>{e.addEventListener("click",e=>{e.stopPropagation()})}),this.#i.addEventListener("click",()=>{this.#i.hasAttribute("disabled")||this.solve()}),this.#i.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||this.#i.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))}updateUI(e,t,r=!1){this.#i&&(this.#i.setAttribute("data-state",e),this.#i.querySelector("p").innerText=t,r?this.#i.setAttribute("disabled","true"):this.#i.removeAttribute("disabled"))}handleProgress(e){if(!this.#i)return;const t=this.#i.querySelector("p"),r=this.#i.querySelector(".checkbox");t&&r&&(r.style.setProperty("--progress",`${e.detail.progress}%`),t.innerText=`${this.getI18nText("verifying-label","Verifying...")} ${e.detail.progress}%`),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)}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&&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.#t&&(clearTimeout(this.#t),this.#t=null),this.dispatchEvent("reset"),this.token=null;const e=this.getCapAttribute("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.#a&&(this.#a.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#t&&(clearTimeout(this.#t),this.#t=null),this.#e&&(URL.revokeObjectURL(this.#e),this.#e="")}}class o{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=o,customElements.get("cap-widget")||window?.CAP_DONT_SKIP_REDEFINE?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",s);class n extends s{}customElements.get("cap-mini")||window?.CAP_DONT_SKIP_REDEFINE||customElements.define("cap-mini",n),"object"==typeof exports&&"undefined"!=typeof module?module.exports=o:"function"==typeof define&&define.amd&&define([],()=>o),"undefined"!=typeof exports&&(exports.default=o)})();
{
"name": "@before.sh/mini",
"version": "1.3.5",
"version": "1.4.1",
"description": "Client-side widget for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.",

@@ -5,0 +5,0 @@ "keywords": [

@@ -16,9 +16,47 @@ # @before.sh/mini

### New API (recommended)
```html
<script src="https://cdn.hopjs.net/npm/@before.sh/mini@1.3.5/cap.min.js"></script>
<cap-widget data-cap-api-endpoint="/api/"></cap-widget>
<script src="https://cdn.hopjs.net/npm/@before.sh/mini@1.4.1/cap.min.js"></script>
<cap-mini cap-site-key="YOUR_SITE_KEY"></cap-mini>
```
### Legacy API
```html
<script src="https://cdn.hopjs.net/npm/@before.sh/mini@1.4.1/cap.min.js"></script>
<cap-widget cap-api-endpoint="/api/"></cap-widget>
```
## Features
- ✅ Automatic fallback to multiple endpoints
- ✅ Exponential backoff retry logic
- ✅ Request timeout handling (10s)
- ✅ Enhanced error messages
- ✅ Dark mode support
- ✅ Customizable branding
## Attributes
### New Consistent Naming (v1.4.1+)
- `cap-site-key` - Your site key (new API)
- `cap-endpoints` - Custom endpoints (comma-separated, optional)
- `cap-api-endpoint` - Legacy API endpoint
- `cap-darkmode` - Enable dark mode (`true`/`false`)
- `cap-label` - Show Cap label or before.sh branding (`true`/`false`)
- `cap-worker-count` - Number of worker threads
- `cap-hidden-field-name` - Name for hidden input field (default: `cap-token`)
### Legacy Names (still supported for backwards compatibility)
The following legacy attribute names are still supported:
- `data-cap-site-key` → use `cap-site-key`
- `data-cap-endpoints` → use `cap-endpoints`
- `data-cap-api-endpoint` → use `cap-api-endpoint`
- `darkmode` → use `cap-darkmode`
- `data-cap-worker-count` → use `cap-worker-count`
- `data-cap-hidden-field-name` → use `cap-hidden-field-name`
## License
Apache-2.0 (inherited from Cap.js)
+229
-50
(() => {
const WASM_VERSION = "0.0.6";
// Default fallback endpoints
const DEFAULT_ENDPOINTS = [
'https://captcha.before.sh/v1/captcha'
];
const capFetch = (...args) => {

@@ -11,2 +16,66 @@ if (window?.CAP_CUSTOM_FETCH) {

/**
* Fetch with automatic fallback and retry logic
* @param {string[]} endpoints - Array of endpoint URLs to try
* @param {string} siteKey - Site key for the captcha
* @param {string} path - API path (e.g., 'challenge', 'redeem')
* @param {object} options - Fetch options
* @param {number} maxRetries - Maximum retry attempts per endpoint
* @returns {Promise<any>} Response JSON
*/
async function fetchWithFallback(endpoints, siteKey, path, options = {}, maxRetries = 2) {
const errors = [];
for (let i = 0; i < endpoints.length; i++) {
const endpoint = endpoints[i];
let lastError;
// Try each endpoint with retries
for (let retry = 0; retry <= maxRetries; retry++) {
try {
const url = `${endpoint}/${siteKey}/${path}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
const response = await capFetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (err) {
lastError = err;
// Don't retry on certain errors
if (err.name === 'AbortError') {
console.warn(`[cap] Request timeout for ${endpoint} (attempt ${retry + 1}/${maxRetries + 1})`);
} else if (err.message.includes('400') || err.message.includes('401') || err.message.includes('403')) {
// Client errors - don't retry
errors.push({ endpoint, error: err.message, fatal: true });
break;
} else {
console.warn(`[cap] Request failed for ${endpoint} (attempt ${retry + 1}/${maxRetries + 1}):`, err.message);
}
// Exponential backoff before retry
if (retry < maxRetries) {
await new Promise(resolve => setTimeout(resolve, Math.min(1000 * Math.pow(2, retry), 5000)));
}
}
}
errors.push({ endpoint, error: lastError?.message || 'Unknown error', fatal: false });
}
// All endpoints failed
const errorMsg = errors.map(e => `${e.endpoint}: ${e.error}`).join('; ');
throw new Error(`All endpoints failed: ${errorMsg}`);
}
function prng(seed, length) {

@@ -66,4 +135,28 @@ function fnv1a(str) {

/**
* Get attribute with backwards compatibility
* Checks new cap-* name first, then falls back to legacy data-cap-* or old name
*/
getCapAttribute(name, legacyName = null) {
// Try new cap-* name first
const newName = name.startsWith('cap-') ? name : `cap-${name}`;
let value = this.getAttribute(newName);
// Fall back to legacy name if provided
if (value === null && legacyName) {
value = this.getAttribute(legacyName);
}
// Fall back to data-cap-* version
if (value === null && !name.startsWith('data-cap-')) {
const dataName = name.startsWith('cap-') ? `data-${name}` : `data-cap-${name}`;
value = this.getAttribute(dataName);
}
return value;
}
getI18nText(key, defaultValue) {
return this.getAttribute(`data-cap-i18n-${key}`) || defaultValue;
// Support both cap-i18n-* and data-cap-i18n-*
return this.getAttribute(`cap-i18n-${key}`) || this.getAttribute(`data-cap-i18n-${key}`) || defaultValue;
}

@@ -77,6 +170,17 @@

"onerror",
// New consistent naming
"cap-worker-count",
"cap-i18n-initial-state",
"cap-site-key",
"cap-endpoints",
"cap-api-endpoint",
"cap-darkmode",
"cap-label",
// Legacy support
"data-cap-worker-count",
"data-cap-i18n-initial-state",
"data-cap-site-key",
"data-cap-endpoints",
"data-cap-api-endpoint",
"darkmode",
"cap-label",
"[cap]",

@@ -131,8 +235,10 @@ ];

if (name === "data-cap-worker-count") {
// Worker count (both new and legacy names)
if (name === "cap-worker-count" || name === "data-cap-worker-count") {
this.setWorkersCount(parseInt(value));
}
// I18n (both new and legacy names)
if (
name === "data-cap-i18n-initial-state" &&
(name === "cap-i18n-initial-state" || name === "data-cap-i18n-initial-state") &&
this.#div &&

@@ -147,6 +253,8 @@ this.#div?.querySelector("p")?.innerText

if (name === "darkmode" && this.#div) {
// Dark mode (both new and legacy names)
if ((name === "cap-darkmode" || name === "darkmode") && this.#div) {
this.applyTheme();
}
// Label visibility
if (name === "cap-label" && this.#div) {

@@ -166,7 +274,6 @@ this.updateLabel();

const workers = this.getAttribute("data-cap-worker-count");
const workers = this.getCapAttribute("worker-count");
const parsedWorkers = workers ? parseInt(workers, 10) : null;
this.setWorkersCount(parsedWorkers || navigator.hardwareConcurrency || 8);
const fieldName =
this.getAttribute("data-cap-hidden-field-name") || "cap-token";
const fieldName = this.getCapAttribute("hidden-field-name") || "cap-token";
this.#host.innerHTML = `<input type="hidden" name="${fieldName}">`;

@@ -199,56 +306,117 @@ }

try {
let apiEndpoint = this.getAttribute("data-cap-api-endpoint");
// Determine API mode: new siteKey-based or legacy endpoint-based
const siteKey = this.getCapAttribute("site-key");
const legacyEndpoint = this.getCapAttribute("api-endpoint");
const customEndpoints = this.getCapAttribute("endpoints");
if (!apiEndpoint && window?.CAP_CUSTOM_FETCH) {
apiEndpoint = "/";
} else if (!apiEndpoint)
throw new Error(
"Missing API endpoint. Either custom fetch or an API endpoint must be provided.",
let challengeData, redeemData;
if (siteKey) {
// New siteKey-based API with fallback support
const endpoints = customEndpoints
? customEndpoints.split(',').map(e => e.trim())
: DEFAULT_ENDPOINTS;
console.log(`[cap] Using siteKey: ${siteKey} with endpoints:`, endpoints);
// Fetch challenge
challengeData = await fetchWithFallback(
endpoints,
siteKey,
'challenge',
{ method: 'GET' }
);
const { challenge, token } = await (
await capFetch(`${apiEndpoint}challenge`, {
method: "POST",
})
).json();
const { challenge, token } = challengeData;
let challenges = challenge;
// Prepare challenges
let challenges = challenge;
if (!Array.isArray(challenges)) {
let i = 0;
challenges = Array.from({ length: challenge.c }, () => {
i = i + 1;
return [
prng(`${token}${i}`, challenge.s),
prng(`${token}${i}d`, challenge.d),
];
});
}
if (!Array.isArray(challenges)) {
let i = 0;
// Solve challenges
const solutions = await this.solveChallenges(challenges);
challenges = Array.from({ length: challenge.c }, () => {
i = i + 1;
// Redeem solution
redeemData = await fetchWithFallback(
endpoints,
siteKey,
'redeem',
{
method: 'POST',
body: JSON.stringify({ token, solutions }),
headers: { 'Content-Type': 'application/json' }
}
);
return [
prng(`${token}${i}`, challenge.s),
prng(`${token}${i}d`, challenge.d),
];
});
}
} else if (legacyEndpoint || window?.CAP_CUSTOM_FETCH) {
// Legacy mode: backwards compatible
let apiEndpoint = legacyEndpoint;
const solutions = await this.solveChallenges(challenges);
if (!apiEndpoint && window?.CAP_CUSTOM_FETCH) {
apiEndpoint = "/";
} else if (!apiEndpoint) {
throw new Error(
"Missing configuration. Please provide either data-cap-site-key or data-cap-api-endpoint.",
);
}
const resp = await (
await capFetch(`${apiEndpoint}redeem`, {
method: "POST",
body: JSON.stringify({ token, solutions }),
headers: { "Content-Type": "application/json" },
})
).json();
console.log(`[cap] Using legacy endpoint: ${apiEndpoint}`);
const { challenge, token } = await (
await capFetch(`${apiEndpoint}challenge`, {
method: "POST",
})
).json();
let challenges = challenge;
if (!Array.isArray(challenges)) {
let i = 0;
challenges = Array.from({ length: challenge.c }, () => {
i = i + 1;
return [
prng(`${token}${i}`, challenge.s),
prng(`${token}${i}d`, challenge.d),
];
});
}
const solutions = await this.solveChallenges(challenges);
redeemData = await (
await capFetch(`${apiEndpoint}redeem`, {
method: "POST",
body: JSON.stringify({ token, solutions }),
headers: { "Content-Type": "application/json" },
})
).json();
} else {
throw new Error(
"Missing configuration. Please provide either data-cap-site-key or data-cap-api-endpoint.",
);
}
this.dispatchEvent("progress", { progress: 100 });
if (!resp.success) throw new Error("Invalid solution");
const fieldName =
this.getAttribute("data-cap-hidden-field-name") || "cap-token";
if (!redeemData.success) throw new Error("Invalid solution");
const fieldName = this.getCapAttribute("hidden-field-name") || "cap-token";
if (this.querySelector(`input[name='${fieldName}']`)) {
this.querySelector(`input[name='${fieldName}']`).value = resp.token;
this.querySelector(`input[name='${fieldName}']`).value = redeemData.token;
}
this.dispatchEvent("solve", { token: resp.token });
this.token = resp.token;
this.dispatchEvent("solve", { token: redeemData.token });
this.token = redeemData.token;
if (this.#resetTimer) clearTimeout(this.#resetTimer);
const expiresIn = new Date(resp.expires).getTime() - Date.now();
const expiresIn = new Date(redeemData.expires).getTime() - Date.now();
if (expiresIn > 0 && expiresIn < 24 * 60 * 60 * 1000) {

@@ -277,2 +445,14 @@ this.#resetTimer = setTimeout(() => this.reset(), expiresIn);

);
// Enhanced error messaging
let userMessage = "Error. Try again.";
if (err.message.includes("All endpoints failed")) {
userMessage = "Service unavailable. Try again.";
} else if (err.message.includes("Invalid solution")) {
userMessage = "Verification failed. Try again.";
} else if (err.message.includes("timeout") || err.name === 'AbortError') {
userMessage = "Request timed out. Try again.";
}
this.updateUI("error", this.getI18nText("error-label", userMessage));
this.error(err.message);

@@ -405,3 +585,3 @@ throw err;

applyTheme() {
const isDark = this.getAttribute("darkmode") === "true";
const isDark = this.getCapAttribute("darkmode") === "true";
if (isDark) {

@@ -415,3 +595,3 @@ this.#div.setAttribute("data-theme", "dark");

updateLabel() {
const showCapLabel = this.getAttribute("cap-label") !== "false";
const showCapLabel = this.getCapAttribute("label") !== "false";
const creditsEl = this.#div.querySelector(".credits");

@@ -440,3 +620,3 @@ const pillEl = this.#div.querySelector(".before-pill");

const showCapLabel = this.getAttribute("cap-label") !== "false";
const showCapLabel = this.getCapAttribute("label") !== "false";
this.#div.innerHTML = `<div class="checkbox" part="checkbox"></div><p part="label">${this.getI18nText(

@@ -569,4 +749,3 @@ "initial-state",

this.token = null;
const fieldName =
this.getAttribute("data-cap-hidden-field-name") || "cap-token";
const fieldName = this.getCapAttribute("hidden-field-name") || "cap-token";
if (this.querySelector(`input[name='${fieldName}']`)) {

@@ -573,0 +752,0 @@ this.querySelector(`input[name='${fieldName}']`).value = "";