admin管理员组

文章数量:1327661

I'm in a bind, and Javascript world it's very weird.

I need to make a form with the reCAPTCHA v3 token resolved before proceeding with form submission. Since some events are bound to the form submit event, the event loop must wait until it's resolved otherwise it fails:

  • Some forms have validation events that prevent the submission if there are errors (like an invalid value inside an input).
  • If the user clicks the form too quickly (thanks to the autoplete) when the page loads, and the token is still not retrieved, the request returns an error.

While I'm trying to do is to have a listener for the submit event that blocks the submission until the token is resolved before going on with other listeners.

Currently the documentation on the reCAPTCHA script is zero, and I haven't found a reliable way to actually and forcefully resolve the token. The reCAPTCHA script is asynchronous, so there is no way to wait until the token is resolved:

// Let's retrieve the form by its id.
let form = document.getElementById('example_form');

let addToken = (form) => {
    grecaptcha.execute('site_key', {
        // some options
    }).then(token => {
        // Include the token as an input inside the form so it's sent.
    });
};

form.addEventListener('submit', () => {
    return addToken(form); // <-- This won't work, since it's async.
});

I'm in a bind, and Javascript world it's very weird.

I need to make a form with the reCAPTCHA v3 token resolved before proceeding with form submission. Since some events are bound to the form submit event, the event loop must wait until it's resolved otherwise it fails:

  • Some forms have validation events that prevent the submission if there are errors (like an invalid value inside an input).
  • If the user clicks the form too quickly (thanks to the autoplete) when the page loads, and the token is still not retrieved, the request returns an error.

While I'm trying to do is to have a listener for the submit event that blocks the submission until the token is resolved before going on with other listeners.

Currently the documentation on the reCAPTCHA script is zero, and I haven't found a reliable way to actually and forcefully resolve the token. The reCAPTCHA script is asynchronous, so there is no way to wait until the token is resolved:

// Let's retrieve the form by its id.
let form = document.getElementById('example_form');

let addToken = (form) => {
    grecaptcha.execute('site_key', {
        // some options
    }).then(token => {
        // Include the token as an input inside the form so it's sent.
    });
};

form.addEventListener('submit', () => {
    return addToken(form); // <-- This won't work, since it's async.
});
Share Improve this question edited May 26, 2020 at 7:53 Brian Tompsett - 汤莱恩 5,89372 gold badges61 silver badges133 bronze badges asked May 26, 2020 at 6:17 DarkGhostHunterDarkGhostHunter 1,5503 gold badges13 silver badges25 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 5

I had the same issue. This is my solution:

async function recaptchaCall(){
  var recaptcha_token = '';
  grecaptcha.ready(() => {
    grecaptcha.execute(grecaptchaKey, {action: 'submit'}).then((token) => {
      recaptcha_token = token;
    });
  });
  while(recaptcha_token == ''){
    await new Promise(r => setTimeout(r, 100));
  }
  return recaptcha_token; 
}

let recaptcha_token = await recaptchaCall();

I think it's the best solution since it is not possible to make it as an synchronously function correctly. I'm using setTimeout to wait until I receive Recaptcha answer. I hope it is helpful.

Figured out, there is no way to make recaptcha.execute() synchronous. In other words, waiting for the token to be resolved from servers is impossible.

Instead, you should hammer nonchalantly the reCAPTCHA servers by requesting an initial token, and then setting up the same action through an interval of 100 seconds to be sure the token is received before expiration.

This code is adjusted for many forms. Use at your own risk.

const site_key = 'HEREYOURSITEKEY';

// This function retrieves the token from reCAPTCHA.
const retrieveToken = (form) => {
    // Mark the form as unresolved until the retrieval ends.
    form.unresolved = true;
    // Get the token.
    grecaptcha.execute(site_key, {
        action: form.action.substring(action.indexOf('?'), action.length).replace(/[^A-z\/_]/gi, '')
    }).then(token => {
        // Append the token to the form so it's sent along the other data.
        let child = document.createElement('input');
        child.setAttribute('type', 'hidden');
        child.setAttribute('name', '_recaptcha');
        child.setAttribute('value', token);
        form.appendChild(child);
        form.unresolved = false;
    });
};

// We will loop for each form in the document. You can add a "filter" method if you want.
Array.from(document.getElementByTagName('form'))
    .forEach(form => {
        // Mark it as unresolved from the beginning.
        form.unresolved = true;
        // Add an event listener that disables submission if the form is unresolved.
        form.addEventListener('submit', event => {
            if (form.unresolved) {
                event.preventDefault();
            }
        });
        // Resolve the token at startup.
        retrieveToken(form);
        // And retrieve a new token each 100 seconds. Bite me.
        setInterval(() => refreshToken(form), 100 * 1000);
    });

You could just disable form submission until the token is loaded and show some loader instead. In the then callback, you save the token in a form field and enable the submission of the form (and hide the loader).

If you want to make it more seamless, you could hide this process from the user: If the user submits the form before the token is loaded, then you disable the button (and prevent the regular submission attempt) and show a loader instead and remember that a submission was pending, and when the token is later received you check if a submission was pending, and if yes, you programmatically submit the form (including the token).

This way the user won't encounter the loader unless they actually submit the form too early.

JavaScript cannot wait for the recaptcha request to finish. I made that work with a timestamp global variable that I pare to the current timestamp at each submit. So, onSubmit has 2 behaviours: if requesting a new token is necessary, then onSubmit only takes care of the recaptcha token (submission is prevented). If not necessary, then it does the usual (validations and stuff).

<script src="https://www.google./recaptcha/api.js?render=SITE_KEY"></script>

<script> 
    
        $(function() {
    
            // last recaptcha token timestamp in ms (to check if the refresh interval of max 2 minutes is reached in between submit attempts)
            recaptchaLoginLastTokenTimestamp = 0;
    
            $("#loginform").on('submit', function(e){   
                
                nowTimestamp = new Date().getTime();
    
                // if 100 seconds passed since last recaptcha token => get new token
                if(nowTimestamp - recaptchaLoginLastTokenTimestamp > 100000){
                    // stop submission
                    e.preventDefault();
    
                    // call the recaptcha service to get a token
                    grecaptcha.ready(function() {
                        grecaptcha.execute('SITE_KEY', {action:'action_login'})
                                .then(function(token) 
                        {
                            // add token value to form
                            document.getElementById('recaptcha_response_token').value = token;
    
                            // update last token timestamp
                            recaptchaLoginLastTokenTimestamp = new Date().getTime();
    
                            // re-submit (with the updated token timestamp, the normal submit process will occur, with validations and other stuff that are bind to submission; no infinite recursive submit calls)
                            $("#loginform").submit();
                        });    
                    });
                }  
                          
            });
    
        });
    
    </script>

本文标签: javascriptHow to wait for the reCAPTCHA v3 token synchronouslyStack Overflow