Search code examples
javascriptrecaptchasveltekit

How to get recaptcha token in SvelteKit contact form


I'm developing a static website using SvelteKit to get my first approach to svelte. I got almost everything working as intended. Everything but the recaptcha protection on contact form.

I can obtain recaptcha token but I'm losing it the passing it to formData request. My relative code is:

<script lang="ts">
    import * as yup from "yup";
    import {createForm, ErrorMessage} from "svelte-forms-lib";
    ...

    function getCaptcha(){
        grecaptcha.ready(function () {
            grecaptcha.execute('recaptcha-key', { action: 'contactForm' })
                .then(function (token) {

                    console.log(token) // here I get the token
                    return token;
                });
        });
    }

    const  sendMail= (values)=>
    {
        const url = '/sendmail.php';

    const formData = new FormData();
    let token = getCaptcha();
    console.log('token: '+token); // here token is null

    formData.append("token", token);

    fetch(url, {
        method: 'post',
    body: formData,
        })
    .then(function (response) {
        console.debug(response.status)
    })
    .catch(function (e) {
        sendError = true;
    console.debug(e);
        });
    }
</script>

Form submission is working fine except, obviously, for recaptcha token.

Thanks for any idea on how to solve it.


Solution

  • token is null because your getCaptcha function isn't actually returning anything, since you're using callbacks to get the token.

    function getCaptcha(){  // <- this function isn't returning anything
        grecaptcha.ready(function () {
            grecaptcha.execute('recaptcha-key', { action: 'contactForm' })
                .then(function (token) {  // <- this function returns, but the value isn't going anywhere
                    console.log(token);
                    return token;
                });
        });
    }
    

    To fix this, you may want to look into using async/await syntax for promises. MDN has a great article if you're interested.

    You would probably want to do something like this:

    async function getCaptcha(){
        await new Promise((resolve, reject) => {
            // grecaptcha.ready needs a callback so we create a promise to await
            grecaptcha.ready(resolve);
        });
        // grecaptcha.execute returns a promise so we can await it
        const token = await grecaptcha.execute('recaptcha-key', { action: 'contactForm' });
        console.log(token);
        return token;
    }
    
    const sendMail = async (values) => {
        const url = '/sendmail.php';
    
        const formData = new FormData();
    
        let token = await getCaptcha();
        console.log('token: ' + token);
    
        formData.append("token", token);
    
        // you have a few options for converting
        // the Promise.catch()
        // you can use a try {} catch {} block
        // or you can use .catch() on this sendMail function
        try {
            const response = fetch(url, {
                method: 'post',
                body: formData,
            });
            console.debug(response.status);
        } catch (e) {
            sendError = true;
            console.debug(e);
        }
    }
    

    Keep in mind that this will make the sendMail function an async function, which means you will have to await calls to it, or use sendMail().then(). I believe it's also possible to call async functions directly in synchronous code and have them run, it's just not guaranteed that the stuff in the function will run before the code that comes after the call.