@@ -38,12 +38,14 @@ import * as reCAPTCHA from './recaptcha';
38
38
import * as client from './client' ;
39
39
import * as storage from './storage' ;
40
40
import * as util from './util' ;
41
+ import { logger } from './logger' ;
41
42
import { getState , clearState , setState , getDebugState } from './state' ;
42
43
import { AppCheckTokenListener } from './public-types' ;
43
- import { Deferred } from '@firebase/util' ;
44
+ import { Deferred , FirebaseError } from '@firebase/util' ;
44
45
import { ReCaptchaEnterpriseProvider , ReCaptchaV3Provider } from './providers' ;
45
46
import { AppCheckService } from './factory' ;
46
47
import { ListenerType } from './types' ;
48
+ import { AppCheckError } from './errors' ;
47
49
48
50
const fakeRecaptchaToken = 'fake-recaptcha-token' ;
49
51
const fakeRecaptchaAppCheckToken = {
@@ -385,6 +387,62 @@ describe('internal api', () => {
385
387
) ;
386
388
expect ( token ) . to . deep . equal ( { token : fakeRecaptchaAppCheckToken . token } ) ;
387
389
} ) ;
390
+
391
+ it ( 'throttles for a period less than 1d on 503' , async ( ) => {
392
+ // More detailed check of exponential backoff in providers.test.ts
393
+ const appCheck = initializeAppCheck ( app , {
394
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY )
395
+ } ) ;
396
+ const warnStub = stub ( logger , 'warn' ) ;
397
+ stub ( client , 'exchangeToken' ) . returns (
398
+ Promise . reject (
399
+ new FirebaseError (
400
+ AppCheckError . FETCH_STATUS_ERROR ,
401
+ 'test error msg' ,
402
+ { httpStatus : 503 }
403
+ )
404
+ )
405
+ ) ;
406
+
407
+ const token = await getToken ( appCheck as AppCheckService ) ;
408
+
409
+ // ReCaptchaV3Provider's _throttleData is private so checking
410
+ // the resulting error message to be sure it has roughly the
411
+ // correct throttle time. This also tests the time formatter.
412
+ // Check both the error itself and that it makes it through to
413
+ // console.warn
414
+ expect ( token . error ?. message ) . to . include ( '503' ) ;
415
+ expect ( token . error ?. message ) . to . include ( '00m' ) ;
416
+ expect ( token . error ?. message ) . to . not . include ( '1d' ) ;
417
+ expect ( warnStub . args [ 0 ] [ 0 ] ) . to . include ( '503' ) ;
418
+ } ) ;
419
+
420
+ it ( 'throttles 1d on 403' , async ( ) => {
421
+ const appCheck = initializeAppCheck ( app , {
422
+ provider : new ReCaptchaV3Provider ( FAKE_SITE_KEY )
423
+ } ) ;
424
+ const warnStub = stub ( logger , 'warn' ) ;
425
+ stub ( client , 'exchangeToken' ) . returns (
426
+ Promise . reject (
427
+ new FirebaseError (
428
+ AppCheckError . FETCH_STATUS_ERROR ,
429
+ 'test error msg' ,
430
+ { httpStatus : 403 }
431
+ )
432
+ )
433
+ ) ;
434
+
435
+ const token = await getToken ( appCheck as AppCheckService ) ;
436
+
437
+ // ReCaptchaV3Provider's _throttleData is private so checking
438
+ // the resulting error message to be sure it has roughly the
439
+ // correct throttle time. This also tests the time formatter.
440
+ // Check both the error itself and that it makes it through to
441
+ // console.warn
442
+ expect ( token . error ?. message ) . to . include ( '403' ) ;
443
+ expect ( token . error ?. message ) . to . include ( '1d' ) ;
444
+ expect ( warnStub . args [ 0 ] [ 0 ] ) . to . include ( '403' ) ;
445
+ } ) ;
388
446
} ) ;
389
447
390
448
describe ( 'addTokenListener' , ( ) => {
@@ -404,7 +462,7 @@ describe('internal api', () => {
404
462
expect ( getState ( app ) . tokenObservers [ 0 ] . next ) . to . equal ( listener ) ;
405
463
} ) ;
406
464
407
- it ( 'starts proactively refreshing token after adding the first listener' , ( ) => {
465
+ it ( 'starts proactively refreshing token after adding the first listener' , async ( ) => {
408
466
const listener = ( ) : void => { } ;
409
467
setState ( app , {
410
468
...getState ( app ) ,
@@ -420,6 +478,12 @@ describe('internal api', () => {
420
478
listener
421
479
) ;
422
480
481
+ expect ( getState ( app ) . tokenRefresher ?. isRunning ( ) ) . to . be . undefined ;
482
+
483
+ // addTokenListener() waits for the result of cachedTokenPromise
484
+ // before starting the refresher
485
+ await getState ( app ) . cachedTokenPromise ;
486
+
423
487
expect ( getState ( app ) . tokenRefresher ?. isRunning ( ) ) . to . be . true ;
424
488
} ) ;
425
489
@@ -430,6 +494,7 @@ describe('internal api', () => {
430
494
431
495
setState ( app , {
432
496
...getState ( app ) ,
497
+ cachedTokenPromise : Promise . resolve ( undefined ) ,
433
498
token : {
434
499
token : `fake-memory-app-check-token` ,
435
500
expireTimeMillis : Date . now ( ) + 60000 ,
@@ -493,7 +558,7 @@ describe('internal api', () => {
493
558
expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 0 ) ;
494
559
} ) ;
495
560
496
- it ( 'should stop proactively refreshing token after deleting the last listener' , ( ) => {
561
+ it ( 'should stop proactively refreshing token after deleting the last listener' , async ( ) => {
497
562
const listener = ( ) : void => { } ;
498
563
setState ( app , { ...getState ( app ) , isTokenAutoRefreshEnabled : true } ) ;
499
564
setState ( app , {
@@ -506,6 +571,11 @@ describe('internal api', () => {
506
571
ListenerType . INTERNAL ,
507
572
listener
508
573
) ;
574
+
575
+ // addTokenListener() waits for the result of cachedTokenPromise
576
+ // before starting the refresher
577
+ await getState ( app ) . cachedTokenPromise ;
578
+
509
579
expect ( getState ( app ) . tokenObservers . length ) . to . equal ( 1 ) ;
510
580
expect ( getState ( app ) . tokenRefresher ?. isRunning ( ) ) . to . be . true ;
511
581
0 commit comments