@anilkumarthakur/vue3-recaptcha
Version:
A Vue 3 plugin for Google reCAPTCHA v2 (checkbox & invisible) and v3 integration
549 lines (548 loc) • 16.1 kB
JavaScript
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