diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
| commit | 9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch) | |
| tree | ce5959571a981b9c4047da3c7b3fd080aa44222c /packages/frontend/src/components/MkCaptcha.vue | |
| parent | wip: retention for dashboard (diff) | |
| download | misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2 misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip | |
rename: client -> frontend
Diffstat (limited to 'packages/frontend/src/components/MkCaptcha.vue')
| -rw-r--r-- | packages/frontend/src/components/MkCaptcha.vue | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue new file mode 100644 index 0000000000..6d218389fc --- /dev/null +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -0,0 +1,118 @@ +<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' | 'turnstile'; + +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'; + case 'turnstile': return 'turnstile'; + } +}); + +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'; + case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; + } +}); + +const scriptId = computed(() => `script-${props.provider}`) + +const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha); + +if (loaded) { + available.value = true; +} else { + (document.getElementById(scriptId.value) || document.head.appendChild(Object.assign(document.createElement('script'), { + async: true, + id: scriptId.value, + 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> |