UNPKG

ng-recaptcha-2

Version:

Angular component for Google reCAPTCHA for Angular v18 and onwards

548 lines (534 loc) 27.8 kB
import * as i0 from '@angular/core'; import { InjectionToken, PLATFORM_ID, Inject, Optional, Injectable, model, output, HostBinding, ChangeDetectionStrategy, Component, NgModule, forwardRef, Directive } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { of, BehaviorSubject, Subject, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; /** @deprecated Use `LOADER_OPTIONS` instead. See `RecaptchaLoaderOptions.onBeforeLoad` */ const RECAPTCHA_LANGUAGE = new InjectionToken("recaptcha-language"); /** @deprecated Use `LOADER_OPTIONS` instead. See `RecaptchaLoaderOptions.onBeforeLoad` */ const RECAPTCHA_BASE_URL = new InjectionToken("recaptcha-base-url"); /** @deprecated Use `LOADER_OPTIONS` instead. See `RecaptchaLoaderOptions.onBeforeLoad` */ const RECAPTCHA_NONCE = new InjectionToken("recaptcha-nonce-tag"); const RECAPTCHA_SETTINGS = new InjectionToken("recaptcha-settings"); const RECAPTCHA_V3_SITE_KEY = new InjectionToken("recaptcha-v3-site-key"); /** * See the documentation for `RecaptchaLoaderOptions`. */ const RECAPTCHA_LOADER_OPTIONS = new InjectionToken("recaptcha-loader-options"); function loadScript(renderMode, onBeforeLoad, onLoaded, { url, lang, nonce } = {}) { window.ng2recaptchaloaded = () => { onLoaded(grecaptcha); }; const script = document.createElement("script"); script.innerHTML = ""; const { url: baseUrl, nonce: onBeforeLoadNonce } = onBeforeLoad(new URL(url || "https://www.google.com/recaptcha/api.js")); baseUrl.searchParams.set("render", renderMode === "explicit" ? renderMode : renderMode.key); baseUrl.searchParams.set("onload", "ng2recaptchaloaded"); baseUrl.searchParams.set("trustedtypes", "true"); if (lang) { baseUrl.searchParams.set("hl", lang); } script.src = baseUrl.href; const nonceValue = onBeforeLoadNonce || nonce; if (nonceValue) { script.setAttribute("nonce", nonceValue); } script.async = true; script.defer = true; document.head.appendChild(script); } function newLoadScript({ v3SiteKey, onBeforeLoad, onLoaded, }) { const renderMode = v3SiteKey ? { key: v3SiteKey } : "explicit"; loader.loadScript(renderMode, onBeforeLoad, onLoaded); } const loader = { loadScript, newLoadScript }; function toNonNullObservable(subject) { return subject.asObservable().pipe(filter((value) => value !== null)); } class RecaptchaLoaderService { /** * @internal * @nocollapse */ static { this.ready = null; } constructor( // eslint-disable-next-line @typescript-eslint/ban-types platformId, // eslint-disable-next-line deprecation/deprecation language, // eslint-disable-next-line deprecation/deprecation baseUrl, // eslint-disable-next-line deprecation/deprecation nonce, v3SiteKey, options) { this.platformId = platformId; this.language = language; this.baseUrl = baseUrl; this.nonce = nonce; this.v3SiteKey = v3SiteKey; this.options = options; const subject = this.init(); this.ready = subject ? toNonNullObservable(subject) : of(); } /** @internal */ init() { if (RecaptchaLoaderService.ready) { return RecaptchaLoaderService.ready; } if (!isPlatformBrowser(this.platformId)) { return undefined; } const subject = new BehaviorSubject(null); RecaptchaLoaderService.ready = subject; loader.newLoadScript({ v3SiteKey: this.v3SiteKey, onBeforeLoad: (url) => { if (this.options?.onBeforeLoad) { return this.options.onBeforeLoad(url); } const newUrl = new URL(this.baseUrl ?? url); if (this.language) { newUrl.searchParams.set("hl", this.language); } return { url: newUrl, nonce: this.nonce, }; }, onLoaded: (recaptcha) => { let value = recaptcha; if (this.options?.onLoaded) { value = this.options.onLoaded(recaptcha); } subject.next(value); }, }); return subject; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaLoaderService, deps: [{ token: PLATFORM_ID }, { token: RECAPTCHA_LANGUAGE, optional: true }, { token: RECAPTCHA_BASE_URL, optional: true }, { token: RECAPTCHA_NONCE, optional: true }, { token: RECAPTCHA_V3_SITE_KEY, optional: true }, { token: RECAPTCHA_LOADER_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaLoaderService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaLoaderService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RECAPTCHA_LANGUAGE] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RECAPTCHA_BASE_URL] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RECAPTCHA_NONCE] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RECAPTCHA_V3_SITE_KEY] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RECAPTCHA_LOADER_OPTIONS] }] }] }); let nextId = 0; class RecaptchaComponent { constructor(elementRef, loader, zone, settings) { this.elementRef = elementRef; this.loader = loader; this.zone = zone; this.id = model(`ngrecaptcha-${nextId++}`, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ [])); this.siteKey = model(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "siteKey" }] : /* istanbul ignore next */ [])); this.theme = model(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "theme" }] : /* istanbul ignore next */ [])); this.type = model(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "type" }] : /* istanbul ignore next */ [])); this.size = model(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "size" }] : /* istanbul ignore next */ [])); this.tabIndex = model(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ [])); this.badge = model(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "badge" }] : /* istanbul ignore next */ [])); this.errorMode = model("default", /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "errorMode" }] : /* istanbul ignore next */ [])); this.resolved = output(); /** * @deprecated `(error) output will be removed in the next major version. Use (errored) instead */ // eslint-disable-next-line @angular-eslint/no-output-native this.error = output(); this.errored = output(); /** @internal */ this.widget = null; /** @internal */ this.executeRequested = false; if (settings) { this.siteKey.set(settings.siteKey); if (settings.theme) { this.theme.set(settings.theme); } if (settings.type) { this.type.set(settings.type); } if (settings.size) { this.size.set(settings.size); } if (settings.badge) { this.badge.set(settings.badge); } } } ngAfterViewInit() { this.subscription = this.loader.ready.subscribe((grecaptcha) => { if (grecaptcha != null && grecaptcha.render instanceof Function) { this.grecaptcha = grecaptcha; this.renderRecaptcha(); } }); } ngOnDestroy() { // reset the captcha to ensure it does not leave anything behind // after the component is no longer needed this.grecaptchaReset(); if (this.subscription) { this.subscription.unsubscribe(); } } /** * Executes the invisible recaptcha. * Does nothing if component's size is not set to "invisible". */ execute() { if (this.size() !== "invisible") { return; } if (this.widget != null) { void this.grecaptcha?.execute(this.widget); } else { // delay execution of recaptcha until it actually renders this.executeRequested = true; } } reset() { if (this.widget != null) { if (this.grecaptcha?.getResponse(this.widget)) { // Only emit an event in case if something would actually change. // That way we do not trigger "touching" of the control if someone does a "reset" // on a non-resolved captcha. this.resolved.emit(null); } this.grecaptchaReset(); } } /** * ⚠️ Warning! Use this property at your own risk! * * While this member is `public`, it is not a part of the component's public API. * The semantic versioning guarantees _will not be honored_! Thus, you might find that this property behavior changes in incompatible ways in minor or even patch releases. * You are **strongly advised** against using this property. * Instead, use more idiomatic ways to get reCAPTCHA value, such as `resolved` EventEmitter, or form-bound methods (ngModel, formControl, and the likes).å */ get __unsafe_widgetValue() { return this.widget != null && this.grecaptcha ? this.grecaptcha.getResponse(this.widget) : null; } /** @internal */ expired() { this.resolved.emit(null); } /** @internal */ onError(args) { // eslint-disable-next-line deprecation/deprecation this.error.emit(args); this.errored.emit(args); } /** @internal */ captchaResponseCallback(response) { this.resolved.emit(response); } /** @internal */ grecaptchaReset() { this.zone.runOutsideAngular(() => this.widget && this.grecaptcha?.reset(this.widget)); } /** @internal */ renderRecaptcha() { // This `any` can be removed after @types/grecaptcha get updated const renderOptions = { badge: this.badge(), callback: (response) => { this.zone.run(() => this.captchaResponseCallback(response)); }, "expired-callback": () => { this.zone.run(() => this.expired()); }, sitekey: this.siteKey(), size: this.size(), tabindex: this.tabIndex(), theme: this.theme(), type: this.type(), }; if (this.errorMode() === "handled") { renderOptions["error-callback"] = (...args) => { this.zone.run(() => this.onError(args)); }; } this.widget = this.grecaptcha?.render(this.elementRef.nativeElement, renderOptions) ?? null; if (this.executeRequested === true) { this.executeRequested = false; this.execute(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaComponent, deps: [{ token: i0.ElementRef }, { token: RecaptchaLoaderService }, { token: i0.NgZone }, { token: RECAPTCHA_SETTINGS, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.1", type: RecaptchaComponent, isStandalone: false, selector: "re-captcha", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, siteKey: { classPropertyName: "siteKey", publicName: "siteKey", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, errorMode: { classPropertyName: "errorMode", publicName: "errorMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { id: "idChange", siteKey: "siteKeyChange", theme: "themeChange", type: "typeChange", size: "sizeChange", tabIndex: "tabIndexChange", badge: "badgeChange", errorMode: "errorModeChange", resolved: "resolved", error: "error", errored: "errored" }, host: { properties: { "attr.id": "this.id" } }, exportAs: ["reCaptcha"], ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.Eager }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaComponent, decorators: [{ type: Component, args: [{ exportAs: "reCaptcha", selector: "re-captcha", template: ``, changeDetection: ChangeDetectionStrategy.Eager, standalone: false, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: RecaptchaLoaderService }, { type: i0.NgZone }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RECAPTCHA_SETTINGS] }] }], propDecorators: { id: [{ type: HostBinding, args: ["attr.id"] }], siteKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "siteKey", required: false }] }, { type: i0.Output, args: ["siteKeyChange"] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }, { type: i0.Output, args: ["themeChange"] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }, { type: i0.Output, args: ["typeChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }, { type: i0.Output, args: ["sizeChange"] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }, { type: i0.Output, args: ["tabIndexChange"] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }, { type: i0.Output, args: ["badgeChange"] }], errorMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMode", required: false }] }, { type: i0.Output, args: ["errorModeChange"] }], resolved: [{ type: i0.Output, args: ["resolved"] }], error: [{ type: i0.Output, args: ["error"] }], errored: [{ type: i0.Output, args: ["errored"] }] } }); class RecaptchaCommonModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaCommonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaCommonModule, declarations: [RecaptchaComponent], exports: [RecaptchaComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaCommonModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaCommonModule, decorators: [{ type: NgModule, args: [{ declarations: [RecaptchaComponent], exports: [RecaptchaComponent], }] }] }); class RecaptchaModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaModule, imports: [RecaptchaCommonModule], exports: [RecaptchaComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaModule, providers: [RecaptchaLoaderService], imports: [RecaptchaCommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaModule, decorators: [{ type: NgModule, args: [{ exports: [RecaptchaComponent], imports: [RecaptchaCommonModule], providers: [RecaptchaLoaderService], }] }] }); /** * The main service for working with reCAPTCHA v3 APIs. * * Use the `execute` method for executing a single action, and * `onExecute` observable for listening to all actions at once. */ class ReCaptchaV3Service { constructor(zone, recaptchaLoader, siteKey) { this.recaptchaLoader = recaptchaLoader; this.zone = zone; this.siteKey = siteKey; this.init(); } get onExecute() { if (!this.onExecuteSubject || !this.onExecuteObservable) { this.onExecuteSubject = new Subject(); this.onExecuteObservable = this.onExecuteSubject.asObservable(); } return this.onExecuteObservable; } get onExecuteError() { if (!this.onExecuteErrorSubject || !this.onExecuteErrorObservable) { this.onExecuteErrorSubject = new Subject(); this.onExecuteErrorObservable = this.onExecuteErrorSubject.asObservable(); } return this.onExecuteErrorObservable; } /** * Executes the provided `action` with reCAPTCHA v3 API. * Use the emitted token value for verification purposes on the backend. * * For more information about reCAPTCHA v3 actions and tokens refer to the official documentation at * https://developers.google.com/recaptcha/docs/v3. * * @param {string} action the action to execute * @returns {Observable<string>} an `Observable` that will emit the reCAPTCHA v3 string `token` value whenever ready. * The returned `Observable` completes immediately after emitting a value. */ execute(action) { const subject = new Subject(); if (!this.grecaptcha) { if (!this.actionBacklog) { this.actionBacklog = []; } this.actionBacklog.push([action, subject]); } else { this.executeActionWithSubject(action, subject); } return subject.asObservable(); } /** @internal */ executeActionWithSubject(action, subject) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const onError = (error) => { this.zone.run(() => { subject.error(error); if (this.onExecuteErrorSubject) { // We don't know any better at this point, unfortunately, so have to resort to `any` // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.onExecuteErrorSubject.next({ action, error }); } }); }; this.zone.runOutsideAngular(() => { try { this.grecaptcha?.execute(this.siteKey, { action }).then((token) => { this.zone.run(() => { subject.next(token); subject.complete(); if (this.onExecuteSubject) { this.onExecuteSubject.next({ action, token }); } }); }, onError); } catch (e) { onError(e); } }); } /** @internal */ init() { this.recaptchaLoader.ready.subscribe((value) => { this.grecaptcha = value; if (this.actionBacklog && this.actionBacklog.length > 0) { this.actionBacklog.forEach(([action, subject]) => this.executeActionWithSubject(action, subject)); this.actionBacklog = undefined; } }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ReCaptchaV3Service, deps: [{ token: i0.NgZone }, { token: RecaptchaLoaderService }, { token: RECAPTCHA_V3_SITE_KEY }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ReCaptchaV3Service }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ReCaptchaV3Service, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i0.NgZone }, { type: RecaptchaLoaderService }, { type: undefined, decorators: [{ type: Inject, args: [RECAPTCHA_V3_SITE_KEY] }] }] }); class RecaptchaV3Module { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaV3Module, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaV3Module }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaV3Module, providers: [ReCaptchaV3Service, RecaptchaLoaderService] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaV3Module, decorators: [{ type: NgModule, args: [{ providers: [ReCaptchaV3Service, RecaptchaLoaderService], }] }] }); class RecaptchaValueAccessorDirective { constructor(host) { this.host = host; this.requiresControllerReset = false; this.subscription = new Subscription(); this.subscription.add(host.resolved.subscribe((value) => { this.onResolve(value); })); } writeValue(value) { if (!value) { this.host.reset(); } else { // In this case, it is most likely that a form controller has requested to write a specific value into the component. // This isn't really a supported case - reCAPTCHA values are single-use, and, in a sense, readonly. // What this means is that the form controller has recaptcha control state of X, while reCAPTCHA itself can't "restore" // to that state. In order to make form controller aware of this discrepancy, and to fix the said misalignment, // we'll be telling the controller to "reset" the value back to null. if (this.host.__unsafe_widgetValue !== value && Boolean(this.host.__unsafe_widgetValue) === false) { this.requiresControllerReset = true; } } } registerOnChange(fn) { this.onChange = fn; if (this.requiresControllerReset) { this.requiresControllerReset = false; this.onChange(null); } } registerOnTouched(fn) { this.onTouched = fn; } onResolve($event) { if (this.onChange) { this.onChange($event); } if (this.onTouched) { this.onTouched(); } } ngOnDestroy() { this.subscription.unsubscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaValueAccessorDirective, deps: [{ token: RecaptchaComponent }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: RecaptchaValueAccessorDirective, isStandalone: false, selector: "re-captcha[formControlName],re-captcha[formControl],re-captcha[ngModel]", providers: [ { multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RecaptchaValueAccessorDirective), }, ], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaValueAccessorDirective, decorators: [{ type: Directive, args: [{ providers: [ { multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RecaptchaValueAccessorDirective), }, ], selector: "re-captcha[formControlName],re-captcha[formControl],re-captcha[ngModel]", standalone: false, }] }], ctorParameters: () => [{ type: RecaptchaComponent }] }); class RecaptchaFormsModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaFormsModule, declarations: [RecaptchaValueAccessorDirective], imports: [FormsModule, RecaptchaCommonModule], exports: [RecaptchaValueAccessorDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaFormsModule, imports: [FormsModule, RecaptchaCommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: RecaptchaFormsModule, decorators: [{ type: NgModule, args: [{ declarations: [RecaptchaValueAccessorDirective], exports: [RecaptchaValueAccessorDirective], imports: [FormsModule, RecaptchaCommonModule], }] }] }); /// <reference types="grecaptcha" preserve="true"/> /** * Generated bundle index. Do not edit. */ export { RECAPTCHA_BASE_URL, RECAPTCHA_LANGUAGE, RECAPTCHA_LOADER_OPTIONS, RECAPTCHA_NONCE, RECAPTCHA_SETTINGS, RECAPTCHA_V3_SITE_KEY, ReCaptchaV3Service, RecaptchaComponent, RecaptchaFormsModule, RecaptchaLoaderService, RecaptchaModule, RecaptchaV3Module, RecaptchaValueAccessorDirective }; //# sourceMappingURL=ng-recaptcha-2.mjs.map