繁体   English   中英

是否有任何 recaptcha v2 关闭事件?

[英]Is there any recaptcha v2 close event?

我正在用这样的代码渲染 grecaptcha

let callback;
const p = new Promise((resolve) => callback = (result) => resolve(result));

grecaptcha.render(el, {
    sitekey: window.settings.recaptchaKey,
    size: "invisible",
    type: "image",
    callback: result => callback(result),
    badge: "inline"
});

const key = await p;

一切正常,但是如果用户单击recaptcha modal 的背景,recaptcha 将关闭并且我无法检测到它,所以我等待无限响应

我需要某种事件或回调来检测它何时关闭

不幸的是,Google 没有 API 事件来跟踪这一点,但我们可以使用Mutation Observer Web API 自己跟踪 Google API 的 DOM 更改。

我们在这里有两个挑战。

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() 检查的原因。 除非挑战得到解决,否则它不会返回任何内容。 差不多就是这样 - 我希望能有所帮助:)

作为一个肮脏的解决方法,我们可以设置超时并等待recaptcha iframe显示然后等待它隐藏

我制作了进行所有操作的模块

它取决于 jquery 和全局 recaptcha

我像这样使用它

try {
    key = await captcha(elementToBind, 'yoursitekey');
}
catch (error) {
    console.log(error); // when recaptcha canceled it will print captcha canceled
}

不好的部分,当谷歌更改 html 结构中的某些内容时,它可能会中断

模块代码

/* global grecaptcha */
import $ from "jquery";

let callback = () => {};
let hideCallback = () => {};

export default function captcha (el, sitekey) {
    const $el = $(el);
    el = $el[0];
    let captchaId = $el.attr("captcha-id");
    let wrapper;
    if (captchaId == null) {
        captchaId = grecaptcha.render(el, {
            sitekey,
            size: "invisible",
            type: "image",
            callback: result => callback(result),
            badge: "inline",
        });
        $(el).attr("captcha-id", captchaId);
    }
    else {
        grecaptcha.reset(captchaId);
    }
    const waitForWrapper = setInterval(() => {
        // first we search for recaptcha iframe
        const iframe = $("iframe").filter((idx, iframe) => iframe.src.includes("recaptcha/api2/bframe"));
        iframe.toArray().some(iframe => {
            const w = $(iframe).closest("body > *");
            // find the corresponding iframe for current captcha
            if (w[0] && !w[0].hasAttribute("captcha-id") || w.attr("captcha-id") == captchaId) {
                w.attr("captcha-id", captchaId);
                wrapper = w; // save iframe wrapper element
                clearInterval(waitForWrapper);
                return true;
            }
        });
    }, 100);
    const result = new Promise((resolve, reject) => {
        callback = (result) => {
            clearInterval(waitForHide);
            resolve(result);
        };
        hideCallback = (result) => {
            clearInterval(waitForHide);
            reject(result);
        };
    });
    grecaptcha.execute(captchaId);
    let shown = false;
    const waitForHide = setInterval(() => {
        if (wrapper) { // if we find iframe wrapper
            if (!shown) {
                // waiting for captcha to show
                if (wrapper.css("visibility") !== "hidden") {
                    shown = true;
                    console.log("shown");
                }
            }
            else {
                // now waiting for it to hide
                if (wrapper.css("visibility") === "hidden") {
                    console.log("hidden");
                    hideCallback(new Error("captcha canceled"));
                }
            }
        }
    }, 100);
    return result;
}

我创建了一个 dom 观察者来检测验证码何时附加到 DOM,然后我断开它(因为不再需要它)并将点击处理程序添加到它的背景元素。

请记住,此解决方案对 DOM 结构的任何更改都很敏感,因此如果 google 出于任何原因决定更改它,它可能会中断。

还记得清理观察者/听众,在我的情况下(反应)我在 useEffect 的清理功能中进行。

    const captchaBackgroundClickHandler = () => {
        ...do whatever you need on captcha cancel
    };

    const domObserver = new MutationObserver(() => {
        const iframe = document.querySelector("iframe[src^=\"https://www.google.com/recaptcha\"][src*=\"bframe\"]");

        if (iframe) {
            domObserver.disconnect();

            captchaBackground = iframe.parentNode?.parentNode?.firstChild;
            captchaBackground?.addEventListener("click", captchaBackgroundClickHandler);
        }
    });

    domObserver.observe(document.documentElement || document.body, { childList: true, subtree: true });

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM