@cap.js/widget 0.1.37 → 0.1.38

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/README.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  Client-side widget for Cap, the self-hosted CAPTCHA for the modern web.
4
4
 
5
- **[Learn more](https://github.com/tiagozip/cap)**
5
+ **[Learn more](https://github.com/tiagozip/cap)**
package/cap.compat.min.js CHANGED
@@ -1 +1,408 @@
1
- !function(){let e;const t="0.0.5",r=function(){return window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(...arguments):fetch(...arguments)};window.CAP_CUSTOM_WASM_URL||[`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${t}/browser/cap_wasm.min.js`,`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${t}/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;#s;#i;#n;#a=!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","[cap]"]}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(){this.#e=URL.createObjectURL(new Blob([e],{type:"application/javascript"}))}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),s=this.#o.get(e);if(s&&this.removeEventListener(t,s),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.#o.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"))}async connectedCallback(){this.#n=this,this.#s=this.attachShadow({mode:"open"}),this.#i=document.createElement("div"),this.createUI(),this.addEventListeners(),await this.initialize(),this.#i.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const r=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#n.innerHTML=`<input type="hidden" name="${r}">`}async solve(){if(!this.#a)try{this.#a=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.dispatchEvent("progress",{progress:0});try{const e=this.getAttribute("data-cap-api-endpoint");if(!e)throw new Error("Missing API endpoint");const{challenge:t,token:s}=await(await r(`${e}challenge`,{method:"POST"})).json(),i=await this.solveChallenges(t),n=await(await r(`${e}redeem`,{method:"POST",body:JSON.stringify({token:s,solutions:i}),headers:{"Content-Type":"application/json"}})).json();if(this.dispatchEvent("progress",{progress:100}),!n.success)throw new Error("Invalid solution");const a=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${a}']`)&&(this.querySelector(`input[name='${a}']`).value=n.token),this.dispatchEvent("solve",{token:n.token}),this.token=n.token,this.#t&&clearTimeout(this.#t);const o=new Date(n.expires).getTime()-Date.now();return o>0&&o<864e5?this.#t=setTimeout((()=>this.reset()),o):this.error("Invalid expiration time"),{success:!0,token:this.token}}catch(e){throw this.error(e.message),e}}finally{this.#a=!1}}async solveChallenges(e){const r=e.length;let s=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")}})),n=([e,n],a)=>new Promise(((o,c)=>{const d=i[a];if(!d)return void c(new Error("Worker not available"));const h=setTimeout((()=>{try{d.terminate(),i[a]=new Worker(this.#e)}catch(e){console.error("[cap] Error terminating/recreating worker:",e)}c(new Error("Worker timeout"))}),3e4);d.onmessage=({data:t})=>{t.found&&(clearTimeout(h),s++,this.dispatchEvent("progress",{progress:Math.round(s/r*100)}),o([e,n,t.nonce]))},d.onerror=e=>{clearTimeout(h),this.error(`Error in worker: ${e.message||e}`),c(e)},d.postMessage({salt:e,target:n,wasmUrl:window.CAP_CUSTOM_WASM_URL||`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${t}/browser/cap_wasm.min.js`})})),a=[];try{for(let t=0;t<e.length;t+=this.#r){const r=e.slice(t,Math.min(t+this.#r,e.length)),s=await Promise.all(r.map(((e,t)=>n(e,t))));a.push(...s)}}finally{i.forEach((e=>{if(e)try{e.terminate()}catch(e){console.error("[cap] Error terminating worker:",e)}}))}return a}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#r=!isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}createUI(){this.#i.classList.add("captcha"),this.#i.setAttribute("role","button"),this.#i.setAttribute("tabindex","0"),this.#i.setAttribute("disabled","true"),this.#i.innerHTML=`<div class="checkbox"></div><p>${this.getI18nText("initial-state","I'm a human")}</p><a href="https://capjs.js.org/" class="credits" target="_blank" rel="follow noopener"><span>Secured by&nbsp;</span>Cap</a>`,this.#s.innerHTML='<style>\n\n.captcha * {box-sizing:border-box;}\n\n.captcha{background-color:var(--cap-background,#fdfdfd);border:1px solid var(--cap-border-color,#dddddd8f);border-radius:var(--cap-border-radius,14px);\nuser-select:none;\n\nheight:var(--cap-widget-height, 30px);\n\nwidth: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%)}\n\n.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);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)}\n\n.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:10px;right:10px;font-size:var(--cap-credits-font-size,12px);color:var(--cap-color,#212121);opacity:var(--cap-opacity-hover,0.8)}.captcha .credits span{display:none;text-decoration:underline}.captcha .credits:hover span{display:inline-block}</style>',this.#s.appendChild(this.#i)}addEventListeners(){this.#i&&(this.#i.querySelector("a").addEventListener("click",(e=>{e.stopPropagation(),e.preventDefault(),window.open("https://capjs.js.org","_blank")})),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(),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);if(r)try{new Function("event",r).call(this,t)}catch(t){console.error(`[cap] Error executing ${e}:`,t)}}error(e="Unknown error"){console.error("[cap] Error:",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const r=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(r)}reset(){this.#t&&(clearTimeout(this.#t),this.#t=null),this.dispatchEvent("reset"),this.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.#s&&(this.#s.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){let r=t||document.createElement("cap-widget");if(Object.entries(e).forEach((([e,t])=>{r.setAttribute(e,t)})),!e.apiEndpoint)throw r.remove(),new Error("Missing API endpoint");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))}}e=`(() => {${function(){if("object"!=typeof WebAssembly||"function"!=typeof WebAssembly?.instantiate)return self.onmessage=async({data:{salt:e,target:t}})=>{let r=0;let s=0;const i=new TextEncoder,n=new Uint8Array(t.length/2);for(let e=0;e<n.length;e++)n[e]=parseInt(t.substring(2*e,2*e+2),16);const a=n.length;for(;;)try{for(let t=0;t<5e4;t++){const t=e+r,s=i.encode(t),o=await crypto.subtle.digest("SHA-256",s),c=new Uint8Array(o,0,a);let d=!0;for(let e=0;e<a;e++)if(c[e]!==n[e]){d=!1;break}if(d)return void self.postMessage({nonce:r,found:!0});r++}s+=5e4}catch(e){return console.error("[cap] fallback worker error",e),void self.postMessage({found:!1,error:e.message})}},console.warn("[cap] WebAssembly is not supported, falling back to alternative solver.");let e,t;self.onmessage=async({data:{salt:r,target:s,wasmUrl:i}})=>{e!==i&&(e=i,await import(i).then((e=>e.default().then((r=>{t=(r&&r.exports?r.exports:e).solve_pow})))).catch((e=>{console.error("[cap] using fallback solver due to error:",e)})));try{const e=performance.now(),i=t(r,s),n=performance.now();self.postMessage({nonce:Number(i),found:!0,durationMs:(n-e).toFixed(2)})}catch(e){console.error("[cap] solver error",e),self.postMessage({found:!1,error:e.message||String(e)})}},self.onerror=e=>{self.postMessage({found:!1,error:`Worker error: ${e.message||e}`})}}.toString().replace(/^function\s*\([^\)]*\)\s*{|\}$/g,"").trim()}})()`,window.Cap=i,customElements.get("cap-widget")?console.warn("The cap-widget element has already been defined. Skipping re-defining it."):customElements.define("cap-widget",s),"object"==typeof exports&&"undefined"!=typeof module?module.exports=i:"function"==typeof define&&define.amd&&define([],(function(){return i})),"undefined"!=typeof exports&&(exports.default=i)}();
1
+ !(function () {
2
+ let e;
3
+ const t = "0.0.5",
4
+ r = function () {
5
+ return window?.CAP_CUSTOM_FETCH ? window.CAP_CUSTOM_FETCH(...arguments) : fetch(...arguments);
6
+ };
7
+ window.CAP_CUSTOM_WASM_URL ||
8
+ [
9
+ `https://cdn.jsdelivr.net/npm/@cap.js/wasm@${t}/browser/cap_wasm.min.js`,
10
+ `https://cdn.jsdelivr.net/npm/@cap.js/wasm@${t}/browser/cap_wasm_bg.wasm`,
11
+ ].forEach((e) => {
12
+ const t = document.createElement("link");
13
+ ((t.rel = "prefetch"),
14
+ (t.href = e),
15
+ (t.as = e.endsWith(".wasm") ? "fetch" : "script"),
16
+ document.head.appendChild(t));
17
+ });
18
+ class s extends HTMLElement {
19
+ #e = "";
20
+ #t = null;
21
+ #r = navigator.hardwareConcurrency || 8;
22
+ token = null;
23
+ #s;
24
+ #i;
25
+ #n;
26
+ #a = !1;
27
+ #o;
28
+ getI18nText(e, t) {
29
+ return this.getAttribute(`data-cap-i18n-${e}`) || t;
30
+ }
31
+ static get observedAttributes() {
32
+ return [
33
+ "onsolve",
34
+ "onprogress",
35
+ "onreset",
36
+ "onerror",
37
+ "data-cap-worker-count",
38
+ "data-cap-i18n-initial-state",
39
+ "[cap]",
40
+ ];
41
+ }
42
+ constructor() {
43
+ (super(),
44
+ this.#o &&
45
+ this.#o.forEach((e, t) => {
46
+ this.removeEventListener(t.slice(2), e);
47
+ }),
48
+ (this.#o = new Map()),
49
+ (this.boundHandleProgress = this.handleProgress.bind(this)),
50
+ (this.boundHandleSolve = this.handleSolve.bind(this)),
51
+ (this.boundHandleError = this.handleError.bind(this)),
52
+ (this.boundHandleReset = this.handleReset.bind(this)));
53
+ }
54
+ initialize() {
55
+ this.#e = URL.createObjectURL(new Blob([e], { type: "application/javascript" }));
56
+ }
57
+ attributeChangedCallback(e, t, r) {
58
+ if (e.startsWith("on")) {
59
+ const t = e.slice(2),
60
+ s = this.#o.get(e);
61
+ if ((s && this.removeEventListener(t, s), r)) {
62
+ const r = (t) => {
63
+ const r = this.getAttribute(e);
64
+ "function" == typeof window[r] && window[r].call(this, t);
65
+ };
66
+ (this.#o.set(e, r), this.addEventListener(t, r));
67
+ }
68
+ }
69
+ ("data-cap-worker-count" === e && this.setWorkersCount(parseInt(r)),
70
+ "data-cap-i18n-initial-state" === e &&
71
+ this.#i &&
72
+ this.#i?.querySelector("p")?.innerText &&
73
+ (this.#i.querySelector("p").innerText = this.getI18nText(
74
+ "initial-state",
75
+ "I'm a human",
76
+ )));
77
+ }
78
+ async connectedCallback() {
79
+ ((this.#n = this),
80
+ (this.#s = this.attachShadow({ mode: "open" })),
81
+ (this.#i = document.createElement("div")),
82
+ this.createUI(),
83
+ this.addEventListeners(),
84
+ await this.initialize(),
85
+ this.#i.removeAttribute("disabled"));
86
+ const e = this.getAttribute("data-cap-worker-count"),
87
+ t = e ? parseInt(e, 10) : null;
88
+ this.setWorkersCount(t || navigator.hardwareConcurrency || 8);
89
+ const r = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
90
+ this.#n.innerHTML = `<input type="hidden" name="${r}">`;
91
+ }
92
+ async solve() {
93
+ if (!this.#a)
94
+ try {
95
+ ((this.#a = !0),
96
+ this.updateUI("verifying", this.getI18nText("verifying-label", "Verifying..."), !0),
97
+ this.dispatchEvent("progress", { progress: 0 }));
98
+ try {
99
+ const e = this.getAttribute("data-cap-api-endpoint");
100
+ if (!e) throw new Error("Missing API endpoint");
101
+ const { challenge: t, token: s } = await (
102
+ await r(`${e}challenge`, { method: "POST" })
103
+ ).json(),
104
+ i = await this.solveChallenges(t),
105
+ n = await (
106
+ await r(`${e}redeem`, {
107
+ method: "POST",
108
+ body: JSON.stringify({ token: s, solutions: i }),
109
+ headers: { "Content-Type": "application/json" },
110
+ })
111
+ ).json();
112
+ if ((this.dispatchEvent("progress", { progress: 100 }), !n.success))
113
+ throw new Error("Invalid solution");
114
+ const a = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
115
+ (this.querySelector(`input[name='${a}']`) &&
116
+ (this.querySelector(`input[name='${a}']`).value = n.token),
117
+ this.dispatchEvent("solve", { token: n.token }),
118
+ (this.token = n.token),
119
+ this.#t && clearTimeout(this.#t));
120
+ const o = new Date(n.expires).getTime() - Date.now();
121
+ return (
122
+ o > 0 && o < 864e5
123
+ ? (this.#t = setTimeout(() => this.reset(), o))
124
+ : this.error("Invalid expiration time"),
125
+ { success: !0, token: this.token }
126
+ );
127
+ } catch (e) {
128
+ throw (this.error(e.message), e);
129
+ }
130
+ } finally {
131
+ this.#a = !1;
132
+ }
133
+ }
134
+ async solveChallenges(e) {
135
+ const r = e.length;
136
+ let s = 0;
137
+ const i = Array(this.#r)
138
+ .fill(null)
139
+ .map(() => {
140
+ try {
141
+ return new Worker(this.#e);
142
+ } catch (e) {
143
+ throw (
144
+ console.error("[cap] Failed to create worker:", e),
145
+ new Error("Worker creation failed")
146
+ );
147
+ }
148
+ }),
149
+ n = ([e, n], a) =>
150
+ new Promise((o, c) => {
151
+ const d = i[a];
152
+ if (!d) return void c(new Error("Worker not available"));
153
+ const h = setTimeout(
154
+ () => {
155
+ try {
156
+ (d.terminate(), (i[a] = new Worker(this.#e)));
157
+ } catch (e) {
158
+ console.error("[cap] Error terminating/recreating worker:", e);
159
+ }
160
+ c(new Error("Worker timeout"));
161
+ },
162
+ 3e4,
163
+ );
164
+ ((d.onmessage = ({ data: t }) => {
165
+ t.found &&
166
+ (clearTimeout(h),
167
+ s++,
168
+ this.dispatchEvent("progress", { progress: Math.round((s / r) * 100) }),
169
+ o([e, n, t.nonce]));
170
+ }),
171
+ (d.onerror = (e) => {
172
+ (clearTimeout(h), this.error(`Error in worker: ${e.message || e}`), c(e));
173
+ }),
174
+ d.postMessage({
175
+ salt: e,
176
+ target: n,
177
+ wasmUrl:
178
+ window.CAP_CUSTOM_WASM_URL ||
179
+ `https://cdn.jsdelivr.net/npm/@cap.js/wasm@${t}/browser/cap_wasm.min.js`,
180
+ }));
181
+ }),
182
+ a = [];
183
+ try {
184
+ for (let t = 0; t < e.length; t += this.#r) {
185
+ const r = e.slice(t, Math.min(t + this.#r, e.length)),
186
+ s = await Promise.all(r.map((e, t) => n(e, t)));
187
+ a.push(...s);
188
+ }
189
+ } finally {
190
+ i.forEach((e) => {
191
+ if (e)
192
+ try {
193
+ e.terminate();
194
+ } catch (e) {
195
+ console.error("[cap] Error terminating worker:", e);
196
+ }
197
+ });
198
+ }
199
+ return a;
200
+ }
201
+ setWorkersCount(e) {
202
+ const t = parseInt(e, 10),
203
+ r = Math.min(navigator.hardwareConcurrency || 8, 16);
204
+ this.#r = !isNaN(t) && t > 0 && t <= r ? t : navigator.hardwareConcurrency || 8;
205
+ }
206
+ createUI() {
207
+ (this.#i.classList.add("captcha"),
208
+ this.#i.setAttribute("role", "button"),
209
+ this.#i.setAttribute("tabindex", "0"),
210
+ this.#i.setAttribute("disabled", "true"),
211
+ (this.#i.innerHTML = `<div class="checkbox"></div><p>${this.getI18nText("initial-state", "I'm a human")}</p><a href="https://capjs.js.org/" class="credits" target="_blank" rel="follow noopener"><span>Secured by&nbsp;</span>Cap</a>`),
212
+ (this.#s.innerHTML =
213
+ '<style>\n\n.captcha * {box-sizing:border-box;}\n\n.captcha{background-color:var(--cap-background,#fdfdfd);border:1px solid var(--cap-border-color,#dddddd8f);border-radius:var(--cap-border-radius,14px);\nuser-select:none;\n\nheight:var(--cap-widget-height, 30px);\n\nwidth: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%)}\n\n.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);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)}\n\n.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:10px;right:10px;font-size:var(--cap-credits-font-size,12px);color:var(--cap-color,#212121);opacity:var(--cap-opacity-hover,0.8)}.captcha .credits span{display:none;text-decoration:underline}.captcha .credits:hover span{display:inline-block}</style>'),
214
+ this.#s.appendChild(this.#i));
215
+ }
216
+ addEventListeners() {
217
+ this.#i &&
218
+ (this.#i.querySelector("a").addEventListener("click", (e) => {
219
+ (e.stopPropagation(), e.preventDefault(), window.open("https://capjs.js.org", "_blank"));
220
+ }),
221
+ this.#i.addEventListener("click", () => {
222
+ this.#i.hasAttribute("disabled") || this.solve();
223
+ }),
224
+ this.#i.addEventListener("keydown", (e) => {
225
+ ("Enter" !== e.key && " " !== e.key) ||
226
+ this.#i.hasAttribute("disabled") ||
227
+ (e.preventDefault(), this.solve());
228
+ }),
229
+ this.addEventListener("progress", this.boundHandleProgress),
230
+ this.addEventListener("solve", this.boundHandleSolve),
231
+ this.addEventListener("error", this.boundHandleError),
232
+ this.addEventListener("reset", this.boundHandleReset));
233
+ }
234
+ updateUI(e, t, r = !1) {
235
+ this.#i &&
236
+ (this.#i.setAttribute("data-state", e),
237
+ (this.#i.querySelector("p").innerText = t),
238
+ r ? this.#i.setAttribute("disabled", "true") : this.#i.removeAttribute("disabled"));
239
+ }
240
+ handleProgress(e) {
241
+ if (!this.#i) return;
242
+ const t = this.#i.querySelector("p"),
243
+ r = this.#i.querySelector(".checkbox");
244
+ (t &&
245
+ r &&
246
+ (r.style.setProperty("--progress", `${e.detail.progress}%`),
247
+ (t.innerText = `${this.getI18nText("verifying-label", "Verifying...")} ${e.detail.progress}%`)),
248
+ this.executeAttributeCode("onprogress", e));
249
+ }
250
+ handleSolve(e) {
251
+ (this.updateUI("done", this.getI18nText("solved-label", "You're a human"), !0),
252
+ this.executeAttributeCode("onsolve", e));
253
+ }
254
+ handleError(e) {
255
+ (this.updateUI("error", this.getI18nText("error-label", "Error. Try again.")),
256
+ this.executeAttributeCode("onerror", e));
257
+ }
258
+ handleReset(e) {
259
+ (this.updateUI("", this.getI18nText("initial-state", "I'm a human")),
260
+ this.executeAttributeCode("onreset", e));
261
+ }
262
+ executeAttributeCode(e, t) {
263
+ const r = this.getAttribute(e);
264
+ if (r)
265
+ try {
266
+ new Function("event", r).call(this, t);
267
+ } catch (t) {
268
+ console.error(`[cap] Error executing ${e}:`, t);
269
+ }
270
+ }
271
+ error(e = "Unknown error") {
272
+ (console.error("[cap] Error:", e), this.dispatchEvent("error", { isCap: !0, message: e }));
273
+ }
274
+ dispatchEvent(e, t = {}) {
275
+ const r = new CustomEvent(e, { bubbles: !0, composed: !0, detail: t });
276
+ super.dispatchEvent(r);
277
+ }
278
+ reset() {
279
+ (this.#t && (clearTimeout(this.#t), (this.#t = null)),
280
+ this.dispatchEvent("reset"),
281
+ (this.token = null));
282
+ const e = this.getAttribute("data-cap-hidden-field-name") || "cap-token";
283
+ this.querySelector(`input[name='${e}']`) &&
284
+ (this.querySelector(`input[name='${e}']`).value = "");
285
+ }
286
+ get tokenValue() {
287
+ return this.token;
288
+ }
289
+ disconnectedCallback() {
290
+ (this.removeEventListener("progress", this.boundHandleProgress),
291
+ this.removeEventListener("solve", this.boundHandleSolve),
292
+ this.removeEventListener("error", this.boundHandleError),
293
+ this.removeEventListener("reset", this.boundHandleReset),
294
+ this.#o.forEach((e, t) => {
295
+ this.removeEventListener(t.slice(2), e);
296
+ }),
297
+ this.#o.clear(),
298
+ this.#s && (this.#s.innerHTML = ""),
299
+ this.reset(),
300
+ this.cleanup());
301
+ }
302
+ cleanup() {
303
+ (this.#t && (clearTimeout(this.#t), (this.#t = null)),
304
+ this.#e && (URL.revokeObjectURL(this.#e), (this.#e = "")));
305
+ }
306
+ }
307
+ class i {
308
+ constructor(e = {}, t) {
309
+ let r = t || document.createElement("cap-widget");
310
+ if (
311
+ (Object.entries(e).forEach(([e, t]) => {
312
+ r.setAttribute(e, t);
313
+ }),
314
+ !e.apiEndpoint)
315
+ )
316
+ throw (r.remove(), new Error("Missing API endpoint"));
317
+ (r.setAttribute("data-cap-api-endpoint", e.apiEndpoint),
318
+ (this.widget = r),
319
+ (this.solve = this.widget.solve.bind(this.widget)),
320
+ (this.reset = this.widget.reset.bind(this.widget)),
321
+ (this.addEventListener = this.widget.addEventListener.bind(this.widget)),
322
+ Object.defineProperty(this, "token", {
323
+ get: () => r.token,
324
+ configurable: !0,
325
+ enumerable: !0,
326
+ }),
327
+ t || ((r.style.display = "none"), document.documentElement.appendChild(r)));
328
+ }
329
+ }
330
+ ((e = `(() => {${function () {
331
+ if ("object" != typeof WebAssembly || "function" != typeof WebAssembly?.instantiate)
332
+ return (
333
+ (self.onmessage = async ({ data: { salt: e, target: t } }) => {
334
+ let r = 0;
335
+ let s = 0;
336
+ const i = new TextEncoder(),
337
+ n = new Uint8Array(t.length / 2);
338
+ for (let e = 0; e < n.length; e++) n[e] = parseInt(t.substring(2 * e, 2 * e + 2), 16);
339
+ const a = n.length;
340
+ for (;;)
341
+ try {
342
+ for (let t = 0; t < 5e4; t++) {
343
+ const t = e + r,
344
+ s = i.encode(t),
345
+ o = await crypto.subtle.digest("SHA-256", s),
346
+ c = new Uint8Array(o, 0, a);
347
+ let d = !0;
348
+ for (let e = 0; e < a; e++)
349
+ if (c[e] !== n[e]) {
350
+ d = !1;
351
+ break;
352
+ }
353
+ if (d) return void self.postMessage({ nonce: r, found: !0 });
354
+ r++;
355
+ }
356
+ s += 5e4;
357
+ } catch (e) {
358
+ return (
359
+ console.error("[cap] fallback worker error", e),
360
+ void self.postMessage({ found: !1, error: e.message })
361
+ );
362
+ }
363
+ }),
364
+ console.warn("[cap] WebAssembly is not supported, falling back to alternative solver.")
365
+ );
366
+ let e, t;
367
+ ((self.onmessage = async ({ data: { salt: r, target: s, wasmUrl: i } }) => {
368
+ e !== i &&
369
+ ((e = i),
370
+ await import(i)
371
+ .then((e) =>
372
+ e.default().then((r) => {
373
+ t = (r && r.exports ? r.exports : e).solve_pow;
374
+ }),
375
+ )
376
+ .catch((e) => {
377
+ console.error("[cap] using fallback solver due to error:", e);
378
+ }));
379
+ try {
380
+ const e = performance.now(),
381
+ i = t(r, s),
382
+ n = performance.now();
383
+ self.postMessage({ nonce: Number(i), found: !0, durationMs: (n - e).toFixed(2) });
384
+ } catch (e) {
385
+ (console.error("[cap] solver error", e),
386
+ self.postMessage({ found: !1, error: e.message || String(e) }));
387
+ }
388
+ }),
389
+ (self.onerror = (e) => {
390
+ self.postMessage({ found: !1, error: `Worker error: ${e.message || e}` });
391
+ }));
392
+ }
393
+ .toString()
394
+ .replace(/^function\s*\([^\)]*\)\s*{|\}$/g, "")
395
+ .trim()}})()`),
396
+ (window.Cap = i),
397
+ customElements.get("cap-widget")
398
+ ? console.warn("The cap-widget element has already been defined. Skipping re-defining it.")
399
+ : customElements.define("cap-widget", s),
400
+ "object" == typeof exports && "undefined" != typeof module
401
+ ? (module.exports = i)
402
+ : "function" == typeof define &&
403
+ define.amd &&
404
+ define([], function () {
405
+ return i;
406
+ }),
407
+ "undefined" != typeof exports && (exports.default = i));
408
+ })();
package/cap.d.ts CHANGED
@@ -70,40 +70,16 @@ interface CapWidget extends HTMLElement {
70
70
  reset(): void;
71
71
  setWorkersCount(workers: number): void;
72
72
 
73
- addEventListener(
74
- type: "progress",
75
- listener: (event: CapProgressEvent) => void
76
- ): void;
77
- addEventListener(
78
- type: "solve",
79
- listener: (event: CapSolveEvent) => void
80
- ): void;
81
- addEventListener(
82
- type: "error",
83
- listener: (event: CapErrorEvent) => void
84
- ): void;
85
- addEventListener(
86
- type: "reset",
87
- listener: (event: CapResetEvent) => void
88
- ): void;
73
+ addEventListener(type: "progress", listener: (event: CapProgressEvent) => void): void;
74
+ addEventListener(type: "solve", listener: (event: CapSolveEvent) => void): void;
75
+ addEventListener(type: "error", listener: (event: CapErrorEvent) => void): void;
76
+ addEventListener(type: "reset", listener: (event: CapResetEvent) => void): void;
89
77
  addEventListener(type: string, listener: EventListener): void;
90
78
 
91
- removeEventListener(
92
- type: "progress",
93
- listener: (event: CapProgressEvent) => void
94
- ): void;
95
- removeEventListener(
96
- type: "solve",
97
- listener: (event: CapSolveEvent) => void
98
- ): void;
99
- removeEventListener(
100
- type: "error",
101
- listener: (event: CapErrorEvent) => void
102
- ): void;
103
- removeEventListener(
104
- type: "reset",
105
- listener: (event: CapResetEvent) => void
106
- ): void;
79
+ removeEventListener(type: "progress", listener: (event: CapProgressEvent) => void): void;
80
+ removeEventListener(type: "solve", listener: (event: CapSolveEvent) => void): void;
81
+ removeEventListener(type: "error", listener: (event: CapErrorEvent) => void): void;
82
+ removeEventListener(type: "reset", listener: (event: CapResetEvent) => void): void;
107
83
  removeEventListener(type: string, listener: EventListener): void;
108
84
  }
109
85
 
@@ -116,22 +92,10 @@ declare class Cap {
116
92
  solve(): Promise<SolveResult>;
117
93
  reset(): void;
118
94
 
119
- addEventListener(
120
- type: "progress",
121
- listener: (event: CapProgressEvent) => void
122
- ): void;
123
- addEventListener(
124
- type: "solve",
125
- listener: (event: CapSolveEvent) => void
126
- ): void;
127
- addEventListener(
128
- type: "error",
129
- listener: (event: CapErrorEvent) => void
130
- ): void;
131
- addEventListener(
132
- type: "reset",
133
- listener: (event: CapResetEvent) => void
134
- ): void;
95
+ addEventListener(type: "progress", listener: (event: CapProgressEvent) => void): void;
96
+ addEventListener(type: "solve", listener: (event: CapSolveEvent) => void): void;
97
+ addEventListener(type: "error", listener: (event: CapErrorEvent) => void): void;
98
+ addEventListener(type: "reset", listener: (event: CapResetEvent) => void): void;
135
99
  addEventListener(type: string, listener: EventListener): void;
136
100
  }
137
101