What is the correct way of using Google reCaptcha v3 with turboRouter in October v3

Here is the issue: Google reCaptcha v3 document adds dom. Then user switching between pages turboRouter reRender the content of the document and of course destroys the reCaptcha part of it.

Right now I found one working solution to simply make a createElement(‘script’) with reCAPTCHA… in connect() and then onSubmit() → grecaptcha.ready() → grecaptcha.execute(). This method is working, but I’m not sure if it is a correct approach and what is happening with garbage collector…

Advice would be really helpful,
Thanks in advance

Hey @mrmax

Can you paste a code snippet (use code fences ```) with your hot control definition? We can likely help you fix it up.

Sure @daft, here is how I do this now:

oc.registerControl('leave-review', class extends oc.ControlBase {
    init() {
        ...
    }

    connect() {
        this.invokeReCaptchaScript();
        this.listen('click', '#submit-form', this.onSubmit);
    }

    disconnect() {
    	// Not sure about that. Correct me if I'm wrong, in SPA grecaptcha should act like a global var, so one's inited
    	// should be re-used in other forms. Can be of course dispatched here or in other places. 
        grecaptcha.reset();
    }

    // This is the only solution I've found so that the google reCaptcha badge stays on page 
    // no matter if user is on its first page load or hops through pages with forms.
    invokeReCaptchaScript() {
        let scriptElement = document.createElement('script');
        scriptElement.src = 'https://www.google.com/recaptcha/api.js?render=' + encodeURIComponent(reCaptcha_sitekey);
        scriptElement.async = true;

        // But this keeps loading / re-loading api.js every page (with form) visit (it is ~150kb and it bothers me a lot)
        document.head.appendChild(scriptElement);
    }

    onSubmit(e) {
    	grecaptcha.ready(() => {
            grecaptcha.execute(reCaptcha_sitekey, {action: 'submit_review'}).then((token) => {
            	## action like -> oc.request(token);
            });
        });
        e.preventDefault();
    }
    ...
});

I’ve tried:

{% put script %} This is working, but then user visits second form
Google reCaptcha badge in a bottom-right corner dissappears. But in this case api.js is not loaded second or third time. grecaptcha.ready() grecaptcha.execute also working fine.

It’s basically all about having with badge with terms in a lower-right corner and ability to load api.js from google only ones.

Just to be clear: this goole reCaptcha badge:
image

Is the invoke script non-idempotent? Shouldn’t you be calling it only if it’s missing? Something like:

connect() {
    if (!window.grecaptcha) {
        this.invokeReCaptchaScript();
    }
    
    this.listen('click', '#submit-form', this.onSubmit);
}
1 Like

Good point! But that doesn’t solve the issue that this badge dissappearing then visiting several pages with form and reCaptcha. If there would be the way to set data-turbo-permanent to the badge from google api script?

Sorry, I’m not too familiar with reCaptcha, since I haven’t used it in a while!

The script you posted doesn’t quite show where the captcha script is bound to an element. It shows it is being reset, and it shows the submit logic. Where is the logic that binds the captcha to an element/page?

reCaptcha v3 doesn’t really bound to an element like v1 or v2. Instead it just adds dom to the document with a badge like is shown on the screenshot above. v3 should be bound to the action instead and onSubmit is almost the same as the example in google docs.
I wonder how the Jivo chat or similar third party scripts (or chat from whatsApp business) will work in case of turboRouter in enabled, this might be the same case.

Ah, yes, I see it in the code now. Looking at the docs, I don’t see any obvious mention of calling reset()

Ok, perhaps .reset() is destroying its possible future use. Does commenting out this line help?

// grecaptcha.reset();

It looks like reset() is only used by reCAPTCHA v2

reCaptcha V3 in an invisible captcha, it automagically detects user behaviors and rate it, no need to reload or user’s direct interaction with any UI. reCAPTCHA v3 returns a score for each request without user friction. The score is based on interactions with your site and enables you to take an appropriate action for your site.

let badge;

const badgeStorage = document.createElement('div');

grecaptcha.ready(() => {
    badge = document.querySelector('.grecaptcha-badge').parentElement;
});

addEventListener('page:unload', () => {
    if (badge) {
        badgeStorage.appendChild(badge);
    }
});

addEventListener('page:loaded', () => {
    if (badge) {
        document.body.appendChild(badge);
    }
});
1 Like