16

我正在使用 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 有另一个回调,当挑战屏幕关闭时调用,而用户没有通过选择图像来验证自己。

4

5 回答 5

17

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

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

  • 窗口iframesrc:包含“google.com/recaptcha/api2/bframe”
  • CSSopacity属性:关闭窗口时改为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')
}
于 2017-07-08T09:31:47.883 回答
1

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

Internet Explorer 上的 Array.from

ie 不支持 'includes' 方法

和更新的代码:

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

            }
于 2019-02-27T14:33:55.853 回答
1

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

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

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

于 2019-11-08T22:46:28.073 回答
0

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

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

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

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

于 2019-12-24T23:21:31.240 回答
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()

于 2019-08-16T11:51:56.347 回答