Search code examples
javascriptgoogle-apipolymerpolymer-3.x

Polymer 3.0 recaptcha google


Currently i'm using polymer version 3.0, I tried to implement recaptcha v2, but there is a problem to load script: <script src="https://www.google.com/recaptcha/api.js" async defer></script>, polymer cannot load script inside shadow dom. I found webcomponent for captcha here: https://www.webcomponents.org/element/Zecat/google-recaptcha, but that only supports the version 1 and 2.x of polymer. Is there a way to implement recaptcha(checkbox) in polymer 3.0?


Solution

  • The hack I found is to create the necessary script tags during run time and attaching the script to either the document head or body in order to display the reCAPTCHA element. Do this on the firstUpdated() method Follow these steps :

    1: Create script tag for the google recaptcha api

     var script = document.createElement( 'script' );
    script.src= 'https://www.google.com/recaptcha/api.js';
    script.async = true; 
    script.defer = true;
    
    1. Attach the script to the document head document.head.appendChild(script);

    2. Create the div to host the reCAPTCHA also in run time and attach it to the body because it will not be display on the shadowRoot

    var recaptcha = document.createElement('div');
            recaptcha.setAttribute('class','g-recaptcha');
            recaptcha.setAttribute('data-theme','light');
            recaptcha.setAttribute('data-callback','recaptchaCallback');
            recaptcha.setAttribute('data-sitekey','site_key');
            //attaching recaptcha element to document body
            document.body.appendChild(recaptcha);
    

    At this point the recaptcha element will display on the document. But the only issue will be the position of where it is. So question here is how to display it to where the shdadowRoot is loaded on the DOM. The hack for this is to get the position of the shadowRoot in runtime as well and attach a style attribute to the recaptcha div we just created in which we set the position to absoulte postion:absolute and add the bottom and left position based on where the shadowRoot is located. It will look like this:

    let rBody =this.shadowRoot.querySelector("#rBody"); //element in the shadowRoot
    
    let topPosition = rBody.getBoundingClientRect().top;
    let leftPosition = rBody.getBoundingClientRect().left;
    
    recaptcha.setAttribute('style','position:absolute;top:'+ topPosition+ 'px;'+ 'left:'+ leftPosition + 'px;');
    

    At this point the recaptcha should have positioned itself accordingly. Hopefully there is no animations to the form you are using because the recaptcha will always stay in place, but it works.

    So another critical factor is knowing whether the verification of the recaptcha was successful. We also have to do another hack for this. We do this by also creating a script tag at run time and the text content for it embed a function that will be called by the data-callback attribute by google. the callback function is only called when it is successful and that's good enough for us. For more info check the docs here It should look something like this:

    recaptcha.setAttribute('data-callback','recaptchaCallback');
    //script for callback
        let callBackScript =  document.createElement( 'script' );
        callBackScript.textContent = ' function recaptchaCallback(){var recapValue = document.createElement("p"); recapValue.setAttribute("id","recapValue"); recapValue.setAttribute("pass","r-true");document.body.appendChild(recapValue);console.log("Recaptcha callback passed");}';
    
        document.body.appendChild(callBackScript);
    

    the callback function I am also creating a p element and setting a custom attribute in which if the element is actually created it means that the verification was successful in which you can confirm by creating a method to check if the created element, in this case p if its on the dom by :

     let recaptchaValue = document.querySelector('#recapValue');
        if(recaptchaValue !=null){
          return recaptchaValue.getAttribute("pass");
        }else{
          return null; 
        }
    

    Hopefully this will help you out. Unfortunately with Polymer 3.0 we have to resort to these hacks because there is no node package for our particular use case.