javascriptrecaptchaclojurescript

Why can't I pass Clojurescript functions as callbacks to Javascript?


I'm trying to use Google's recaptcha in a Clojurescript/Reagent SPA as seen in the code below.

(ns myapp.captcha
  (:require  [reagent.core :as r]
             [cljs.core.async :refer [<! >! chan]])
  (:require-macros [cljs.core.async.macros :refer [go go-loop]]))

(def captcha-ch (chan))

(defn ^:export data-callback [human-proof]
  (go (>! captcha-ch {:captcha-data human-proof})))

(defn ^:export data-expired-callback []
  (go (>! captcha-ch {:captcha-expired true})))

(defn captcha [site-key]
  (let [grecaptcha-script (doto (.createElement js/document "script")
                            (.setAttribute "id" "grecaptcha-script")
                            (.setAttribute "src" "https://www.google.com/recaptcha/api.js"))
        out-ch (chan)
        comp (r/create-class
              {:component-did-mount (fn [this]
                                      (.appendChild (.-body js/document)
                                                    grecaptcha-script))
               :component-will-unmount (fn [this]
                                         (.removeChild (.-body js/document)
                                                       (.getElementById js/document "grecaptcha-script"))
                                         (go (>! captcha-ch {:exit true})))
               :reagent-render (fn [this]
                                 [:div.g-recaptcha
                                  {:data-sitekey site-key
                                   :data-callback "myapp.captcha.data_callback"
                                   :data-expired-callback "myapp.captcha.data_expired_callback"}])})]
    (go-loop []
      (let [msg (<! captcha-ch)]
        (if-not (:exit msg)
          (>! out-ch msg)
          (recur))))

    {:chan out-ch :comp comp}))

When the captcha is solved and data-callback is supposed to be called I get an error saying:

ReCAPTCHA couldn't find user-provided function: myapp.captcha.data_callback

On the other hand if I call myapp.captcha.data_callback from the browser's debugger console the function is visible and executes correctly.

PS: For now please ignore the global chan, which is a different matter. In order to fix that I have to call captcha render explicitly and that puts me in some race conditions apparently related to the order of script loading. I admit that it might be a cleaner approach but for now it's interesting to see what the problem is here.


Solution

  • This is because the Closure compiler obfuscates your code during compilation, including renaming your functions. The easiest solution is first ensuring the compiler optimization respects your function names (or simply disabling the optimization such as via :optimization :none with shadow cljs.

    Next, you want to make sure the function you want to use is exported. This is done with ^:export, such as: (defn ^:export my-exported-fun [] ...)

    Lastly, pass the full namespace when refering to the function, such as myapp.frontend.my-exported-fun.

    Hope this helps the future travelers :)