vue-client-recaptcha
Version:
Build simple recaptcha for vuejs without need server
278 lines (276 loc) • 9.25 kB
JavaScript
/*
* vue-client-recaptcha
* Creator:parsajiravand
* Build simple recaptcha for vuejs without need server
* v2.0.1
* MIT License
*/
import { ref as P, defineComponent as F, useSlots as I, computed as p, onMounted as U, watch as L, watchEffect as H, openBlock as j, createBlock as N, h as f } from "vue";
const k = {
alphanumeric: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
numeric: "0123456789",
letters: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
function K() {
if (typeof crypto < "u" && crypto.getRandomValues) {
const i = new Uint32Array(1);
return crypto.getRandomValues(i), i[0] / 4294967296;
}
return Math.random();
}
function W(i = {}) {
const w = typeof i == "function" ? i : () => i, v = P("");
function m() {
const {
chars: h = k.alphanumeric,
charsPreset: r = "alphanumeric",
count: a = 5
} = w(), g = r === "custom" ? h : k[r] ?? h;
let s = "";
const c = g;
for (let u = 0; u < a; u++) {
const x = Math.floor(K() * c.length);
s += c[x];
}
return v.value = s, s;
}
function l(h) {
return !!v.value && v.value === h;
}
function e() {
return m();
}
return {
code: v,
generate: m,
validate: l,
reset: e
};
}
const $ = /* @__PURE__ */ F({
__name: "vue-client-recaptcha",
props: {
modelValue: { default: "" },
value: { default: "" },
chars: { default: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" },
charsPreset: { default: "alphanumeric" },
count: { default: 5 },
hideLines: { type: Boolean, default: !1 },
customTextColor: { default: "" },
textColors: { default: () => [] },
width: { type: [Number, Function], default: (i) => i.count * 30 },
height: { default: 50 },
canvasClass: { default: "" },
icon: { default: "refresh" },
captchaFont: { default: "bold 28px sans-serif" },
hideRefreshIcon: { type: Boolean, default: !1 },
radius: { default: 0 },
refreshLabel: { default: "Refresh captcha" },
canvasLabel: { default: "Captcha image" },
theme: { default: "light" },
noiseDots: { default: 0 },
noiseLines: { default: -1 },
distortion: { default: "lines" },
audioEnabled: { type: Boolean, default: !1 },
simpleMode: { type: Boolean, default: !1 }
},
emits: ["isValid", "update:valid", "getCode", "update:modelValue", "refresh", "ready", "error"],
setup(i, { expose: w, emit: v }) {
const m = {
alphanumeric: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
numeric: "0123456789",
letters: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
custom: ""
};
function l() {
if (typeof crypto < "u" && crypto.getRandomValues) {
const t = new Uint32Array(1);
return crypto.getRandomValues(t), t[0] / 4294967296;
}
return Math.random();
}
const e = i, h = I(), r = v, a = P(null), g = W(() => ({
chars: e.chars,
charsPreset: e.charsPreset,
count: e.count
})), s = g.code, c = p(
() => typeof e.width == "function" ? e.width(e) : e.width
), u = p(() => e.height);
p(
() => e.charsPreset === "custom" ? e.chars : m[e.charsPreset] || m.alphanumeric
);
const x = p(
() => (e.modelValue !== void 0 && e.modelValue !== null ? e.modelValue : e.value) ?? ""
), S = p(() => !!(s.value && s.value === x.value));
U(() => {
if (!a.value) {
r("error", new Error("Canvas ref not available"));
return;
}
try {
y(), r("ready");
} catch (t) {
r("error", t);
}
}), L(
[c, u],
() => {
a.value && y();
},
{ flush: "post" }
), L(
[
() => e.distortion,
() => e.noiseDots,
() => e.noiseLines,
() => e.hideLines,
() => e.customTextColor,
() => e.textColors,
() => e.captchaFont,
() => e.simpleMode,
() => e.theme
],
() => {
a.value && y();
},
{ flush: "post" }
);
const y = () => {
var d;
if (!a.value) return;
s.value = "", a.value.width = c.value, a.value.height = u.value;
const t = a.value.getContext("2d");
if (!t) {
r("error", new Error("Failed to get canvas 2d context"));
return;
}
g.generate();
const o = s.value;
if (o) {
if (e.simpleMode) {
const n = e.theme === "dark" || e.theme === "auto" && ((d = window.matchMedia) == null ? void 0 : d.call(window, "(prefers-color-scheme: dark)").matches);
t.fillStyle = n ? "#ffffff" : "#000000", t.font = e.captchaFont, t.textAlign = "center", t.fillText(o, c.value / 2, u.value / 2 + 10);
} else
for (let n = 0; n < o.length; n++) {
const b = o[n], M = l() * 30 * Math.PI / 180, R = 10 + n * 25, V = 30 + l() * 8;
t.font = e.captchaFont, t.translate(R, V), e.customTextColor ? t.fillStyle = e.customTextColor : e.textColors.length ? t.fillStyle = e.textColors[Math.floor(l() * e.textColors.length)] : t.fillStyle = C(), t.rotate(M), t.fillText(b, 0, 0), t.rotate(-M), t.translate(-R, -V);
}
if (!e.simpleMode) {
const n = !e.hideLines && (e.distortion === "lines" || e.distortion === "both"), b = e.distortion === "dots" || e.distortion === "both";
n && D(), b && e.noiseDots && E();
}
if (B(), r("refresh", s.value), e.audioEnabled && typeof window < "u" && "speechSynthesis" in window) {
const n = new SpeechSynthesisUtterance(s.value.split("").join(" "));
n.lang = "en-US", n.rate = 0.8, window.speechSynthesis.cancel(), window.speechSynthesis.speak(n);
}
}
}, T = p(
() => e.noiseLines >= 0 ? e.noiseLines : e.count
), D = () => {
if (!a.value) return;
const t = a.value.getContext("2d");
if (!t) return;
const o = T.value;
for (let d = 0; d < o; d++)
t.strokeStyle = C(), t.beginPath(), t.moveTo(l() * c.value, l() * u.value), t.lineTo(l() * c.value, l() * u.value), t.stroke();
}, E = () => {
if (!a.value || !e.noiseDots) return;
const t = a.value.getContext("2d");
if (!t) return;
const o = c.value, d = u.value;
for (let n = 0; n < e.noiseDots; n++)
t.fillStyle = C(), t.beginPath(), t.arc(l() * o, l() * d, 1, 0, Math.PI * 2), t.fill();
}, B = () => {
r("getCode", s.value);
};
H(() => {
const t = S.value;
r("isValid", t), r("update:valid", t);
});
const C = () => {
const t = Math.floor(l() * 256), o = Math.floor(l() * 256), d = Math.floor(l() * 256);
return `rgb(${t},${o},${d})`;
}, _ = () => {
if (!a.value) return;
const t = a.value.getContext("2d");
t && (t.clearRect(0, 0, c.value, u.value), y());
};
w({
resetCaptcha: _
});
const A = () => {
const t = e.theme === "auto" ? "vue_client_recaptcha_theme_auto" : e.theme === "dark" ? "vue_client_recaptcha_theme_dark" : "";
return f(
"div",
{
class: ["vue_client_recaptcha", t].filter(Boolean).join(" "),
style: {
borderRadius: `var(--vcr-radius, ${e.radius}px)`,
width: `${c.value + 50}px`
}
},
[
f(
"span",
{
"aria-live": "polite",
"aria-atomic": "true",
class: "vue_client_recaptcha_sr_only"
},
S.value ? "Captcha verified" : ""
),
...e.hideRefreshIcon ? [] : [
f(
"div",
{
class: "vue_client_recaptcha_icon",
role: "button",
tabindex: 0,
"aria-label": e.refreshLabel,
onClick: () => _(),
onKeydown: (o) => {
(o.key === "Enter" || o.key === " ") && (o.preventDefault(), _());
}
},
[
h.icon ? f(h.icon) : f(
"svg",
{
class: "vue_client_recaptcha_icon_svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg"
},
[
f("path", {
d: "M2 12a9 9 0 0 0 9 9c2.39 0 4.68-.94 6.4-2.6l-1.5-1.5A6.706 6.706 0 0 1 11 19c-6.24 0-9.36-7.54-4.95-11.95C10.46 2.64 18 5.77 18 12h-3l4 4h.1l3.9-4h-3a9 9 0 0 0-18 0Z",
fill: "currentColor"
})
]
)
]
)
],
f(
"canvas",
{
id: "captcha_canvas",
class: `captcha_canvas ${e.canvasClass}`,
role: "img",
"aria-label": e.canvasLabel,
ref: a
},
s.value
)
]
);
};
return (t, o) => (j(), N(A));
}
});
export {
$ as VueClientRecaptcha,
W as useCaptcha
};