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:
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.
});
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);
});