Created
December 19, 2024 10:17
-
-
Save Chippd/a38af151b997c22fe28335546313e08d to your computer and use it in GitHub Desktop.
Nuxt Captcha plugin (for /r/nuxt reply)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<!-- rest of component --> | |
<TheCaptcha | |
ref="captcha" | |
@verify="handleCaptchaVerify" | |
@error="handleCaptchaError" | |
@expired="handleCaptchaExpired" | |
@challengeExpired="handleCaptchaChallengeExpired" | |
/> | |
<!-- rest of component --> | |
</template> | |
<script> | |
const capcthaToken = ref(null) | |
function generateToken () { | |
// call execute() on captcha | |
captchaToken.value = await captcha.value.execute('some_action') //'some_action' is a trackable action - see recaptcha docs https://cloud.google.com/recaptcha/docs/actions-website | |
} | |
function handleCaptchaVerify(token) { | |
console.log('Captcha verified with token:', token) | |
captchaToken.value = token // token can be used to send to backend as part of any request | |
} | |
function handleCaptchaError() { | |
console.log("handle error here") | |
} | |
function handleCaptchaExpired() { | |
console.log("handle expired here") | |
} | |
function handleCaptchaChallengeExpired() { | |
console.log("handle challenge expired here") | |
} | |
</script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="recaptcha-container" :class="{ 'flex justify-center': center }"> | |
<div ref="recaptchaElement"></div> | |
<button class="g-recaptcha" | |
style="visibility: hidden;" | |
data-size="invisible" | |
:data-sitekey="siteKey" | |
:data-callback='keyCallback()' | |
data-action='submit'> | |
</button> | |
</div> | |
</template> | |
<script setup> | |
const { locale } = useI18n() | |
const props = defineProps({ | |
center: { | |
type: Boolean, | |
default: true | |
} | |
}) | |
const config = useRuntimeConfig() | |
const siteKey = config.public.RECAPTCHA_SITE_KEY | |
const emit = defineEmits(['verify', 'error', 'expired']) | |
const recaptchaElement = ref(null) | |
const isRecaptchaReady = ref(false) | |
let recaptchaInstance = null | |
// Load reCAPTCHA script with locale | |
const loadRecaptcha = () => { | |
return new Promise((resolve, reject) => { | |
if (window.grecaptcha && window.grecaptcha.ready) { | |
window.grecaptcha.ready(() => { | |
isRecaptchaReady.value = true | |
resolve(window.grecaptcha) | |
}) | |
return | |
} | |
const script = document.createElement('script') | |
script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}` | |
script.async = true | |
script.defer = true | |
script.onload = () => { | |
window.grecaptcha.ready(async () => { | |
isRecaptchaReady.value = true | |
resolve(window.grecaptcha) | |
}) | |
} | |
script.onerror = (error) => { | |
reject(new Error('Failed to load reCAPTCHA')) | |
} | |
document.head.appendChild(script) | |
}) | |
} | |
async function execute(action = 'unspecified') { | |
console.log('executing captcha: ', action) | |
try { | |
if (!isRecaptchaReady.value) { | |
await loadRecaptcha() | |
} | |
const token = await window.grecaptcha.execute(siteKey, { action }) | |
console.log('reCAPTCHA token generated') | |
return token | |
} catch (error) { | |
console.error('Error executing reCAPTCHA:', error) | |
emit('error', error) | |
throw error | |
} | |
} | |
// Map i18n locales to reCAPTCHA supported locales | |
const getRecaptchaLocale = (i18nLocale) => { | |
const localeMap = { | |
'en': 'en', | |
'fr': 'fr', | |
'es': 'es', | |
'de': 'de', | |
'pt': 'pt', | |
'it': 'it', | |
'nl': 'nl', | |
'ru': 'ru', | |
'ja': 'ja', | |
'ko': 'ko', | |
'zh': 'zh-CN', | |
'ar': 'ar', | |
} | |
return localeMap[i18nLocale] || 'en' | |
} | |
onMounted(async () => { | |
try { | |
await loadRecaptcha() | |
} catch (error) { | |
console.error('Error initializing reCAPTCHA:', error) | |
emit('error', error) | |
} | |
}) | |
// Watch for locale changes and reset reCAPTCHA when locale changes | |
watch(locale, async (newLocale) => { | |
if (isRecaptchaReady.value) { | |
const currentLang = getRecaptchaLocale(newLocale) | |
const iframe = document.querySelector('.g-recaptcha iframe') | |
if (iframe) { | |
const currentSrc = iframe.src | |
const newSrc = currentSrc.replace(/hl=([\w-]+)/, `hl=${currentLang}`) | |
iframe.src = newSrc | |
} | |
} | |
}) | |
onUnmounted(() => { | |
const element = document.querySelector('.g-recaptcha') | |
if (element) { | |
element.remove() | |
} | |
}) | |
const reset = async () => { | |
if (!isRecaptchaReady.value) { | |
await loadRecaptcha() | |
} | |
await window.grecaptcha.reset() | |
} | |
const keyCallback = (token) => { | |
console.log('Captcha verified with token:', token) | |
emit('verify', token) | |
} | |
defineExpose({ | |
reset, | |
execute | |
}) | |
</script> | |
<style scoped> | |
.recaptcha-container { | |
@apply min-h-[78px]; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment