在Google recaptcha关闭挑战窗口时检测

时间:2017-04-19 06:58:04

标签: javascript callback recaptcha invisible-recaptcha

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

一旦按下按钮,我当前在按钮上放置了一个微调器,该按钮呈现了recaptcha挑战。用户无法通过其他挑战窗口提示。

我以编程方式调用render函数:

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会在关闭挑战画面时调用另一个回调,而用户无需通过选择图像来验证自己。

5 个答案:

答案 0 :(得分:12)

如您所述, 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')
}

答案 1 :(得分:1)

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

Array.from on the Internet Explorer

ie does not support 'includes' method

以及更新的代码:

function 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'] })

            }

答案 2 :(得分:0)

我的解决方案:

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

答案 3 :(得分:0)

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

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

通过这种方式,仅当挑战已完成 并且用户尚未完成挑战时,它才执行所需的代码,因此无法使用grecaptcha.getResponse()

答案 4 :(得分:0)

对于每个不太了解其工作原理的人,这是另一个示例,其中包含一些可能会有用的解释:

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

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

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 challenge"]');

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

然后,我们创建了一个函数,当观察者检测到我们在config对象中指定的特定类型的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 challenge"]');

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

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