diff --git a/cypress/e2e/ngx-turnstile.cy.ts b/cypress/e2e/ngx-turnstile.cy.ts index fa18dd4..583c8d7 100644 --- a/cypress/e2e/ngx-turnstile.cy.ts +++ b/cypress/e2e/ngx-turnstile.cy.ts @@ -12,4 +12,23 @@ describe('tests the ngx-turnstile library', () => { cy.wait(3000); cy.contains('Value: XXXX.DUMMY.TOKEN.XXXX'); }); + + // visits multiple routes and makes sure script is not injected multiple times + it('Passes Visiting Multiple Routes Example', () => { + cy.visit(Cypress.env('reactiveFormUrl')); + cy.wait(3000); + cy.contains('Value: XXXX.DUMMY.TOKEN.XXXX'); + + cy.visit(Cypress.env('templateDrivenFormUrl'), { + onBeforeLoad(win) { + cy.spy(win.console, 'warn').as('consoleWarn'); + }, + }); + cy.wait(3000); + cy.contains('Value: XXXX.DUMMY.TOKEN.XXXX'); + cy.get('@consoleWarn').should( + 'not.be.calledWith', + '[Cloudflare Turnstile] Turnstile already has been loaded. Was Turnstile imported multiple times?.', + ); + }); }); diff --git a/projects/ngx-turnstile/src/lib/ngx-turnstile.component.ts b/projects/ngx-turnstile/src/lib/ngx-turnstile.component.ts index 3cc5b0c..9e5b669 100644 --- a/projects/ngx-turnstile/src/lib/ngx-turnstile.component.ts +++ b/projects/ngx-turnstile/src/lib/ngx-turnstile.component.ts @@ -7,7 +7,9 @@ import { Output, EventEmitter, OnDestroy, + Inject, } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; import { TurnstileOptions } from './interfaces/turnstile-options'; declare global { @@ -27,6 +29,7 @@ declare global { } } +const SCRIPT_ID = 'ngx-turnstile'; const CALLBACK_NAME = 'onloadTurnstileCallback'; type SupportedVersion = '0'; @@ -52,6 +55,7 @@ export class NgxTurnstileComponent implements AfterViewInit, OnDestroy { constructor( private elementRef: ElementRef, private zone: NgZone, + @Inject(DOCUMENT) private document: Document, ) {} private _getCloudflareTurnstileUrl(): string { @@ -83,8 +87,6 @@ export class NgxTurnstileComponent implements AfterViewInit, OnDestroy { }, }; - const script = document.createElement('script'); - window[CALLBACK_NAME] = () => { if (!this.elementRef?.nativeElement) { return; @@ -96,10 +98,17 @@ export class NgxTurnstileComponent implements AfterViewInit, OnDestroy { ); }; + if (this.scriptLoaded()) { + window[CALLBACK_NAME](); + return; + } + + const script = this.document.createElement('script'); script.src = `${this._getCloudflareTurnstileUrl()}?render=explicit&onload=${CALLBACK_NAME}`; + script.id = SCRIPT_ID; script.async = true; script.defer = true; - document.head.appendChild(script); + this.document.head.appendChild(script); } public reset(): void { @@ -114,4 +123,8 @@ export class NgxTurnstileComponent implements AfterViewInit, OnDestroy { window.turnstile.remove(this.widgetId); } } + + public scriptLoaded(): boolean { + return !!this.document.getElementById(SCRIPT_ID); + } }