UNPKG

@anilkumarthakur/vue3-recaptcha

Version:

A Vue 3 plugin for Google reCAPTCHA v2 (checkbox & invisible) and v3 integration

549 lines (548 loc) 16.1 kB
import { computed as C, createVNode as L, defineComponent as P, inject as K, onBeforeUnmount as _, onMounted as A, onUnmounted as q, readonly as R, ref as v, watch as z } from "vue"; var I = /* @__PURE__ */ Symbol("vue3-recaptcha"), U = "vue3-recaptcha-script", B = "https://www.google.com/recaptcha/api.js", k = null, S = !1; function V() { return S && typeof window < "u" && typeof window.grecaptcha < "u"; } function N() { return document.querySelector(`#${U}`); } function D() { const e = N(); e && e.remove(), k = null, S = !1; } function F(e) { const { siteKey: r, version: u, language: i, scriptUrl: t } = e; if (t) return t; const a = new URLSearchParams(); return u === "v3" ? a.set("render", r) : (a.set("render", "explicit"), typeof window < "u" && (window.___grecaptcha_cfg = window.___grecaptcha_cfg ?? {}, window.___grecaptcha_cfg.explicit = !0)), i && a.set("hl", i), `${B}?${a.toString()}`; } function T(e) { return k || (V() ? Promise.resolve() : (k = new Promise((r, u) => { const i = N(); if (i) { if (V()) { r(); return; } i.addEventListener("load", () => { S = !0, e.onLoad?.(), r(); }), i.addEventListener("error", () => { const a = /* @__PURE__ */ new Error("Failed to load reCAPTCHA script"); e.onError?.(a), u(a); }); return; } const t = document.createElement("script"); t.id = U, t.src = F(e), t.async = !0, t.defer = !0, t.onload = () => { e.version === "v3" ? window.grecaptcha.ready(() => { S = !0, e.onLoad?.(), r(); }) : (S = !0, e.onLoad?.(), r()); }, t.onerror = () => { const a = /* @__PURE__ */ new Error("Failed to load reCAPTCHA script"); k = null, e.onError?.(a), u(a); }, document.head.appendChild(t); }), k)); } function J() { return new Promise((e, r) => { if (V()) { e(); return; } const u = window.setTimeout(() => { window.clearInterval(i), r(/* @__PURE__ */ new Error("Timeout waiting for reCAPTCHA to load")); }, 1e4), i = window.setInterval(() => { typeof window.grecaptcha < "u" && (window.clearInterval(i), window.clearTimeout(u), typeof window.grecaptcha.ready == "function" ? window.grecaptcha.ready(() => { S = !0, e(); }) : (S = !0, e())); }, 100); }); } var W = /* @__PURE__ */ P({ name: "RecaptchaV2Checkbox", props: { siteKey: { type: String, default: void 0 }, theme: { type: String, default: "light" }, size: { type: String, default: "normal" }, tabindex: { type: Number, default: 0 }, modelValue: { type: String, default: "" } }, emits: { "update:modelValue": (e) => typeof e == "string", verify: (e) => typeof e == "string", expire: null, error: (e) => e instanceof Error, load: null }, setup(e, { emit: r, expose: u }) { const i = v(null), t = v(null), a = v(!1), c = K(I, void 0), o = C(() => e.siteKey ?? c?.siteKey ?? ""); o.value || console.error("[vue3-recaptcha] Site key is required for RecaptchaV2Checkbox"); function s(n) { r("update:modelValue", n), r("verify", n); } function m() { r("update:modelValue", ""), r("expire"); } function g(n) { r("update:modelValue", ""), r("error", n); } async function y() { const n = i.value, f = o.value; if (!(!n || !f)) try { V() || await T({ siteKey: f, version: "v2" }), await new Promise((h) => { const l = window.setInterval(() => { typeof window.grecaptcha?.render == "function" && (window.clearInterval(l), h()); }, 50); }), t.value = window.grecaptcha.render(n, { sitekey: f, theme: e.theme, size: e.size, tabindex: e.tabindex, callback: s, "expired-callback": m, "error-callback": () => { g(/* @__PURE__ */ new Error("reCAPTCHA verification error")); } }), a.value = !0, r("load"); } catch (h) { g(h instanceof Error ? h : new Error(String(h))); } } function d() { const n = t.value; n !== null && (window.grecaptcha.reset(n), r("update:modelValue", "")); } function p() { const n = t.value; return n !== null ? window.grecaptcha.getResponse(n) : ""; } return z(() => [e.theme, e.size], () => { const n = i.value; a.value && n && (n.innerHTML = "", t.value = null, a.value = !1, y()); }), A(() => { y(); }), _(() => { t.value = null; }), u({ reset: d, getResponse: p, widgetId: t }), () => L("div", { ref: i, class: "vue3-recaptcha-v2-checkbox", style: { display: "inline-block" } }, null); } }), $ = /* @__PURE__ */ P({ name: "RecaptchaV2Invisible", props: { siteKey: { type: String, default: void 0 }, badge: { type: String, default: "bottomright" }, tabindex: { type: Number, default: 0 }, modelValue: { type: String, default: "" } }, emits: { "update:modelValue": (e) => typeof e == "string", verify: (e) => typeof e == "string", expire: null, error: (e) => e instanceof Error, load: null }, setup(e, { emit: r, expose: u }) { const i = v(null), t = v(null), a = v(!1), c = v(null), o = v(null), s = K(I, void 0), m = C(() => e.siteKey ?? s?.siteKey ?? ""); m.value || console.error("[vue3-recaptcha] Site key is required for RecaptchaV2Invisible"); function g(l) { r("update:modelValue", l), r("verify", l); const w = c.value; w && (c.value = null, o.value = null, w(l)); } function y() { r("update:modelValue", ""), r("expire"); } function d(l) { r("update:modelValue", ""), r("error", l); const w = o.value; w && (c.value = null, o.value = null, w(l)); } async function p() { const l = i.value, w = m.value; if (!(!l || !w)) try { V() || await T({ siteKey: w, version: "v2" }), await new Promise((E) => { const b = window.setInterval(() => { typeof window.grecaptcha?.render == "function" && (window.clearInterval(b), E()); }, 50); }), t.value = window.grecaptcha.render(l, { sitekey: w, size: "invisible", tabindex: e.tabindex, callback: g, "expired-callback": y, "error-callback": () => { d(/* @__PURE__ */ new Error("reCAPTCHA verification error")); } }), a.value = !0, r("load"); } catch (E) { d(E instanceof Error ? E : new Error(String(E))); } } async function n() { a.value || await p(); const l = t.value; if (l === null) throw new Error("[vue3-recaptcha] Widget not initialized"); return new Promise((w, E) => { const b = window.setTimeout(() => { const x = o.value; c.value = null, o.value = null, x?.(/* @__PURE__ */ new Error("[vue3-recaptcha] Verification timeout after 60 seconds")); }, 6e4); c.value = (x) => { window.clearTimeout(b), w(x); }, o.value = (x) => { window.clearTimeout(b), E(x); }; try { window.grecaptcha.execute(l); } catch (x) { window.clearTimeout(b), c.value = null, o.value = null, E(x instanceof Error ? x : new Error(String(x))); } }); } function f() { const l = t.value; l !== null && (window.grecaptcha.reset(l), r("update:modelValue", ""), c.value = null, o.value = null); } function h() { const l = t.value; return l !== null ? window.grecaptcha.getResponse(l) : ""; } return A(() => { p(); }), _(() => { t.value = null, c.value = null, o.value = null; }), u({ execute: n, reset: f, getResponse: h, widgetId: t }), () => L("div", { ref: i, class: "vue3-recaptcha-v2-invisible", style: { display: "none" } }, null); } }), H = /* @__PURE__ */ P({ name: "RecaptchaV3", props: { siteKey: { type: String, default: void 0 }, action: { type: String, default: "submit" }, badge: { type: String, default: "bottomright" }, hideBadge: { type: Boolean, default: !1 }, modelValue: { type: String, default: "" } }, emits: { "update:modelValue": (e) => typeof e == "string", verify: (e) => typeof e == "string", error: (e) => e instanceof Error, load: null }, setup(e, { emit: r, expose: u }) { const i = v(!1), t = v(!1), a = v(null); let c; const o = K(I, void 0), s = C(() => e.siteKey ?? o?.siteKey ?? ""); s.value || console.error("[vue3-recaptcha] Site key is required for RecaptchaV3"); function m() { if (typeof document > "u") return; const d = document.querySelector(".grecaptcha-badge"); d && (d.style.visibility = e.hideBadge ? "hidden" : "visible"); } async function g() { if (!(i.value || t.value)) { t.value = !0, a.value = null; try { await T({ siteKey: s.value, version: "v3", onLoad: () => { i.value = !0, r("load"); }, onError: (d) => { a.value = d, r("error", d); } }), i.value = !0; } catch (d) { const p = d instanceof Error ? d : new Error(String(d)); a.value = p, r("error", p); } finally { t.value = !1; } } } async function y(d) { if (i.value || await g(), typeof window.grecaptcha > "u") { const f = /* @__PURE__ */ new Error("[vue3-recaptcha] grecaptcha is not available"); throw r("error", f), f; } const p = s.value, n = d ?? e.action ?? "submit"; try { const f = await window.grecaptcha.execute(p, { action: n }); return r("update:modelValue", f), r("verify", f), c !== void 0 && clearTimeout(c), c = setTimeout(() => { r("update:modelValue", ""); }, 11e4), f; } catch (f) { const h = f instanceof Error ? f : new Error(String(f)); throw a.value = h, r("error", h), r("update:modelValue", ""), h; } } return z(() => e.hideBadge, m), A(() => { g().then(() => { m(); }); }), _(() => { c !== void 0 && clearTimeout(c); }), u({ load: g, loadRecaptcha: g, execute: y, isLoaded: i, isLoading: t, error: a }), () => L("div", { class: "vue3-recaptcha-v3", style: { display: "none" } }, null); } }); function O(e = {}) { const r = K(I, void 0), u = e.siteKey ?? r?.siteKey; if (!u) throw new Error("[vue3-recaptcha] Site key is required. Provide it via plugin options or composable options."); const i = u, t = v(""), a = v(!1), c = v(!1), o = v(null), s = v(null); async function m() { if (V()) { a.value = !0; return; } c.value = !0, o.value = null; try { await T({ siteKey: i, version: "v2", onError: (n) => { o.value = n; } }), a.value = !0; } catch (n) { throw o.value = n instanceof Error ? n : new Error(String(n)), o.value; } finally { c.value = !1; } } async function g(n) { return await m(), s.value !== null && d(), new Promise((f, h) => { try { const l = window.grecaptcha.render(n, { sitekey: i, theme: e.theme, size: e.size, tabindex: e.tabindex, callback: (w) => { t.value = w; }, "expired-callback": () => { t.value = ""; }, "error-callback": () => { o.value = /* @__PURE__ */ new Error("reCAPTCHA verification error"), t.value = ""; } }); s.value = l, f(l); } catch (l) { const w = l instanceof Error ? l : new Error(String(l)); o.value = w, h(w); } }); } async function y() { await m(); const n = s.value; if (n === null) throw new Error("[vue3-recaptcha] Widget not rendered. Call render() first."); return new Promise((f, h) => { const l = t.value, w = setInterval(() => { t.value && t.value !== l && (clearInterval(w), clearTimeout(E), f(t.value)); }, 100), E = setTimeout(() => { clearInterval(w), h(/* @__PURE__ */ new Error("[vue3-recaptcha] Execute timeout after 30 seconds")); }, 3e4); try { window.grecaptcha.execute(n); } catch (b) { clearInterval(w), clearTimeout(E), h(b instanceof Error ? b : new Error(String(b))); } }); } function d() { const n = s.value; n !== null && (window.grecaptcha.reset(n), t.value = ""); } function p() { const n = s.value; return n !== null ? window.grecaptcha.getResponse(n) : t.value; } return q(() => { s.value = null, t.value = ""; }), { token: R(t), isReady: R(a), isLoading: R(c), error: R(o), widgetId: R(s), render: g, execute: y, reset: d, getResponse: p }; } function Y(e = {}) { const r = K(I, void 0), u = e.siteKey ?? r?.siteKey; if (!u) throw new Error("[vue3-recaptcha] Site key is required. Provide it via plugin options or composable options."); const i = u, t = v(""), a = v(!1), c = v(!1), o = v(null); let s; async function m() { if (V()) { a.value = !0; return; } c.value = !0, o.value = null; try { await T({ siteKey: i, version: "v3", onError: (y) => { o.value = y; } }), a.value = !0; } catch (y) { throw o.value = y instanceof Error ? y : new Error(String(y)), o.value; } finally { c.value = !1; } } async function g(y) { await m(); const d = y ?? e.action ?? "submit"; c.value = !0, o.value = null; try { const p = await window.grecaptcha.execute(i, { action: d }); return t.value = p, s !== void 0 && clearTimeout(s), s = setTimeout(() => { t.value = ""; }, 11e4), p; } catch (p) { const n = p instanceof Error ? p : new Error(String(p)); throw o.value = n, t.value = "", n; } finally { c.value = !1; } } return q(() => { s !== void 0 && clearTimeout(s), t.value = ""; }), { token: R(t), isReady: R(a), isLoading: R(c), error: R(o), execute: g }; } var j = { install(e, r) { if (!r?.siteKey) { console.error("[vue3-recaptcha] Site key is required"); return; } const u = r.version ?? "v3", i = v(!1); e.component("RecaptchaV2Checkbox", W), e.component("RecaptchaV2Invisible", $), e.component("RecaptchaV2", W), e.component("RecaptchaV3", H), e.component("Recaptcha", H); const t = async () => { await T({ siteKey: r.siteKey, version: u, language: r.language, scriptUrl: r.scriptUrl, onLoad: () => { i.value = !0, r.onLoad?.(); } }), i.value = !0; }, a = { siteKey: r.siteKey, version: u, isLoaded: i, loadScript: t }; e.provide(I, a), r.autoLoad !== !1 && t().catch((c) => { const o = c instanceof Error ? c : new Error(String(c)); console.error("[vue3-recaptcha] Failed to auto-load script:", o), r.onError?.(o); }); } }, G = j; export { I as RECAPTCHA_INJECTION_KEY, H as Recaptcha, W as RecaptchaV2, W as RecaptchaV2Checkbox, $ as RecaptchaV2Invisible, H as RecaptchaV3, j as VueRecaptchaPlugin, j as default, V as isRecaptchaLoaded, T as loadRecaptchaScript, G as recaptchaPlugin, D as removeScript, O as useRecaptchaV2, Y as useRecaptchaV3, J as waitForRecaptcha }; //# sourceMappingURL=index.es.js.map