import * as i0 from '@angular/core'; import { InjectionToken, PLATFORM_ID, Injectable, Inject, Optional, EventEmitter, Component, Input, HostBinding, Output, NgModule, forwardRef, Directive, HostListener } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { of, BehaviorSubject, Subject } from 'rxjs'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; const RECAPTCHA_LANGUAGE = new InjectionToken("recaptcha-language"); const RECAPTCHA_BASE_URL = new InjectionToken("recaptcha-base-url"); 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"); function loadScript(renderMode, onLoaded, urlParams, url, nonce) { window.ng2recaptchaloaded = () => { onLoaded(grecaptcha); }; const script = document.createElement("script"); script.innerHTML = ""; const baseUrl = url || "https://www.google.com/recaptcha/api.js"; script.src = `${baseUrl}?render=${renderMode}&onload=ng2recaptchaloaded${urlParams}`; if (nonce) { script.nonce = nonce; } script.async = true; script.defer = true; document.head.appendChild(script); } const loader = { loadScript }; class RecaptchaLoaderService { /** * @internal * @nocollapse */ static { this.ready = null; } constructor( // eslint-disable-next-line @typescript-eslint/ban-types platformId, language, baseUrl, nonce, v3SiteKey) { this.platformId = platformId; this.language = language; this.baseUrl = baseUrl; this.nonce = nonce; this.v3SiteKey = v3SiteKey; this.init(); this.ready = isPlatformBrowser(this.platformId) ? RecaptchaLoaderService.ready.asObservable() : of(); } /** @internal */ init() { if (RecaptchaLoaderService.ready) { return; } if (isPlatformBrowser(this.platformId)) { const subject = new BehaviorSubject(null); RecaptchaLoaderService.ready = subject; const langParam = this.language ? "&hl=" + this.language : ""; const renderMode = this.v3SiteKey || "explicit"; loader.loadScript(renderMode, (grecaptcha) => subject.next(grecaptcha), langParam, this.baseUrl, this.nonce); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.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 }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaLoaderService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaLoaderService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ 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] }] }]; } }); let nextId = 0; class RecaptchaComponent { constructor(elementRef, loader, zone, settings) { this.elementRef = elementRef; this.loader = loader; this.zone = zone; this.id = `ngrecaptcha-${nextId++}`; this.errorMode = "default"; this.resolved = new EventEmitter(); /** * @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 = new EventEmitter(); this.errored = new EventEmitter(); if (settings) { this.siteKey = settings.siteKey; this.theme = settings.theme; this.type = settings.type; this.size = settings.size; this.badge = 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.getResponse(this.widget) : null; } /** @internal */ expired() { this.resolved.emit(null); } /** @internal */ onError(args) { this.error.emit(args); this.errored.emit(args); } /** @internal */ captchaResponseCallback(response) { this.resolved.emit(response); } /** @internal */ grecaptchaReset() { if (this.widget != null) { this.zone.runOutsideAngular(() => 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); if (this.executeRequested === true) { this.executeRequested = false; this.execute(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.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: "14.0.0", version: "16.0.1", type: RecaptchaComponent, selector: "re-captcha", inputs: { id: "id", siteKey: "siteKey", theme: "theme", type: "type", size: "size", tabIndex: "tabIndex", badge: "badge", errorMode: "errorMode" }, outputs: { resolved: "resolved", error: "error", errored: "errored" }, host: { properties: { "attr.id": "this.id" } }, exportAs: ["reCaptcha"], ngImport: i0, template: ``, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaComponent, decorators: [{ type: Component, args: [{ exportAs: "reCaptcha", selector: "re-captcha", template: ``, }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: RecaptchaLoaderService }, { type: i0.NgZone }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RECAPTCHA_SETTINGS] }] }]; }, propDecorators: { id: [{ type: Input }, { type: HostBinding, args: ["attr.id"] }], siteKey: [{ type: Input }], theme: [{ type: Input }], type: [{ type: Input }], size: [{ type: Input }], tabIndex: [{ type: Input }], badge: [{ type: Input }], errorMode: [{ type: Input }], resolved: [{ type: Output }], error: [{ type: Output }], errored: [{ type: Output }] } }); class RecaptchaCommonModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaCommonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaCommonModule, declarations: [RecaptchaComponent], exports: [RecaptchaComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaCommonModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.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: "16.0.1", ngImport: i0, type: RecaptchaModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaModule, imports: [RecaptchaCommonModule], exports: [RecaptchaComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaModule, providers: [RecaptchaLoaderService], imports: [RecaptchaCommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.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, siteKey, // eslint-disable-next-line @typescript-eslint/ban-types platformId, baseUrl, nonce, language) { /** @internal */ this.onLoadComplete = (grecaptcha) => { this.grecaptcha = grecaptcha; if (this.actionBacklog && this.actionBacklog.length > 0) { this.actionBacklog.forEach(([action, subject]) => this.executeActionWithSubject(action, subject)); this.actionBacklog = undefined; } }; this.zone = zone; this.isBrowser = isPlatformBrowser(platformId); this.siteKey = siteKey; this.nonce = nonce; this.language = language; this.baseUrl = baseUrl; this.init(); } get onExecute() { if (!this.onExecuteSubject) { this.onExecuteSubject = new Subject(); this.onExecuteObservable = this.onExecuteSubject.asObservable(); } return this.onExecuteObservable; } get onExecuteError() { if (!this.onExecuteErrorSubject) { 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} 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.isBrowser) { 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() { if (this.isBrowser) { if ("grecaptcha" in window) { this.grecaptcha = grecaptcha; } else { const langParam = this.language ? "&hl=" + this.language : ""; loader.loadScript(this.siteKey, this.onLoadComplete, langParam, this.baseUrl, this.nonce); } } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: ReCaptchaV3Service, deps: [{ token: i0.NgZone }, { token: RECAPTCHA_V3_SITE_KEY }, { token: PLATFORM_ID }, { token: RECAPTCHA_BASE_URL, optional: true }, { token: RECAPTCHA_NONCE, optional: true }, { token: RECAPTCHA_LANGUAGE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: ReCaptchaV3Service }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: ReCaptchaV3Service, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: undefined, decorators: [{ type: Inject, args: [RECAPTCHA_V3_SITE_KEY] }] }, { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }, { 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_LANGUAGE] }] }]; } }); class RecaptchaV3Module { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaV3Module, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaV3Module }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaV3Module, providers: [ReCaptchaV3Service] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaV3Module, decorators: [{ type: NgModule, args: [{ providers: [ReCaptchaV3Service], }] }] }); class RecaptchaValueAccessorDirective { constructor(host) { this.host = host; this.requiresControllerReset = false; } 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(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaValueAccessorDirective, deps: [{ token: RecaptchaComponent }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.1", type: RecaptchaValueAccessorDirective, selector: "re-captcha[formControlName],re-captcha[formControl],re-captcha[ngModel]", host: { listeners: { "resolved": "onResolve($event)" } }, providers: [ { multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RecaptchaValueAccessorDirective), }, ], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.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]", }] }], ctorParameters: function () { return [{ type: RecaptchaComponent }]; }, propDecorators: { onResolve: [{ type: HostListener, args: ["resolved", ["$event"]] }] } }); class RecaptchaFormsModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaFormsModule, declarations: [RecaptchaValueAccessorDirective], imports: [FormsModule, RecaptchaCommonModule], exports: [RecaptchaValueAccessorDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaFormsModule, imports: [FormsModule, RecaptchaCommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: RecaptchaFormsModule, decorators: [{ type: NgModule, args: [{ declarations: [RecaptchaValueAccessorDirective], exports: [RecaptchaValueAccessorDirective], imports: [FormsModule, RecaptchaCommonModule], }] }] }); /** * Generated bundle index. Do not edit. */ export { RECAPTCHA_BASE_URL, RECAPTCHA_LANGUAGE, RECAPTCHA_NONCE, RECAPTCHA_SETTINGS, RECAPTCHA_V3_SITE_KEY, ReCaptchaV3Service, RecaptchaComponent, RecaptchaFormsModule, RecaptchaLoaderService, RecaptchaModule, RecaptchaV3Module, RecaptchaValueAccessorDirective }; //# sourceMappingURL=ng-recaptcha.mjs.map