【问题标题】:detect when challenge window is closed for Google recaptcha检测何时关闭 Google recaptcha 的挑战窗口
【发布时间】:2017-09-15 06:18:13
【问题描述】:

我正在使用 Google 隐形验证码。有没有办法检测挑战窗口何时关闭?挑战窗口是指您必须选择一些图像进行验证的窗口。

我目前在按钮上放置了一个微调器,一旦单击按钮,就会呈现 recaptcha 挑战。无法通过另一个挑战窗口提示用户。

我正在以编程方式调用渲染函数:

grecaptcha.render(htmlElement, { callback: this.verified, expiredCallback: this.resetRecaptcha, sitekey: this.siteKey, theme: "light", size: "invisible" });

我有 2 个回调函数将已验证和 resetRecaptcha 函数连接起来,如下所示:

function resetRecaptcha() {
        grecaptcha.reset();
    }

function verified(recaptchaResponse)
{
/*
which calls the server to validate
*/
}

我本来希望 grecaptcha.render 有另一个回调,当挑战屏幕关闭时调用,而用户没有通过选择图像来验证自己。

【问题讨论】:

  • 请向我们展示您已有的代码
  • 我认为这里不需要代码@arcs。我有同样的问题 - 如何检测到挑战小部件刚刚关闭,没有其他操作?
  • @knaos 你是对的。我在下面发布了解决方案......晚了 2 个月

标签: javascript callback recaptcha invisible-recaptcha


【解决方案1】:

正如您提到的,API 不支持此 功能。

但是,您可以自己添加此功能。您可以谨慎使用以下代码,Google 可能会更改其 reCaptcha 并因此破坏此自定义代码。该解决方案依赖于reCaptcha的两个特性,所以如果代码不起作用,请先查看:

  • 窗口iframe src:包含“google.com/recaptcha/api2/bframe”
  • CSS opacity 属性:当窗口关闭时更改为 0

 

// to begin: we listen to the click on our submit button
// where the invisible reCaptcha has been attachtted to
// when clicked the first time, we setup the close listener
recaptchaButton.addEventListener('click', function(){
    if(!window.recaptchaCloseListener) initListener()

})

function initListener() {

    // set a global to tell that we are listening
    window.recaptchaCloseListener = true

    // find the open reCaptcha window
    HTMLCollection.prototype.find = Array.prototype.find
    var recaptchaWindow = document
        .getElementsByTagName('iframe')
        .find(x=>x.src.includes('google.com/recaptcha/api2/bframe'))
        .parentNode.parentNode

    // and now we are listening on CSS changes on it
    // when the opacity has been changed to 0 we know that
    // the window has been closed
    new MutationObserver(x => recaptchaWindow.style.opacity == 0 && onClose())
        .observe(recaptchaWindow, { attributes: true, attributeFilter: ['style'] })

}

// now do something with this information
function onClose() {
    console.log('recaptcha window has been closed')
}

【讨论】:

  • 这在除 IE 之外的所有东西上都很好用——这只是重构 ES6 的一个例子吗?
  • @Doughballs 是的,如果你修复了 ES6,它应该可以在 IE11 上运行。但是,它不适用于 IE10 及更低版本
  • 这很棒,但是当 recaptcha 挑战成功完成时 onClose 也会触发。
  • 效果很好,当然你必须用实际的选择器替换 recaptchaButton。
【解决方案2】:

要在 IE 中工作,此解决方案需要 .include() 和 Array.from() 的 polyfill,如下所示:

Array.from on the Internet Explorer

ie does not support 'includes' method

以及更新后的代码:

函数 initListener() {

              // set a global to tell that we are listening
              window.recaptchaCloseListener = true

              // find the open reCaptcha window

                    var frames = Array.from(document.getElementsByTagName('iframe'));
                    var recaptchaWindow;

                    frames.forEach(function(x){

                        if (x.src.includes('google.com/recaptcha/api2/bframe') ){
                            recaptchaWindow = x.parentNode.parentNode;
                        };

                    });

              // and now we are listening on CSS changes on it
              // when the opacity has been changed to 0 we know that
                // the window has been closed

                new MutationObserver(function(){
                    recaptchaWindow.style.opacity == 0 && onClose();
                })
                  .observe(recaptchaWindow, { attributes: true, attributeFilter: ['style'] })

            }

【讨论】:

    【解决方案3】:

    正如我在 @arcs 提交的答案的 cmets 中提到的,这是一个很好的解决方案,但它也会在用户成功完成挑战时触发 onClose()。我的解决方案是像这样更改onClose() 函数:

    // now do something with this information
    function onClose() {
        if(!grecaptcha.getResponse()) {
            console.log('recaptcha window has been closed')
        }
    }
    

    这样,只有在挑战已关闭且用户尚未完成挑战时,它才会执行所需的代码,因此无法以grecaptcha.getResponse()返回响应

    【讨论】:

      【解决方案4】:

      我的解决方案:

      let removeRecaptchaOverlayEventListener = null
      const reassignGRecatchaExecute = () => {
        if (!window.grecaptcha || !window.grecaptcha.execute) {
          return
        }
        /* save original grecaptcha.execute */
        const originalExecute = window.grecaptcha.execute
        window.grecaptcha.execute = (...params) => {
          try {
            /* find challenge iframe */
            const recaptchaIframe = [...document.body.getElementsByTagName('iframe')].find(el => el.src.match('https://www.google.com/recaptcha/api2/bframe'))
            const recaptchaOverlay = recaptchaIframe.parentElement.parentElement.firstElementChild
            /* detect when the recaptcha challenge window is closed and reset captcha */
            !removeRecaptchaOverlayEventListener && recaptchaOverlay.addEventListener('click', window.grecaptcha.reset)
            /* save remove event listener for click event */
            removeRecaptchaOverlayEventListener = () => recaptchaOverlay.removeEventListener('click', window.grecaptcha.reset)
          } catch (error) {
            console.error(error)
          } finally {
            originalExecute(...params)
          }
        }
      }
      

      在你运行window.grecaptcha.render()之后和window.grecaptcha.execute()之前调用这个函数

      别忘了移除事件监听器:removeRecaptchaOverlayEventListener()

      【讨论】:

        【解决方案5】:

        对于不完全了解其工作原理的每个人,这里有另一个示例,其中包含您可能会发现有用的解释:

        所以我们这里有两个挑战。

        1) 检测挑战何时显示并获取挑战的叠加div

        function detectWhenReCaptchaChallengeIsShown() {
            return new Promise(function(resolve) {
                const targetElement = document.body;
        
                const observerConfig = {
                    childList: true,
                    attributes: false,
                    attributeOldValue: false,
                    characterData: false,
                    characterDataOldValue: false,
                    subtree: false
                };
        
                function DOMChangeCallbackFunction(mutationRecords) {
                    mutationRecords.forEach((mutationRecord) => {
                        if (mutationRecord.addedNodes.length) {
                            var reCaptchaParentContainer = mutationRecord.addedNodes[0];
                            var reCaptchaIframe = reCaptchaParentContainer.querySelectorAll('iframe[title*="recaptcha"]');
        
                            if (reCaptchaIframe.length) {
                                var reCaptchaChallengeOverlayDiv = reCaptchaParentContainer.firstChild;
                                if (reCaptchaChallengeOverlayDiv.length) {
                                    reCaptchaObserver.disconnect();
                                    resolve(reCaptchaChallengeOverlayDiv);
                                }
                            }
                        }
                    });
                }
        
                const reCaptchaObserver = new MutationObserver(DOMChangeCallbackFunction);
                reCaptchaObserver.observe(targetElement, observerConfig);
            });
        }
        

        首先,我们创建了一个目标元素,用于观察 Google iframe 的外观。我们将 document.body 定位为 iframe 将附加到它:

        const targetElement = document.body;
        

        然后我们为 MutationObserver 创建了一个配置对象。在这里,我们可以指定我们在 DOM 更改中跟踪的确切内容。请注意,默认情况下所有值都是“false”,因此我们只能留下“childList”——这意味着我们只会观察目标元素的子节点更改——在我们的例子中是 document.body:

        const observerConfig = {
            childList: true,
            attributes: false,
            attributeOldValue: false,
            characterData: false,
            characterDataOldValue: false,
            subtree: false
        };
        

        然后我们创建了一个函数,当观察者检测到我们在配置对象中指定的特定类型的 DOM 更改时将调用该函数。第一个参数表示Mutation Observer 对象的数组。我们抓取了覆盖 div 并返回了 Promise。

        function DOMChangeCallbackFunction(mutationRecords) {
            mutationRecords.forEach((mutationRecord) => {
                if (mutationRecord.addedNodes.length) { //check only when notes were added to DOM
                    var reCaptchaParentContainer = mutationRecord.addedNodes[0];
                    var reCaptchaIframe = reCaptchaParentContainer.querySelectorAll('iframe[title*="recaptcha"]');
        
                    if (reCaptchaIframe.length) { // Google reCaptcha iframe was loaded
                        var reCaptchaChallengeOverlayDiv = reCaptchaParentContainer.firstChild;
                        if (reCaptchaChallengeOverlayDiv.length) {
                            reCaptchaObserver.disconnect(); // We don't want to observe more DOM changes for better performance
                            resolve(reCaptchaChallengeOverlayDiv); // Returning the overlay div to detect close events
                        }
                    }
                }
            });
        }
        

        最后我们自己实例化了一个观察者并开始观察 DOM 变化:

        const reCaptchaObserver = new MutationObserver(DOMChangeCallbackFunction);
        reCaptchaObserver.observe(targetElement, observerConfig);
        

        2) 第二个挑战是该帖子的主要问题 - 我们如何检测挑战已结束?好吧,我们又需要 MutationObserver 的帮助了。

        detectReCaptchaChallengeAppearance().then(function (reCaptchaChallengeOverlayDiv) {
            var reCaptchaChallengeClosureObserver = new MutationObserver(function () {
                if ((reCaptchaChallengeOverlayDiv.style.visibility === 'hidden') && !grecaptcha.getResponse()) {
                    // TADA!! Do something here as the challenge was either closed by hitting outside of an overlay div OR by pressing ESC key
                    reCaptchaChallengeClosureObserver.disconnect();
                }
            });
            reCaptchaChallengeClosureObserver.observe(reCaptchaChallengeOverlayDiv, {
                attributes: true,
                attributeFilter: ['style']
            });
        });
        

        所以我们所做的就是使用我们在第 1 步中创建的 Promise 获得 Google reCaptcha 挑战覆盖 div,然后我们订阅了覆盖 div 上的“样式”更改。这是因为当挑战结束时 - 谷歌将其淡出。 需要注意的是,当一个人成功解决验证码时,可见性也会被隐藏。这就是我们添加 !grecaptcha.getResponse() 检查的原因。除非挑战得到解决,否则它不会返回任何内容。 差不多就是这样 - 我希望能有所帮助:)

        【讨论】:

        • 如果 reCAPTCHA 已以非英语语言加载,则以下查询选择器很容易失败:reCaptchaParentContainer.querySelectorAll('iframe[title="recaptcha challenge"]');。例如,在 pt-BR 中,标题是“desafio reCAPTCHA”。
        • 谢谢!我已将其更改为更防弹的 'iframe[title*="recaptcha"]'
        猜你喜欢
        • 1970-01-01
        • 2014-10-07
        • 1970-01-01
        • 2017-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多