verto-health / ngx-turnstile

Cloudflare Turnstile for Angular
https://ngx-turnstile.pages.dev/
MIT License
62 stars 13 forks source link

Callback revived on incorrect component #38

Open henninglive opened 2 months ago

henninglive commented 2 months ago

Rapidly creating NgxTurnstileComponents may cause the window callback to be received after components has been destroyed or on a different NgxTurnstileComponent.

Example:


export class NgxTurnstileComponent implements AfterViewInit, OnDestroy {

  ....
  name!: string;

  public ngAfterViewInit(): void {
    console.log('ngAfterViewInit', this.name);

    let turnstileOptions: TurnstileOptions = ...

    window[CALLBACK_NAME] = () => {
      if (!this.elementRef?.nativeElement) {
        return;
      }

      console.log('callback', this.name)

      this.widgetId = window.turnstile.render(
        this.elementRef.nativeElement,
        turnstileOptions,
      );
    };

    if (this.scriptLoaded()) {
      window[CALLBACK_NAME]();
      return;
    }

    ...
  }

  public ngOnDestroy(): void {
    console.log('ngOnDestroy', this.name)
    ...
  }

}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NgxTurnstileComponent } from './ngx-turnstile.component';

describe('NgxTurnstileComponent', () => {
  let component: NgxTurnstileComponent;
  let fixture: ComponentFixture<NgxTurnstileComponent>;

  let run = 1;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [NgxTurnstileComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(NgxTurnstileComponent);
    component = fixture.componentInstance;
    component.siteKey = '1x00000000000000000000AA';
    component.name = 'test-' + run++;
    fixture.detectChanges();
  });

  it(`should create 1`, () => {
    expect(component).toBeTruthy();
  });

  it(`should create 2`, () => {
    expect(component).toBeTruthy();
  });

});
NgxTurnstileComponent
✅should create 1
❌should create 2

LOG: 'ngAfterViewInit', 'test-1'
LOG: 'ngOnDestroy', 'test-1'                                                                                                                                                                                
LOG: 'ngAfterViewInit', 'test-2'                                                                                                                                                                            
LOG: 'callback', 'test-2'
LOG: 'ngOnDestroy', 'test-2'
LOG: 'callback', 'test-2'                                                                                                                                                                                                                                                                                                                                                    

TypeError: Cannot read properties of undefined (reading 'render')
    at window.<computed> (http://localhost:9876/_karma_webpack_/webpack:/src/app/ngx-turnstile/ngx-turnstile.component.ts:110:40)
    at NgxTurnstileComponent.call [as ngAfterViewInit] (http://localhost:9876/_karma_webpack_/webpack:/src/app/ngx-turnstile/ngx-turnstile.component.ts:117:28)
    at callHookInternal (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:5153:14)
    at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:5180:13)
    at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:5134:17)
    at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:5084:9)
    at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:13858:21)
    at detectChangesInView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:13999:9)
    at detectChangesInViewWhileDirty (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:13694:9)
    at detectChangesInternal (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:13673:9)
choyiny commented 1 month ago

@henninglive what is the use case of rapidly creating many turnstile widgets at once? Do you have a production use-case where this is important?

henninglive commented 1 month ago

@choyiny it's mainly an issue in test code.

We encountered this problem when writing unit tests for Angular components containing a NgxTurnstileComponent. Since jasmine unit tests typical recreate the component between each test case, we would consistently receive callbacks after the NgxTurnstileComponent had been destroyed or on a different NgxTurnstileComponent.