Skip to content

Instantly share code, notes, and snippets.

@Chippd
Created December 19, 2024 10:17
Show Gist options
  • Save Chippd/a38af151b997c22fe28335546313e08d to your computer and use it in GitHub Desktop.
Save Chippd/a38af151b997c22fe28335546313e08d to your computer and use it in GitHub Desktop.
Nuxt Captcha plugin (for /r/nuxt reply)
<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>
<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