import { IReCaptchaInstance , IRenderParameters } from './grecaptcha/grecaptcha' ; import { ReCaptchaInstance } from './ReCaptchaInstance' ; enum ELoadingState { NOT_LOADED , LOADING , LOADED } /**
 * This is a loader which takes care about loading the
 * official recaptcha script (https://www.google.com/recaptcha/api.js).
 *
 * The main method  ReCaptchaLoader also
 * prevents loading the recaptcha script multiple times.
  class ReCaptchaLoader { private static loadingState : ELoadingState = null ; private static instance : ReCaptchaInstance = null ; private static instanceSiteKey : string = null ; private static successfulLoadingConsumers : Array < ( instance : ReCaptchaInstance ) => void > = [ ] ; private static errorLoadingRunnable : Array < ( reason : Error ) => void > = [ ] ; private static readonly SCRIPT_LOAD_DELAY = 25 ; param siteKey param options return public static load ( siteKey , options = { } ) { if ( typeof document === 'undefined' ) { return Promise . reject ( new Error ( 'This is a library for the browser!' ) ) ; } if ( ReCaptchaLoader . getLoadingState ( ) === ELoadingState . LOADED ) { if ( ReCaptchaLoader . instance . getSiteKey ( ) === siteKey ) { return Promise . resolve ( ReCaptchaLoader . instance ) ; } else { return Promise . reject ( new Error ( 'reCAPTCHA already loaded with different site key!' ) ) ; } } if ( ReCaptchaLoader . getLoadingState ( ) === ELoadingState . LOADING ) { if ( siteKey !== ReCaptchaLoader . instanceSiteKey ) { return Promise . reject ( new Error ( 'reCAPTCHA already loaded with different site key!' ) ) ; } return new Promise < ReCaptchaInstance > ( ( resolve , reject ) => { ReCaptchaLoader . successfulLoadingConsumers . push ( ( instance ) => resolve ( instance ) ) ; ReCaptchaLoader . errorLoadingRunnable . push ( ( reason ) => reject ( reason ) ) ; } ) ; } ReCaptchaLoader . instanceSiteKey = siteKey ; ReCaptchaLoader . setLoadingState ( ELoadingState . LOADING ) ; const loader = new ReCaptchaLoader ( ) ; return new Promise ( ( resolve , reject ) => { loader . loadScript ( siteKey , options . useRecaptchaNet || false , options . useEnterprise || false , options . renderParameters ? options . renderParameters : { } , options . customUrl ) . then ( ( ) => { ReCaptchaLoader . setLoadingState ( ELoadingState . LOADED ) ; const widgetID = loader . doExplicitRender ( grecaptcha , siteKey , options . explicitRenderParameters ? options . explicitRenderParameters : { } , options . useEnterprise || false ) ; const instance = new ReCaptchaInstance ( siteKey , widgetID , grecaptcha ) ; ReCaptchaLoader . successfulLoadingConsumers . forEach ( ( v ) => v ( instance ) ) ; ReCaptchaLoader . successfulLoadingConsumers = [ ] ; if ( options . autoHideBadge ) { instance . hideBadge ( ) ; } ReCaptchaLoader . instance = instance ; resolve ( instance ) ; } ) . catch ( ( error ) => { ReCaptchaLoader . errorLoadingRunnable . forEach ( ( v ) => v ( error ) ) ; ReCaptchaLoader . errorLoadingRunnable = [ ] ; reject ( error ) ; } ) ; } ) ; } public static getInstance ( ) { return ReCaptchaLoader . instance ; } param state private static setLoadingState ( state ) { ReCaptchaLoader . loadingState = state ; } /**
     * Will return the current loading state. If no loading state is globally set
     * the NO_LOADED state is set as default.
     */ private static getLoadingState ( ) { if ( ReCaptchaLoader . loadingState === null ) { return ELoadingState . NOT_LOADED ; } else { return ReCaptchaLoader . loadingState ; } } param siteKey param useRecaptchaNet param useEnterprise param renderParameters param customUrl private loadScript ( siteKey , useRecaptchaNet = false , useEnterprise = false , renderParameters = { } , customUrl = '' ) { const scriptElement = document . createElement ( 'script' ) ; scriptElement . setAttribute ( 'recaptcha-v3-script' , '' ) ; let scriptBase = 'https://www.google.com/recaptcha/api.js' ; if ( useRecaptchaNet ) { if ( useEnterprise ) { scriptBase = 'https://recaptcha.net/recaptcha/enterprise.js' ; } else { scriptBase = 'https://recaptcha.net/recaptcha/api.js' ; } } if ( useEnterprise ) { scriptBase = 'https://www.google.com/recaptcha/enterprise.js' ; } if ( customUrl ) { scriptBase = customUrl ; } if ( renderParameters . render ) { renderParameters . render = undefined ; } const parametersQuery = this . buildQueryString ( renderParameters ) ; scriptElement . src = scriptBase + '?render=explicit' + parametersQuery ; return new Promise < HTMLScriptElement > ( ( resolve , reject ) => { scriptElement . addEventListener ( 'load' , this . waitForScriptToLoad ( ( ) => { resolve ( scriptElement ) ; } , useEnterprise ) , false ) ; scriptElement . onerror = ( error ) : void => { ReCaptchaLoader . setLoadingState ( ELoadingState . NOT_LOADED ) ; reject ( error ) ; } ; document . head . appendChild ( scriptElement ) ; } ) ; } param parameters private buildQueryString ( parameters ) { const parameterKeys = Object . keys ( parameters ) ; if ( parameterKeys . length < 1 ) { return '' ; } return '&' + Object . keys ( parameters ) . filter ( ( parameterKey ) => { return ! ! parameters [ parameterKey ] ; } ) . map ( ( parameterKey ) => { return parameterKey + '=' + parameters [ parameterKey ] ; } ) . join ( '&' ) ; } param callback private waitForScriptToLoad ( callback , useEnterprise ) { return ( ) : void => { if ( window . grecaptcha === undefined ) { setTimeout ( ( ) => { this . waitForScriptToLoad ( callback , useEnterprise ) ; } , ReCaptchaLoader . SCRIPT_LOAD_DELAY ) ; } else { if ( useEnterprise ) { window . grecaptcha . enterprise . ready ( ( ) => { callback ( ) ; } ) ; } else { window . grecaptcha . ready ( ( ) => { callback ( ) ; } ) ; } } } ; } param grecaptcha param siteKey param parameters return private doExplicitRender ( grecaptcha , siteKey , parameters , isEnterprise ) { const augmentedParameters = { sitekey : siteKey , badge : parameters . badge , size : parameters . size , tabindex : parameters . tabindex } ; if ( parameters . container ) { if ( isEnterprise ) { return grecaptcha . enterprise . render ( parameters . container , augmentedParameters ) ; } else { return grecaptcha . render ( parameters . container , augmentedParameters ) ; } } else { if ( isEnterprise ) { return grecaptcha . enterprise . render ( augmentedParameters ) ; } else { return grecaptcha . render ( augmentedParameters ) ; } } } } /**
 * An interface for all available options for the
 * reCAPTCHA loader.
 */ export interface IReCaptchaLoaderOptions { /**
     * By default the loader uses "google.com", with this
     * option set to `true` it will use "recaptcha.net".
     * (See: https://github.com/AurityLab/recaptcha-v3/pull/2)
     */ useRecaptchaNet ? : boolean ; /**
     * By default the loader uses "https://www.google.com/recaptcha/api.js" api src, with this
     * option set to `true` it will use "https://www.google.com/recaptcha/enterprise.js"
     * api src instead which is the enterprise version of recaptcha API. The loader will also handle differences
     * in response.
     */ useEnterprise ? : boolean ; /**
     * Will automatically hide the badge after loading
     * recaptcha. Warning: The usage of this option is only
     * allowed if you follow the official guide for hiding
     * the badge from Google:
     * https://developers.google.com/recaptcha/docs/faq#id-like-to-hide-the-recaptcha-v3-badge-what-is-allowedl
     */ autoHideBadge ? : boolean ; /**
     * Defines additional parameters for the rendering process.
     * The parameters should be defined as key/value pair.
     *
     * Known possible parameters:
     * `hl` -> Will set the language of the badge.
     */ renderParameters ? : { [ key ] : string ; } ; /**
     * Defines the additional parameters for the explicit rendering process.
     */ explicitRenderParameters ? : IReCaptchaExplicitRenderParameters ; /**
     * Defines a custom url for ReCaptcha JS file.
     * Useful when self hosting or proxied ReCaptcha JS file.
     * https://github.com/AurityLab/recaptcha-v3/issues/76
     */ customUrl ? : string ; } /**
 * Describes the parameters for the explicit rendering call. This gives the possibility to set explicit positioning
 * for the badge.
 */ export interface IReCaptchaExplicitRenderParameters { container ? : string | Element ; badge ? : 'bottomright' | 'bottomleft' | 'inline' ; size ? : 'invisible' ; tabindex ? : number ; } /**
 * Only export the recaptcha load and getInstance method to
 * avoid confusion with the class constructor.
 */ export const load = ReCaptchaLoader . load ; export const getInstance = ReCaptchaLoader . getInstance ;