如何同步等待 reCAPTCHA v3 令牌?

How to wait for the reCAPTCHA v3 token synchronously?

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

在继续提交表单之前,我需要使用 reCAPTCHA v3 令牌 resolved 创建一个表单。由于某些事件绑定到表单 submit 事件,事件循环必须等到它被解决否则失败:

虽然我正在尝试做的是让 submit 事件的侦听器 阻止提交,直到令牌被解析 然后再继续与其他侦听器.

目前关于 reCAPTCHA 脚本的文档为零,我还没有找到可靠的方法来实际和强制解析令牌。 reCAPTCHA 脚本是异步的,所以没有办法等到令牌被解析:

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

您可以在加载令牌之前禁用表单提交,而是显示一些加载程序。在 then 回调中,您将令牌保存在表单字段中并启用表单提交(并隐藏加载程序)。

如果你想让它更无缝,你可以向用户隐藏这个过程:如果用户在加载令牌之前提交表单,然后你禁用按钮(并阻止常规提交尝试)并改为显示加载程序并记住提交正在等待,当稍后收到令牌时,您检查提交是否正在等待,如果是,您以编程方式提交表单(包括令牌) .

这样用户就不会遇到加载程序,除非他们实际上过早地提交了表单。

想通了,没办法让recaptcha.execute()同步。换句话说,等待令牌从服务器解析是不可能的。

相反,您应该通过请求初始令牌来漫不经心地敲打 reCAPTCHA 服务器,然后通过 100 秒的间隔设置相同的操作以确保在过期前收到令牌。

此代码针对多种形式进行了调整。使用风险自负。

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

我遇到了同样的问题。这是我的解决方案:

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();

我认为这是最好的解决方案,因为不可能使其作为同步功能正确运行。我正在使用 setTimeout 等待收到 Recaptcha 答复。希望对你有帮助。

JavaScript 无法等待重新验证请求完成。 我使用一个时间戳全局变量来完成这项工作,我在每次提交时将该变量与当前时间戳进行比较。因此,onSubmit 有两种行为:如果需要请求新令牌,则 onSubmit 仅处理 recaptcha 令牌(阻止提交)。如果没有必要,那么它会执行通常的操作(验证和其他内容)。

<script src="https://www.google.com/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>