opnform-host-nginx/client/components/forms/components/HCaptchaV2.vue

197 lines
4.6 KiB
Vue

<template>
<div class="hcaptcha-container">
<div ref="hcaptchaContainer" />
</div>
</template>
<script setup>
const props = defineProps({
sitekey: {
type: String,
required: true
},
theme: {
type: String,
default: 'light'
},
language: {
type: String,
default: 'en'
}
})
const emit = defineEmits(['verify', 'expired', 'opened', 'closed'])
const hcaptchaContainer = ref(null)
let widgetId = null
// Global script loading state
const SCRIPT_ID = 'hcaptcha-script'
let scriptLoadPromise = null
// Add this cleanup function at the script level
const cleanupHcaptcha = () => {
// Remove all hCaptcha iframes
document.querySelectorAll('iframe[src*="hcaptcha.com"]').forEach(iframe => {
iframe.remove()
})
// Remove all hCaptcha scripts
document.querySelectorAll('script[src*="hcaptcha.com"]').forEach(script => {
script.remove()
})
// Remove specific script
const script = document.getElementById(SCRIPT_ID)
if (script) {
script.remove()
}
// Remove hCaptcha styles
document.querySelectorAll('style[data-emotion]').forEach(style => {
style.remove()
})
// Clean up global variables
if (window.hcaptcha) {
delete window.hcaptcha
}
// Clean up any potential callbacks
Object.keys(window).forEach(key => {
if (key.startsWith('hcaptchaOnLoad_')) {
delete window[key]
}
})
scriptLoadPromise = null
}
const loadHcaptchaScript = () => {
if (scriptLoadPromise) return scriptLoadPromise
// Clean up before loading new script
cleanupHcaptcha()
scriptLoadPromise = new Promise((resolve, reject) => {
// If hcaptcha is already available and ready, use it
if (window.hcaptcha?.render) {
resolve(window.hcaptcha)
return
}
// Create a unique callback name
const callbackName = `hcaptchaOnLoad_${Date.now()}`
// Create the script
const script = document.createElement('script')
script.id = SCRIPT_ID
script.src = `https://js.hcaptcha.com/1/api.js?render=explicit&onload=${callbackName}&recaptchacompat=off`
script.async = true
script.defer = true
let timeoutId = null
// Set up the callback before adding the script
window[callbackName] = () => {
if (timeoutId) clearTimeout(timeoutId)
if (window.hcaptcha?.render) {
resolve(window.hcaptcha)
delete window[callbackName]
} else {
reject(new Error('hCaptcha failed to initialize'))
}
}
script.onerror = (error) => {
if (timeoutId) clearTimeout(timeoutId)
delete window[callbackName]
scriptLoadPromise = null
reject(error)
}
timeoutId = setTimeout(() => {
delete window[callbackName]
scriptLoadPromise = null
reject(new Error('hCaptcha script load timeout'))
}, 10000)
document.head.appendChild(script)
})
return scriptLoadPromise
}
const renderHcaptcha = async () => {
try {
// Clear any existing content first
if (hcaptchaContainer.value) {
hcaptchaContainer.value.innerHTML = ''
}
const hcaptcha = await loadHcaptchaScript()
// Double check container still exists after async operation
if (!hcaptchaContainer.value) return
// Render new widget
widgetId = hcaptcha.render(hcaptchaContainer.value, {
sitekey: props.sitekey,
theme: props.theme,
hl: props.language,
'callback': (token) => emit('verify', token),
'expired-callback': () => emit('expired'),
'error-callback': () => {
if (widgetId !== null) {
hcaptcha.reset(widgetId)
}
},
'open-callback': () => emit('opened'),
'close-callback': () => emit('closed')
})
} catch {
scriptLoadPromise = null // Reset promise on error
}
}
onMounted(() => {
renderHcaptcha()
})
onBeforeUnmount(() => {
// Clean up widget and reset state
if (window.hcaptcha && widgetId !== null) {
try {
window.hcaptcha.remove(widgetId)
} catch {
// Silently handle error
}
}
cleanupHcaptcha()
if (hcaptchaContainer.value) {
hcaptchaContainer.value.innerHTML = ''
}
widgetId = null
})
// Expose reset method that properly reloads the captcha
defineExpose({
reset: async () => {
if (window.hcaptcha && widgetId !== null) {
try {
// Use the official API to reset the captcha widget
window.hcaptcha.reset(widgetId)
return true
} catch (error) {
console.error('Error resetting hCaptcha, falling back to re-render', error)
}
}
// Fall back to full re-render if reset fails or hcaptcha isn't available
cleanupHcaptcha()
await renderHcaptcha()
}
})
</script>