我正在使用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会在关闭挑战画面时调用另一个回调,而用户无需通过选择图像来验证自己。
答案 0 :(得分:12)
如您所述, API不支持此功能。
但是,您可以自己添加此功能。您可以谨慎使用以下代码,Google可能会更改其reCaptcha,并通过这样做来破坏此自定义代码。该解决方案依赖于reCaptcha的两个特性,因此如果代码不起作用,请先查看:
iframe
src:包含“google.com/recaptcha/api2/bframe”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()检查的原因。除非解决了挑战,否则它将不会返回任何内容。 差不多就可以了-我希望能有所帮助:)