Hello, I wonder if anyone has successfully integra...
# kobweb
s
Hello, I wonder if anyone has successfully integrated CAPTCHA’s into their site? If so how would I go about it? Currently I am trying to implement reCAPTCHA, but I am struggling with adding a custom script tag. Any pointers are appreciated 🙂
d
Do you have any links to what it looks like to do this with JavaScript?
s
It should be something like this: https://developers.google.com/recaptcha/docs/v3#programmatically_invoke_the_challenge
Copy code
<script src="<https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key>"></script>
Copy code
<script>
      function onClick(e) {
        e.preventDefault();
        grecaptcha.ready(function() {
          grecaptcha.execute('reCAPTCHA_site_key', {action: 'submit'}).then(function(token) {
              // Add your logic to submit to your backend server here.
          });
        });
      }
  </script>
d
So the script you would put in your build.gradle.kts file and then it looks like you might have to write a binding for it
If no one else answers this thread with a code sample (I'm assuming there won't be one), run
kobweb create examples/opengl
somewhere
That's a project that adds a script tag importing opengl stuff and then adds some bindings to JS code.
You know what, you might also be able to add that script directly into the build.gradle.kts file
Give me a sec, I'll try something.
I don't have a CAPTCHA site key. Try something like this?
Copy code
kobweb {
    app {
        index {
            head.add {
                script(src = "<https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key>") {}
                script {
                    unsafe {
                        raw(
                            """
                             function onClick(e) {
                                e.preventDefault();
                                grecaptcha.ready(function() {
                                  grecaptcha.execute('reCAPTCHA_site_key', {action: 'submit'}).then(function(token) {
                                      // Add your logic to submit to your backend server here.
                                  });
                                });
                              }
                            """.trimIndent()
                        )
                    }
                }
            }
        }
    }
}
s
Awesome, I had no idea that was possible. I will give it a shot
d
I didn't read the CAPTCHA docs too carefully so, yeah, let me know. This puts the script tags in the <head> block which I think should be fine? But sometimes these things as you to put scripts in the <body> area.
I'm also a bit nervous about
// Add your logic to submit to your backend server here.
-- that is probably easier to put into your Kotlin code, if I had to guess. But we'll try one thing at a time for now.
The
button
approach could be worth trying to, from the docs. That would look something like:
Copy code
Button(
  Modifier
    .classNames("g-recaptcha")
    .toAttrs { 
       attr("data-sitekey", "your_site_key")
       attr("data-callback", "onSubmit")
       attr("data-action", "submit")
    }
) { Text("Submit") }
and then
Copy code
kobweb {
    app {
        index {
            head.add {
                script(src = "<https://www.google.com/recaptcha/api.js>") {}
                script {
                    unsafe {
                        raw(
                            """
                            function onSubmit(token) {
                               document.getElementById("demo-form").submit();
                            }
                            """.trimIndent()
                        )
                    }
                }
            }
        }
    }
}
and I guess you'd need to have some form element with ID "demo-form" in your code somewhere.
Apologies as I don't know more about the recaptcha APIs or have experience binding to it, so hard to recommend the most effective approach here.
s
Also my first time with recaptcha, but this definitely looks more promising that the i was down before i asked. I will let you know how it goes when I get back to it tomorrow
d
Sure, no rush. The whole JS <-> Kotlin handshake is really tricky and figuring it out can feel like walking blind for a while. Lots of trial and error if you have to do it. But it's also kind of cool when you get it working. Good luck!
I did a talk for droidcon earlier this year, and I spent a few hours trying to figure out how to wrap reveal.js (https://revealjs.com/) with Kotlin. Don't know if it will help, but you can find that code here: https://github.com/bitspittle/droidcon-sf-24/tree/main/site/src/jsMain/kotlin/dev/bitspittle/droidconSf24/bindings/revealjs
s
So it wasn’t that bad to get it to work actually, you provided a lot of the work 😉 Adding the scrips to the head in build script:
Copy code
head.add {
    script(src = "<https://www.google.com/recaptcha/api.js>") {}
    script {
        unsafe {
            raw(
                """
                function onSubmit(token) {
                   document.getElementById("form").submit();
                }
                """.trimIndent()
            )
        }
    }
}
And adding a form with a submit button:
Copy code
Form(
    action = "/",
    attrs = Modifier
        .id("form")
        .toAttrs {
            this.attr("method", "GET")
        }
) {
    Button(
        Modifier
            .classNames("g-recaptcha")
            .attrsModifier {
                attr("data-sitekey", "6Le5PnIqAAAAABbNCnbw4rYNCBBBA03tap29U8s5")
                attr("data-callback", "onSubmit")
                attr("data-action", "submit")
            }
            .toAttrs()
    ) { Text("Submit") }
}
This will submit the captcha and navigate the site to the current path, but with a
g-recaptcha-response
query parameter appended. The token value in this parameter can then be send to my backend which will post it to https://www.google.com/recaptcha/api/siteverify with a secret token in order to determine if the user is credible.
👍 1
d
That's awesome! Thank you for sharing the code and for the update.
s
While this is working, I don’t want the site to refresh in order to get the
g-recaptcha-response
, so I want to see if I can get the other manual trigger approach to work - I will let you know how that goes
d
Good luck. You can also experiment putting code in your
AppEntry
block. At that point, you'll be figuring out how to dance the line between Kotlin and JS. I'm assuming you've seen this but check it out if not: https://kotlinlang.org/docs/js-interop.html
s
So, I am trying to do something like this: JS in build.gradle.kts
Copy code
script {
    unsafe {
        raw(
            """
             function validateUser() {
                 grecaptcha.ready(function () {
                     grecaptcha.execute('6Le5PnIqAAAAABbNCnbw4rYNCBBBA03tap29U8s5', { action: 'submit' }).then(function (token) {
                         console.log("Token in JavaScript:", token);
                         if (window.handleCaptchaToken) {
                             window.handleCaptchaToken(token); // Call the Kotlin function
                         } else {
                             console.error("handleCaptchaToken is not defined.");
                         }
                     });
                 });
             }
            """.trimIndent()
        )
    }
}
I can then invoke this function with a simple button:
Copy code
Button(
    modifier
        .attrsModifier {
            attr("onClick", "validateUser()")
        }
        .toAttrs()
) { Text("Submit Custom") }
And I have registered a Kotlin JS function like this:
Copy code
@JsName("handleCaptchaToken")
fun handleCaptchaToken(token: String) {
    println("Received token in Kotlin: $token")
}
This is where I am at: I am able to invoke the function and retrieve the token. It is succesfully printed to the console. However the JS in the build script is unable to invoke handleCaptchaToken (undefined), even though I can see it is generated and available in the generated site js script:
function handleCaptchaToken(token) {\n  (0,_kotlin_kotlin_stdlib_mjs__WEBPACK_IMPORTED_MODULE_0__.println2shhhgwwt4c61)('Received token in Kotlin: ' + token);\n}
Any ideas on how to target this function from the custom head script?
I am definitely a JS novice, but it should be possible to invoke a function from a body script, from a script defined in the head, right?
d
In general, I think it's easier to wrap JS inside Kotlin rather than calling Kotlin from JS. You can read https://kotlinlang.org/docs/js-to-kotlin-interop.html however if you want to try the other way I would look into trying to use the
external
keyword as a way to wrap and expose whatever the
grecaptcha
object is.
👍 1
s
Great input, thank you. I was able to get it to work like this:
Copy code
external object grecaptcha {
    fun ready(callback: () -> Unit)
    fun execute(siteKey: String, options: dynamic): dynamic
}
Copy code
Button(
    content =  { Text("External Submit") },
    onClick = {
        grecaptcha.ready {
            val options = js("{ action: 'submit' }")
            grecaptcha.execute("6Le5PnIqAAAAABbNCnbw4rYNCBBBA03tap29U8s5", options)
                .then { token: String ->
                    println("Captcha token : $token")
                }
        }
    })
Really learned a lot about Kotlin JS today. The interop seem really powerful!
🙌 1
d
That's awesome! Thanks again for sharing
The interop stuff is powerful and criminally under documented 😁