web-dev-qa-db-ja.com

GooglereCAPTCHAのチャレンジウィンドウが閉じられたことを検出します

Googleのinvisiblerecaptchaを使用しています。チャレンジウィンドウが閉じられたことを検出する方法はありますか?チャレンジウィンドウとは、検証のためにいくつかの画像を選択する必要があるウィンドウを意味します。

私は現在、ボタンがクリックされると、reCAPTCHAチャレンジをレンダリングしたボタンにスピナーを配置しています。ユーザーに別のチャレンジウィンドウを表示する方法はありません。

プログラムでレンダリング関数を呼び出しています。

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

次のような2つのコールバック関数がverified関数とresetRecaptcha関数に接続されています。

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

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

Grecaptcha.renderには、ユーザーが画像を選択して自分自身を確認せずにチャレンジ画面を閉じたときに呼び出される別のコールバックがあることを期待していました。

11
Tarek

おっしゃるように、APIはこれをサポートしていません機能。

ただし、この機能は自分で追加できます。次のコードは注意して使用できます。GoogleはreCaptchaを変更する可能性があり、そうすることでこのカスタムコードを壊す可能性があります。このソリューションはreCaptchaの2つの特性に依存しているため、コードが機能しない場合は、まずそこを確認してください。

  • ウィンドウ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')
}
14
arcs

IEこのソリューションで機能するには、以下に示す.include()およびArray.from()のポリフィルが必要です。

Internet ExplorerのArray.from

つまり、 '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'] })

            }
1
Doughballs

すべてがどのように機能するかを完全に理解していなかったすべての人のために、ここにあなたが役立つかもしれない説明を含む別の例があります:

したがって、ここには2つの課題があります。

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

まず、Googleiframeの外観で観察するターゲット要素を作成しました。 iframeが追加されるのでdocument.bodyをターゲットにしました:

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)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の「スタイル」の変更をサブスクライブしました。これは、チャレンジが閉じられると、Googleがフェードアウトするためです。人がキャプチャを正常に解決すると、可視性も非表示になることに注意することが重要です。そのため、!grecaptcha.getResponse()チェックを追加しました。チャレンジが解決されない限り、何も返されません。これはほとんどそれです-私はそれが役立つことを願っています:)

0
kosmeln

私の解決策:

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

0
mh77