@@ -368,6 +368,95 @@ describe('internal api', () => {
368
368
} ) ;
369
369
} ) ;
370
370
371
+ it ( 'ignores in-memory token if it is invalid and continues to exchange request' , async ( ) => {
372
+ const appCheck = initializeAppCheck ( app , {
373
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY )
374
+ } ) ;
375
+ setState ( app , {
376
+ ...getState ( app ) ,
377
+ token : {
378
+ token : 'something' ,
379
+ expireTimeMillis : Date . now ( ) - 1000 ,
380
+ issuedAtTimeMillis : 0
381
+ }
382
+ } ) ;
383
+
384
+ stub ( reCAPTCHA , 'getToken' ) . returns ( Promise . resolve ( fakeRecaptchaToken ) ) ;
385
+ stub ( client , 'exchangeToken' ) . returns (
386
+ Promise . resolve ( {
387
+ token : 'new-recaptcha-app-check-token' ,
388
+ expireTimeMillis : Date . now ( ) + 60000 ,
389
+ issuedAtTimeMillis : 0
390
+ } )
391
+ ) ;
392
+
393
+ expect ( await getToken ( appCheck as AppCheckService ) ) . to . deep . equal ( {
394
+ token : 'new-recaptcha-app-check-token'
395
+ } ) ;
396
+ } ) ;
397
+
398
+ it ( 'returns the valid token in storage without making a network request' , async ( ) => {
399
+ const clock = useFakeTimers ( ) ;
400
+
401
+ storageReadStub . resolves ( fakeCachedAppCheckToken ) ;
402
+ const appCheck = initializeAppCheck ( app , {
403
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY )
404
+ } ) ;
405
+
406
+ const clientStub = stub ( client , 'exchangeToken' ) ;
407
+ expect ( await getToken ( appCheck as AppCheckService ) ) . to . deep . equal ( {
408
+ token : fakeCachedAppCheckToken . token
409
+ } ) ;
410
+ expect ( clientStub ) . to . not . have . been . called ;
411
+
412
+ clock . restore ( ) ;
413
+ } ) ;
414
+
415
+ it ( 'deletes cached token if it is invalid and continues to exchange request' , async ( ) => {
416
+ storageReadStub . resolves ( {
417
+ token : 'something' ,
418
+ expireTimeMillis : Date . now ( ) - 1000 ,
419
+ issuedAtTimeMillis : 0
420
+ } ) ;
421
+ const appCheck = initializeAppCheck ( app , {
422
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY )
423
+ } ) ;
424
+
425
+ const freshToken = {
426
+ token : 'new-recaptcha-app-check-token' ,
427
+ expireTimeMillis : Date . now ( ) + 60000 ,
428
+ issuedAtTimeMillis : 0
429
+ } ;
430
+
431
+ stub ( reCAPTCHA , 'getToken' ) . returns ( Promise . resolve ( fakeRecaptchaToken ) ) ;
432
+ stub ( client , 'exchangeToken' ) . returns ( Promise . resolve ( freshToken ) ) ;
433
+
434
+ expect ( await getToken ( appCheck as AppCheckService ) ) . to . deep . equal ( {
435
+ token : 'new-recaptcha-app-check-token'
436
+ } ) ;
437
+
438
+ // When it wiped the invalid token.
439
+ expect ( storageWriteStub ) . has . been . calledWith ( app , undefined ) ;
440
+
441
+ // When it wrote the new token fetched from the exchange endpoint.
442
+ expect ( storageWriteStub ) . has . been . calledWith ( app , freshToken ) ;
443
+ } ) ;
444
+
445
+ it ( 'returns the actual token and an internalError if a token is valid but the request fails' , async ( ) => {
446
+ stub ( logger , 'error' ) ;
447
+ const appCheck = initializeAppCheck ( app , {
448
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY )
449
+ } ) ;
450
+ setState ( app , { ...getState ( app ) , token : fakeRecaptchaAppCheckToken } ) ;
451
+
452
+ stub ( reCAPTCHA , 'getToken' ) . returns ( Promise . resolve ( fakeRecaptchaToken ) ) ;
453
+ stub ( client , 'exchangeToken' ) . returns ( Promise . reject ( new Error ( 'blah' ) ) ) ;
454
+
455
+ const tokenResult = await getToken ( appCheck as AppCheckService , true ) ;
456
+ expect ( tokenResult . internalError ?. message ) . to . equal ( 'blah' ) ;
457
+ expect ( tokenResult . token ) . to . equal ( 'fake-recaptcha-app-check-token' ) ;
458
+ } ) ;
459
+
371
460
it ( 'exchanges debug token if in debug mode and there is no cached token' , async ( ) => {
372
461
const exchangeTokenStub : SinonStub = stub (
373
462
client ,
@@ -534,6 +623,205 @@ describe('internal api', () => {
534
623
fakeListener
535
624
) ;
536
625
} ) ;
626
+
627
+ it ( 'does not make rapid requests within proactive refresh window' , async ( ) => {
628
+ const clock = useFakeTimers ( ) ;
629
+ const appCheck = initializeAppCheck ( app , {
630
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
631
+ isTokenAutoRefreshEnabled : true
632
+ } ) ;
633
+ setState ( app , {
634
+ ...getState ( app ) ,
635
+ token : {
636
+ token : `fake-cached-app-check-token` ,
637
+ // within refresh window
638
+ expireTimeMillis : 10000 ,
639
+ issuedAtTimeMillis : 0
640
+ }
641
+ } ) ;
642
+
643
+ const fakeListener : AppCheckTokenListener = stub ( ) ;
644
+
645
+ const fakeExchange = stub ( client , 'exchangeToken' ) . returns (
646
+ Promise . resolve ( {
647
+ token : 'new-recaptcha-app-check-token' ,
648
+ expireTimeMillis : 10 * 60 * 1000 ,
649
+ issuedAtTimeMillis : 0
650
+ } )
651
+ ) ;
652
+
653
+ addTokenListener (
654
+ appCheck as AppCheckService ,
655
+ ListenerType . INTERNAL ,
656
+ fakeListener
657
+ ) ;
658
+ // Tick 10s, make sure nothing is called repeatedly in that time.
659
+ await clock . tickAsync ( 10000 ) ;
660
+ expect ( fakeListener ) . to . be . calledWith ( {
661
+ token : 'fake-cached-app-check-token'
662
+ } ) ;
663
+ expect ( fakeListener ) . to . be . calledWith ( {
664
+ token : 'new-recaptcha-app-check-token'
665
+ } ) ;
666
+ expect ( fakeExchange ) . to . be . calledOnce ;
667
+ clock . restore ( ) ;
668
+ } ) ;
669
+
670
+ it ( 'proactive refresh window test - exchange request fails - wait 10s' , async ( ) => {
671
+ stub ( logger , 'error' ) ;
672
+ const clock = useFakeTimers ( ) ;
673
+ const appCheck = initializeAppCheck ( app , {
674
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
675
+ isTokenAutoRefreshEnabled : true
676
+ } ) ;
677
+ setState ( app , {
678
+ ...getState ( app ) ,
679
+ token : {
680
+ token : `fake-cached-app-check-token` ,
681
+ // not expired but within refresh window
682
+ expireTimeMillis : 10000 ,
683
+ issuedAtTimeMillis : 0
684
+ }
685
+ } ) ;
686
+
687
+ const fakeListener : AppCheckTokenListener = stub ( ) ;
688
+
689
+ const fakeExchange = stub ( client , 'exchangeToken' ) . returns (
690
+ Promise . reject ( new Error ( 'fetch failed or something' ) )
691
+ ) ;
692
+
693
+ addTokenListener (
694
+ appCheck as AppCheckService ,
695
+ ListenerType . EXTERNAL ,
696
+ fakeListener
697
+ ) ;
698
+ // Tick 10s, make sure nothing is called repeatedly in that time.
699
+ await clock . tickAsync ( 10000 ) ;
700
+ expect ( fakeListener ) . to . be . calledWith ( {
701
+ token : 'fake-cached-app-check-token'
702
+ } ) ;
703
+ // once on init and once invoked directly in this test
704
+ expect ( fakeListener ) . to . be . calledTwice ;
705
+ expect ( fakeExchange ) . to . be . calledOnce ;
706
+ clock . restore ( ) ;
707
+ } ) ;
708
+
709
+ it ( 'proactive refresh window test - exchange request fails - wait 40s' , async ( ) => {
710
+ stub ( logger , 'error' ) ;
711
+ const clock = useFakeTimers ( ) ;
712
+ const appCheck = initializeAppCheck ( app , {
713
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
714
+ isTokenAutoRefreshEnabled : true
715
+ } ) ;
716
+ setState ( app , {
717
+ ...getState ( app ) ,
718
+ token : {
719
+ token : `fake-cached-app-check-token` ,
720
+ // not expired but within refresh window
721
+ expireTimeMillis : 10000 ,
722
+ issuedAtTimeMillis : 0
723
+ }
724
+ } ) ;
725
+
726
+ const fakeListener : AppCheckTokenListener = stub ( ) ;
727
+
728
+ const fakeExchange = stub ( client , 'exchangeToken' ) . returns (
729
+ Promise . reject ( new Error ( 'fetch failed or something' ) )
730
+ ) ;
731
+
732
+ addTokenListener (
733
+ appCheck as AppCheckService ,
734
+ ListenerType . EXTERNAL ,
735
+ fakeListener
736
+ ) ;
737
+ // Tick 40s, expect one initial exchange request and one retry.
738
+ // (First backoff is 30s).
739
+ await clock . tickAsync ( 40000 ) ;
740
+ expect ( fakeListener ) . to . be . calledTwice ;
741
+ expect ( fakeExchange ) . to . be . calledTwice ;
742
+ clock . restore ( ) ;
743
+ } ) ;
744
+
745
+ it ( 'expired token - exchange request fails - wait 10s' , async ( ) => {
746
+ stub ( logger , 'error' ) ;
747
+ const clock = useFakeTimers ( ) ;
748
+ clock . tick ( 1 ) ;
749
+ const appCheck = initializeAppCheck ( app , {
750
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
751
+ isTokenAutoRefreshEnabled : true
752
+ } ) ;
753
+ setState ( app , {
754
+ ...getState ( app ) ,
755
+ token : {
756
+ token : `fake-cached-app-check-token` ,
757
+ // expired
758
+ expireTimeMillis : 0 ,
759
+ issuedAtTimeMillis : 0
760
+ }
761
+ } ) ;
762
+
763
+ const fakeListener = stub ( ) ;
764
+ const errorHandler = stub ( ) ;
765
+ const fakeNetworkError = new Error ( 'fetch failed or something' ) ;
766
+
767
+ const fakeExchange = stub ( client , 'exchangeToken' ) . returns (
768
+ Promise . reject ( fakeNetworkError )
769
+ ) ;
770
+
771
+ addTokenListener (
772
+ appCheck as AppCheckService ,
773
+ ListenerType . EXTERNAL ,
774
+ fakeListener ,
775
+ errorHandler
776
+ ) ;
777
+ // Tick 10s, make sure nothing is called repeatedly in that time.
778
+ await clock . tickAsync ( 10000 ) ;
779
+ expect ( fakeListener ) . not . to . be . called ;
780
+ expect ( fakeExchange ) . to . be . calledOnce ;
781
+ expect ( errorHandler ) . to . be . calledWith ( fakeNetworkError ) ;
782
+ clock . restore ( ) ;
783
+ } ) ;
784
+
785
+ it ( 'expired token - exchange request fails - wait 40s' , async ( ) => {
786
+ stub ( logger , 'error' ) ;
787
+ const clock = useFakeTimers ( ) ;
788
+ clock . tick ( 1 ) ;
789
+ const appCheck = initializeAppCheck ( app , {
790
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY ) ,
791
+ isTokenAutoRefreshEnabled : true
792
+ } ) ;
793
+ setState ( app , {
794
+ ...getState ( app ) ,
795
+ token : {
796
+ token : `fake-cached-app-check-token` ,
797
+ // expired
798
+ expireTimeMillis : 0 ,
799
+ issuedAtTimeMillis : 0
800
+ }
801
+ } ) ;
802
+
803
+ const fakeListener = stub ( ) ;
804
+ const errorHandler = stub ( ) ;
805
+ const fakeNetworkError = new Error ( 'fetch failed or something' ) ;
806
+
807
+ const fakeExchange = stub ( client , 'exchangeToken' ) . returns (
808
+ Promise . reject ( fakeNetworkError )
809
+ ) ;
810
+
811
+ addTokenListener (
812
+ appCheck as AppCheckService ,
813
+ ListenerType . EXTERNAL ,
814
+ fakeListener ,
815
+ errorHandler
816
+ ) ;
817
+ // Tick 40s, expect one initial exchange request and one retry.
818
+ // (First backoff is 30s).
819
+ await clock . tickAsync ( 40000 ) ;
820
+ expect ( fakeListener ) . not . to . be . called ;
821
+ expect ( fakeExchange ) . to . be . calledTwice ;
822
+ expect ( errorHandler ) . to . be . calledTwice ;
823
+ clock . restore ( ) ;
824
+ } ) ;
537
825
} ) ;
538
826
539
827
describe ( 'removeTokenListener' , ( ) => {
0 commit comments