klaro-org / klaro-js

Klaro Privacy Manager. An open-source, privacy-friendly & compliant consent manager for your website.
https://klaro.org
Other
1.19k stars 253 forks source link

Timing issue with enabled consent #304

Open Jako opened 4 years ago

Jako commented 4 years ago

I got a small issue with recaptcha and the following code on my page:

<script type="text/plain" data-type="application/javascript" data-src="https://www.google.com/recaptcha/api.js?render=…" data-name="recaptcha"></script>
<input type="hidden" name="…">
<input type="hidden" name="…" value="…">
<script type="text/plain" data-type="application/javascript" data-name="recaptcha">
    grecaptcha.ready(function() {
        grecaptcha.execute('…', {action: '…'}).then(function(token) {
            document.querySelector('[name="…"]').value = token;
        });
    });
</script>

After loading the page, an error Uncaught ReferenceError: grecaptcha is not defined occurs. It looks like the second script is executed to early.

At the moment I fix this with wrapping the second script in setTimeout(function() { }, 500);. Is there any better option?

adewes commented 4 years ago

To my knowledge dynamically added script tags (which those script tags are since Klaro is recreating them when the user consents) don't have a fixed execution order (see e.g. https://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order). If you want to ensure the second script only loads when the first one is loaded you could wrap the second script in a function and add that as the onload handler of the first script, like so:

<script type="text/plain" type="application/javascript">
    function loadCaptcha(){
    grecaptcha.ready(function() {
        grecaptcha.execute('…', {action: '…'}).then(function(token) {
            document.querySelector('[name="…"]').value = token;
        });
    });
}
</script>
<script onload="loadCaptcha()" type="text/plain" data-type="application/javascript" data-src="https://www.google.com/recaptcha/api.js?render=…" data-name="recaptcha"></script>
<input type="hidden" name="…">
<input type="hidden" name="…" value="…">

Please note that I execute the original second script before the user consents here (since the code is wrapped in a function it won't do anything until the first script loads). I haven't tested that but I believe it should work. Actually that seems like a pretty common use case, so we might add a way to do this directly via Klaro.

Jako commented 3 years ago

The example code works fine, thanks!

saitho commented 3 years ago

I'm currently running into the same problem but I can't use onload as the dependent JS is coming from a different file (also I'm unable to concatenate those into one file).

Is there a way to ensure a script has been fully loaded before loading the next one? If so, we could add a data-order attribute to allow setting the order in which the scripts have to be included.

<script type="text/plain" data-type="application/javascript" data-src="script1.js" data-name="myscript" data-order="1"></script>
<script type="text/plain" data-type="application/javascript" data-src="script2.js" data-name="myscript" data-order="2"></script>
adewes commented 3 years ago

Hi @saitho! I think this will indeed require some extra logic on the Klaro side, shouldn't be too difficult to implement so we can move it up in the feature queue. Good idea regarding the data-order attribute!