3

So with LWC, as with aura, you cannot include scripts directly in the component. When viewing the google JS is specifically states to not copy and paste...so one should not put it in a static resource.

This led me to the iFrame implementation described HERE and adapted as needed.

The problem, as implemented, requires the iFrame to take up a lot of space on the chance that the reCaptcha challenge tiles are presented as shown below:

enter image description here and enter image description here

This is less than desired. What I would like to see is the recaptcha take up less space then if the challenge is presented either show it in a pop-up over the parent or something else that would be more in the flow. If I set the iFrame width and not height this is what happens:

enter image description here

LWC Div

    <div>
        <iframe src={navigateTo} name="captchaFrame" onload={captchaLoaded} width="310px" style="border-style: none;"></iframe>
    </div>

iFrame SRC

<html>
    <head>
        <title>reCAPTCHA demo: Simple page</title>
        <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
    </head>
    <body>
        <form action="?" method="POST">
            <div id="html_element"></div>
            <br/>
            <input type="submit" value="Submit" style="display: none">
        </form>
        <script type="text/javascript">

            var verifyCallback = function(response) {
                parent.postMessage("Unlock", "https://xxxx.force.com");
            };
            var onloadCallback = function() {
                grecaptcha.render('html_element', {
                    'sitekey' : 'redacted',
                    'size' : 'normal',
                    'callback' : verifyCallback
                });
            };

        </script>
    </body>
</html>

So the question again:

  1. How to get the reCaptcha to take up the appropriate space on load and when/if the challenge is presented.
  2. Bonus if there was a way to get the challenge to show in a pop-up over the parent of the framed window.

Right no, I am thinking that LWC is simply out of the question for this use case and a straight VF page is best given the limitations. Seems an implementation of reCaptcha should be part of both aura and LWC IMHO

1
  • I am trying also to solve this problem, do you have any working solution? Commented Aug 27, 2020 at 6:36

2 Answers 2

4

I was able to workaround this in aura by means of using MutationObserver in VF page. If the captcha is active, VF page sends post message to parent and parent increases heigh of the iframe. If the captcha is not active, it reduces the heigh of the iframe. I can provide code of this workaround for aura, you can definitely adapt it for LWC.

ReCaptcha.vfp

<apex:page showHeader="false" sidebar="false">
    <html>
    <head>
        <script type="text/javascript">
            var verifyCallback = function (response) {
                let hostURL = window.parent.location.protocol + '//' + window.parent.location.hostname,
                    recaptchaResponse = document.getElementById('g-recaptcha-response').value;
                parent.postMessage({
                    action: 'unlock',
                    response: recaptchaResponse
                }, hostURL);
            };
            var onloadCallback = function () {
                grecaptcha.render('html_element', {
                    'sitekey': 'your_sitekey',
                    'callback': verifyCallback
                });
            };

            // this function checks if captcha is active and send post message to parent to increse heigh of iframe
            var observer = new MutationObserver(function (mutations) {
                let hostURL = window.parent.location.protocol + '//' + window.parent.location.hostname;
                if (document.getElementById('captcha') && document.getElementById('captcha').nextSibling && document.getElementById('captcha').nextSibling.nextSibling) {
                    const isCaptchaVisible = document.getElementById('captcha').nextSibling.nextSibling.style.visibility;
                    if (isCaptchaVisible) {
                        parent.postMessage({ 'captchaVisible': isCaptchaVisible }, hostURL);
                    }
                }
            });
            var config = { subtree: true, childList: true };
            observer.observe(document.body, config);

        </script>
        <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async="" defer="" />
    </head>

    <body>
        <form action="?" method="POST">
            <div id="html_element" style="position:absolute transform: scale(0.74);transform-origin: 0 0;-webkit-transform:scale(0.74);-webkit-transform-origin: 0 0"></div>
            <br/>
            <input type="submit" value="Submit" style="display:none" />
        </form>
        <div id="captcha" />
    </body>
    </html>
</apex:page>

in aura component, there is a listener on the message from iframe and CSS class for styling active and inactive captcha.

reCaptcha.cmp

<aura:component access="public">
    <aura:handler name="init" value="{!this}" action="{!c.onInit}" />
    <iframe id="vfFrame" class="reCaptchaSmall" src="../apex/ReCaptcha" scrolling="no" frameborder="0"
        allowtransparency="true" width="100%" />
</aura:component>

reCAptchaController.js

({
    onInit: function (cmp, event, helper) {
        let vfOrigin = location.protocol + '//' + location.hostname;
        window.addEventListener('message', function (event) {
            if (event.origin !== vfOrigin) {
                return;
            }
            if (event.data.captchaVisible) {
                let captchEl = document.getElementById('vfFrame');
                if(event.data.captchaVisible === 'visible'){
                    captchEl.classList.add('reCaptchaBig');
                    captchEl.classList.remove('reCaptchaSmall');
                } else {
                    captchEl.classList.remove('reCaptchaBig');
                    captchEl.classList.add('reCaptchaSmall');
                }
            }
            if (event.data.action === 'unlock') {
                // passing captcha is handled here. event.data.response - is a key of captcha
            }
        }, false);
    }
})

reCaptcah.css

.THIS.reCaptchaSmall{
    height: 60px;
}
.THIS.reCaptchaBig{
    height: 500px;
}
2
  • Interesting. Will give this a shot...
    – Eric
    Commented Jul 11, 2019 at 5:49
  • @Oleksandr this solution does not work for me The Javasript does not find document.getElementById('captcha') and I do not see any other element with Id there. is there anything I can do to make it work? Commented Aug 25, 2020 at 12:15
0

Note the answer from @Oleksandr mostly worked for me, however the adding of classes wasn't working. So instead of these lines in the JS controller

if(event.data.captchaVisible === 'visible'){
  captchEl.classList.add('reCaptchaBig');
  captchEl.classList.remove('reCaptchaSmall');
} else {
  captchEl.classList.remove('reCaptchaBig');
  captchEl.classList.add('reCaptchaSmall');
}

I instead used

if(event.data.captchaVisible === 'visible'){
  captchEl.height = 500;
} else {
  captchEl.height = 60;
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .