1 line
13 KiB
JavaScript
1 line
13 KiB
JavaScript
"use strict"; !function () { let e; const t = (e, t = 1e4) => new Promise(((r, s) => { const a = setTimeout((() => { s(new Error("Initialize timeout")) }), t), i = () => { e() ? (clearTimeout(a), r()) : setTimeout(i, 500) }; i() })); class r extends HTMLElement { #e = ""; #t = null; #r = navigator.hardwareConcurrency || 8; #s = null; #a; #i; #n; #o = !1; #c; static get observedAttributes() { return ["onsolve", "onprogress", "onreset", "onerror", "workers"] } constructor() { super(), this.#c && this.#c.forEach(((e, t) => { this.removeEventListener(t.slice(2), e) })), this.#c = new Map, this.boundHandleProgress = this.handleProgress.bind(this), this.boundHandleSolve = this.handleSolve.bind(this), this.boundHandleError = this.handleError.bind(this), this.boundHandleReset = this.handleReset.bind(this) } async initialize() { this.#e && URL.revokeObjectURL(this.#e); try { await t((() => !!e)), this.#e = URL.createObjectURL(new Blob([e], { type: "application/javascript" })) } catch (e) { throw this.error("Failed to initialize worker"), e } } attributeChangedCallback(e, t, r) { if (e.startsWith("on")) { const t = e.slice(2), s = this.#c.get(e); if (s && this.removeEventListener(t, s), r) { const r = t => { const r = this.getAttribute(e); "function" == typeof window[r] && window[r].call(this, t) }; this.#c.set(e, r), this.addEventListener(t, r) } } } async connectedCallback() { this.#n = this, this.#a = this.attachShadow({ mode: "open" }), this.#i = document.createElement("div"), this.createUI(), this.addEventListeners(), await this.initialize(), this.#i.removeAttribute("disabled"); const e = this.getAttribute("data-cap-worker-count"); this.setWorkersCount(parseInt(e) ? parseInt(e, 10) : navigator.hardwareConcurrency || 8), this.#n.innerHTML = '<input type="hidden" name="cap-token">' } async solve() { if (!this.#o) try { this.#o = !0, this.updateUI("verifying", "Verifying...", !0), await t((() => !!this.#e)), this.dispatchEvent("progress", { progress: 0 }); try { const e = this.getAttribute("data-cap-api-endpoint"); if (!e) throw new Error("Missing API endpoint"); const { challenge: t, target: r, token: s } = await (await fetch(`${e}challenge`, { method: "POST" })).json(), a = await this.solveChallenges(t), i = await (await fetch(`${e}redeem`, { method: "POST", body: JSON.stringify({ token: s, solutions: a }), headers: { "Content-Type": "application/json" } })).json(); if (!i.success) throw new Error("Invalid solution"); this.dispatchEvent("progress", { progress: 100 }), this.dispatchEvent("solve", { token: i.token }), this.#s = i.token, this.querySelector("input[name='cap-token']") && (this.querySelector("input[name='cap-token']").value = i.token), this.#t && clearTimeout(this.#t); const n = new Date(i.expires).getTime() - Date.now(); return n > 0 && n < 864e5 ? this.#t = setTimeout((() => this.reset()), n) : this.error("Invalid expiration time"), { success: !0, token: this.#s } } catch (e) { throw this.error(e.message), e } } finally { this.#o = !1 } } async solveChallenges(e) { const t = e.length; let r = 0; const s = Array(this.#r).fill(null).map((() => new Worker(this.#e))), a = ([e, a], i) => new Promise(((n, o) => { const c = s[i], d = setTimeout((() => { c.terminate(), s[i] = new Worker(this.#e), o(new Error("Worker timeout")) }), 3e4); c.onmessage = ({ data: s }) => { s.found && (clearTimeout(d), r++, this.dispatchEvent("progress", { progress: Math.round(r / t * 100) }), n([e, a, s.nonce])) }, c.onerror = e => { clearTimeout(d), this.error(`Error in worker: ${e}`), o(e) }, c.postMessage({ salt: e, target: a }) })), i = []; 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) => a(e, t)))); i.push(...s) } } finally { s.forEach((e => e.terminate())) } return i } setWorkersCount(e) { const t = parseInt(e, 10), r = Math.min(navigator.hardwareConcurrency || 8, 16); this.#r = !isNaN(t) && t > 0 && t <= r ? t : navigator.hardwareConcurrency || 8 } createUI() { this.#i.classList.add("captcha"), this.#i.setAttribute("role", "button"), this.#i.setAttribute("tabindex", "0"), this.#i.setAttribute("disabled", "true"), this.#i.innerHTML = '<div class="checkbox"></div><p>I\'m a human</p><a href="https://capjs.js.org/" class="credits" target="_blank" rel="follow noopener"><span>Secured by </span>Cap</a>', this.#a.innerHTML = '<style>.captcha{background-color:var(--cap-background);border:1px solid var(--cap-border-color);border-radius:var(--cap-border-radius);width:var(--cap-widget-width);display:flex;align-items:center;padding:var(--cap-widget-padding);gap:var(--cap-gap);cursor:pointer;transition:filter var(--cap-transition-duration),transform var(--cap-transition-duration);position:relative;-webkit-tap-highlight-color:rgba(255,255,255,0);overflow:hidden;color:var(--cap-color)}.captcha:hover{filter:var(--cap-hover-filter)}.captcha:not([disabled]):active{transform:scale(var(--cap-active-scale))}.checkbox{width:var(--cap-checkbox-size);height:var(--cap-checkbox-size);border:var(--cap-checkbox-border);border-radius:var(--cap-checkbox-border-radius);background-color:var(--cap-checkbox-background);transition:opacity var(--cap-transition-duration);margin-top:var(--cap-checkbox-margin);margin-bottom:var(--cap-checkbox-margin)}.captcha *{font-family:var(--cap-font)}.captcha p{margin:0;font-weight:500;font-size:15px;user-select:none;transition:opacity var(--cap-transition-duration)}.captcha[data-state=verifying] .checkbox{background: none;display:flex;align-items:center;justify-content:center;transform: scale(1.1);border: none;border-radius: 50%;background: conic-gradient(var(--cap-spinner-color) 0%, var(--cap-spinner-color) var(--progress, 0%), var(--cap-spinner-background-color) var(--progress, 0%), var(--cap-spinner-background-color) 100%);position: relative;}.captcha[data-state=verifying] .checkbox::after {content: "";background-color: var(--cap-background);width: calc(100% - var(--cap-spinner-thickness));height: calc(100% - var(--cap-spinner-thickness));border-radius: 50%;margin:calc(var(--cap-spinner-thickness) / 2)}.captcha[data-state=done] .checkbox{border:1px solid transparent;background-image:var(--cap-checkmark);background-size:cover}.captcha[data-state=error] .checkbox{border:1px solid transparent;background-image:var(--cap-error-cross);background-size:cover}.captcha[disabled]{\ncursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{position:absolute;bottom:10px;right:10px;font-size:var(--cap-credits-font-size);color:var(--cap-color);opacity:var(--cap-opacity-hover)}.captcha .credits span{display:none;text-decoration:underline}.captcha .credits:hover span{display:inline-block}</style>', this.#a.appendChild(this.#i) } addEventListeners() { this.#i.querySelector("a").addEventListener("click", (e => { e.stopPropagation(), e.preventDefault(), window.open("#", "_blank") })), this.#i.addEventListener("click", (() => { this.#i.hasAttribute("disabled") || this.solve() })), this.addEventListener("progress", this.boundHandleProgress), this.addEventListener("solve", this.boundHandleSolve), this.addEventListener("error", this.boundHandleError), this.addEventListener("reset", this.boundHandleReset) } updateUI(e, t, r = !1) { this.#i.setAttribute("data-state", e), this.#i.querySelector("p").innerText = t, r ? this.#i.setAttribute("disabled", "true") : this.#i.removeAttribute("disabled") } handleProgress(e) { const t = this.#i.querySelector("p"); t && (this.#i.querySelector(".checkbox").style.setProperty("--progress", `${e.detail.progress}%`), t.innerText = `Verifying... ${e.detail.progress}%`), this.executeAttributeCode("onprogress", e) } handleSolve(e) { this.updateUI("done", "You're a human", !0), this.executeAttributeCode("onsolve", e) } handleError(e) { this.updateUI("error", "Error. Try again."), this.executeAttributeCode("onerror", e) } handleReset(e) { this.updateUI("", "I'm a human"), this.executeAttributeCode("onreset", e) } executeAttributeCode(e, t) { const r = this.getAttribute(e); if (!r) return; new Function("event", r).call(this, t) } error(e = "Unknown error") { console.error("[Cap] Error:", e), this.dispatchEvent("error", { isCap: !0, message: e }) } dispatchEvent(e, t = {}) { const r = new CustomEvent(e, { bubbles: !0, composed: !0, detail: t }); super.dispatchEvent(r) } reset() { this.#t && (clearTimeout(this.#t), this.#t = null), this.dispatchEvent("reset"), this.#s = null, this.querySelector("input[name='cap-token']") && (this.querySelector("input[name='cap-token']").value = "") } get token() { return this.#s } disconnectedCallback() { this.removeEventListener("progress", this.boundHandleProgress), this.removeEventListener("solve", this.boundHandleSolve), this.removeEventListener("error", this.boundHandleError), this.removeEventListener("reset", this.boundHandleReset), this.#c.forEach(((e, t) => { this.removeEventListener(t.slice(2), e) })), this.#c.clear(), this.#a && (this.#a.innerHTML = ""), this.reset(), this.cleanup() } cleanup() { this.#t && (clearTimeout(this.#t), this.#t = null), this.#e && (URL.revokeObjectURL(this.#e), this.#e = "") } } class s { constructor(e = {}, t) { let r = t || document.createElement("cap-widget"); if (Object.entries(e).forEach((([e, t]) => { r.setAttribute(e, t) })), !e.apiEndpoint) throw r.remove(), new Error("Missing API endpoint"); console.log(e), r.setAttribute("data-cap-api-endpoint", e.apiEndpoint), this.widget = r, this.solve = 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.getToken(), configurable: !0, enumerable: !0 }), t || (r.style.display = "none", document.documentElement.appendChild(r)) } } const a = new CSSStyleSheet; a.replaceSync('html{--cap-font:system,-apple-system,"BlinkMacSystemFont",".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande","Ubuntu","arial",sans-serif;--cap-color:#212121;--cap-background:#fdfdfd;--cap-border-color:#dddddd8f;--cap-border-radius:14px;--cap-checkbox-border:1px solid #aaaaaad1;--cap-checkbox-border-radius:6px;--cap-checkbox-background:#fafafa91;--cap-widget-width:240px;--cap-widget-padding:14px;--cap-checkbox-size:24px;--cap-checkbox-margin:2px;--cap-transition-duration:0.2s;--cap-gap:15px;--cap-opacity-hover:0.8;--cap-hover-filter:brightness(97%);--cap-active-scale:0.98;--cap-credits-font-size:12px;--cap-spinner-color:black;--cap-spinner-background-color:#eee;--cap-error-cross:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'96\' height=\'96\' viewBox=\'0 0 24 24\'%3E%3Cpath fill=\'%23f55b50\' d=\'M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8\'/%3E%3C/svg%3E");--cap-checkmark:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E");--cap-spinner-thickness:5px;}'), document.adoptedStyleSheets.push(a); const i = function () { let e; self.onmessage = async ({ data: { salt: t, target: r } }) => { let s = 0; const a = 5e4; let i = 0; const n = new TextEncoder; if ("object" != typeof WebAssembly || "function" != typeof WebAssembly.instantiate) { console.log("[cap] WASM not enabled, falling back to crypto.subtle\nThis is significanty slower than the WASM implementation"); const e = new Uint8Array(r.length / 2); for (let t = 0; t < e.length; t++)e[t] = parseInt(r.substring(2 * t, 2 * t + 2), 16); const o = e.length; for (; ;)try { for (let r = 0; r < a; r++) { const r = t + s, a = n.encode(r), i = await crypto.subtle.digest("SHA-256", a), c = new Uint8Array(i, 0, o); let d = !0; for (let t = 0; t < o; t++)if (c[t] !== e[t]) { d = !1; break } if (d) return void self.postMessage({ nonce: s, found: !0 }); s++ } i += a } catch (e) { return console.error("[cap] fallback worker error", e), void self.postMessage({ found: !1, error: e.message }) } } e || (e = await hashwasm.createSHA256()); const o = new Uint8Array(128); for (; ;)try { for (let i = 0; i < a; i++) { const a = t + s.toString(), i = n.encode(a); o.set(i), e.init(), e.update(o.subarray(0, i.length)); if (e.digest("hex").startsWith(r)) return void self.postMessage({ nonce: s, found: !0 }); s++ } i += a } catch (e) { return void self.postMessage({ found: !1, error: e.message }) } } }; setTimeout((async function () { e = await (await fetch("https://cdn.jsdelivr.net/npm/@cap.js/widget/wasm-hashes.min.js")).text() + i.toString().replace(/^function\s*\([^\)]*\)\s*{|\}$/g, "").trim() }), 1), window.Cap = s, customElements.get("cap-widget") ? console.warn("The cap-widget element has already been defined. Skipping re-defining it.") : customElements.define("cap-widget", r), "object" == typeof exports && "undefined" != typeof module ? module.exports = s : "function" == typeof define && define.amd && define([], (function () { return s })), "undefined" != typeof exports && (exports.default = s) }(); |