136 lines
2.7 KiB
Vue
136 lines
2.7 KiB
Vue
<template>
|
|
<div>
|
|
<span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis /></span>
|
|
<div ref="captchaEl"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
|
|
import { defaultStore } from "@/store";
|
|
import { i18n } from "@/i18n";
|
|
|
|
type Captcha = {
|
|
render(
|
|
container: string | Node,
|
|
options: {
|
|
readonly [_ in
|
|
| "sitekey"
|
|
| "theme"
|
|
| "type"
|
|
| "size"
|
|
| "tabindex"
|
|
| "callback"
|
|
| "expired"
|
|
| "expired-callback"
|
|
| "error-callback"
|
|
| "endpoint"]?: unknown;
|
|
}
|
|
): string;
|
|
remove(id: string): void;
|
|
execute(id: string): void;
|
|
reset(id?: string): void;
|
|
getResponse(id: string): string;
|
|
};
|
|
|
|
type CaptchaProvider = "hcaptcha" | "recaptcha";
|
|
|
|
type CaptchaContainer = {
|
|
readonly [_ in CaptchaProvider]?: Captcha;
|
|
};
|
|
|
|
declare global {
|
|
interface Window extends CaptchaContainer {}
|
|
}
|
|
|
|
const props = defineProps<{
|
|
provider: CaptchaProvider;
|
|
sitekey: string;
|
|
modelValue?: string | null;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
(ev: "update:modelValue", v: string | null): void;
|
|
}>();
|
|
|
|
const available = ref(false);
|
|
|
|
const captchaEl = ref<HTMLDivElement | undefined>();
|
|
|
|
const variable = computed(() => {
|
|
switch (props.provider) {
|
|
case "hcaptcha":
|
|
return "hcaptcha";
|
|
case "recaptcha":
|
|
return "grecaptcha";
|
|
}
|
|
});
|
|
|
|
const loaded = !!window[variable.value];
|
|
|
|
const src = computed(() => {
|
|
switch (props.provider) {
|
|
case "hcaptcha":
|
|
return "https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off";
|
|
case "recaptcha":
|
|
return "https://www.recaptcha.net/recaptcha/api.js?render=explicit";
|
|
}
|
|
});
|
|
|
|
const captcha = computed<Captcha>(
|
|
() => window[variable.value] || ({} as unknown as Captcha)
|
|
);
|
|
|
|
if (loaded) {
|
|
available.value = true;
|
|
} else {
|
|
(
|
|
document.getElementById(props.provider) ||
|
|
document.head.appendChild(
|
|
Object.assign(document.createElement("script"), {
|
|
async: true,
|
|
id: props.provider,
|
|
src: src.value,
|
|
})
|
|
)
|
|
).addEventListener("load", () => (available.value = true));
|
|
}
|
|
|
|
function reset() {
|
|
if (captcha.value.reset) captcha.value.reset();
|
|
}
|
|
|
|
function requestRender() {
|
|
if (captcha.value.render && captchaEl.value instanceof Element) {
|
|
captcha.value.render(captchaEl.value, {
|
|
sitekey: props.sitekey,
|
|
theme: defaultStore.state.darkMode ? "dark" : "light",
|
|
callback: callback,
|
|
"expired-callback": callback,
|
|
"error-callback": callback,
|
|
});
|
|
} else {
|
|
window.setTimeout(requestRender, 1);
|
|
}
|
|
}
|
|
|
|
function callback(response?: string) {
|
|
emit("update:modelValue", typeof response === "string" ? response : null);
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (available.value) {
|
|
requestRender();
|
|
} else {
|
|
watch(available, requestRender);
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
reset();
|
|
});
|
|
|
|
defineExpose({
|
|
reset,
|
|
});
|
|
</script>
|