@@ -47,9 +47,11 @@ import { mockEndpointWithParams } from '../../../test/helpers/api/helper';
4747import { Endpoint , RecaptchaClientType , RecaptchaVersion } from '../../api' ;
4848import * as mockFetch from '../../../test/helpers/mock_fetch' ;
4949import { AuthErrorCode } from '../errors' ;
50+ import * as exchangeTokenModule from '../strategies/exhange_token' ;
5051import {
5152 FirebaseToken ,
52- PasswordValidationStatus
53+ PasswordValidationStatus ,
54+ TokenRefreshHandler
5355} from '../../model/public_types' ;
5456import { PasswordPolicyImpl } from './password_policy_impl' ;
5557import { PersistenceUserManager } from '../persistence/persistence_user_manager' ;
@@ -302,6 +304,116 @@ describe('core/auth/auth_impl', () => {
302304 } ) ;
303305 } ) ;
304306
307+ describe ( '#setTokenRefreshHandler' , ( ) => {
308+ it ( 'sets the tokenRefreshHandler on the auth object' , ( ) => {
309+ const handler : TokenRefreshHandler = {
310+ refreshIdpToken : async ( ) => ( { idToken : 'a' , idpConfigId : 'b' } )
311+ } ;
312+ auth . setTokenRefreshHandler ( handler ) ;
313+ expect ( ( auth as any ) . tokenRefreshHandler ) . to . eq ( handler ) ;
314+ } ) ;
315+
316+ describe ( '#getFirebaseAccessToken' , ( ) => {
317+ let exchangeTokenStub : sinon . SinonStub ;
318+ let mockToken : FirebaseToken ;
319+ let expiredMockToken : FirebaseToken ;
320+ let tokenRefreshHandler : TokenRefreshHandler ;
321+ const tokenKey = `firebase:persistence-token:${ FAKE_APP . options . apiKey ! } :${
322+ FAKE_APP . name
323+ } `;
324+
325+ beforeEach ( ( ) => {
326+ exchangeTokenStub = sinon . stub ( exchangeTokenModule , 'exchangeToken' ) . resolves ( ) ;
327+
328+ mockToken = {
329+ token : 'test-token' ,
330+ expirationTime : Date . now ( ) + 300000 // 5 minutes from now
331+ } ;
332+ expiredMockToken = {
333+ token : 'expired-test-token' ,
334+ expirationTime : Date . now ( ) - 1000 // 1 second ago
335+ } ;
336+ tokenRefreshHandler = {
337+ refreshIdpToken : sinon . stub ( ) . resolves ( {
338+ idToken : 'new-id-token' ,
339+ idpConfigId : 'test-idp'
340+ } )
341+ } ;
342+ // Reset cached token and persistence before each test
343+ ( auth as any ) . firebaseToken = null ;
344+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( null ) ;
345+ } ) ;
346+
347+ afterEach ( ( ) => {
348+ sinon . restore ( ) ;
349+ } ) ;
350+
351+ it ( 'should return the existing token if it is valid' , async ( ) => {
352+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
353+ const token = await auth . getFirebaseAccessToken ( ) ;
354+ expect ( token ) . to . eql ( mockToken ) ;
355+ expect ( exchangeTokenStub ) . not . to . have . been . called ;
356+ } ) ;
357+
358+ it ( 'should return null if the token is expired and no token refresh handler is set' , async ( ) => {
359+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
360+ const token = await auth . getFirebaseAccessToken ( ) ;
361+ expect ( token ) . to . be . null ;
362+ expect ( exchangeTokenStub ) . not . to . have . been . called ;
363+ } ) ;
364+
365+ it ( 'should refresh the token if it is expired and a token refresh handler is set' , async ( ) => {
366+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
367+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
368+
369+ exchangeTokenStub . callsFake ( async ( ) => {
370+ // When exchangeToken is called, simulate that the new token is persisted.
371+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
372+ } ) ;
373+
374+ const token = await auth . getFirebaseAccessToken ( ) ;
375+
376+ expect ( tokenRefreshHandler . refreshIdpToken ) . to . have . been . calledOnce ;
377+ expect ( exchangeTokenStub ) . to . have . been . calledWith ( auth , 'test-idp' , 'new-id-token' ) ;
378+ expect ( token ) . to . eql ( mockToken ) ;
379+ } ) ;
380+
381+ it ( 'should force refresh the token when forceRefresh is true' , async ( ) => {
382+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
383+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
384+
385+ exchangeTokenStub . callsFake ( async ( ) => {
386+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( mockToken as any ) ;
387+ } ) ;
388+
389+ await auth . getFirebaseAccessToken ( true ) ;
390+
391+ expect ( tokenRefreshHandler . refreshIdpToken ) . to . have . been . calledOnce ;
392+ expect ( exchangeTokenStub ) . to . have . been . calledWith ( auth , 'test-idp' , 'new-id-token' ) ;
393+ } ) ;
394+
395+ it ( 'should return null and log an error if token refresh fails' , async ( ) => {
396+ const consoleErrorStub = sinon . stub ( console , 'error' ) ;
397+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
398+ ( tokenRefreshHandler . refreshIdpToken as sinon . SinonStub ) . rejects ( new Error ( 'refresh failed' ) ) ;
399+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
400+ const token = await auth . getFirebaseAccessToken ( ) ;
401+ expect ( token ) . to . be . null ;
402+ expect ( consoleErrorStub ) . to . have . been . calledWith ( 'Token refresh failed:' , sinon . match . instanceOf ( Error ) ) ;
403+ } ) ;
404+
405+ it ( 'should return null and log an error if the refreshed token is invalid' , async ( ) => {
406+ const consoleErrorStub = sinon . stub ( console , 'error' ) ;
407+ persistenceStub . _get . withArgs ( tokenKey ) . resolves ( expiredMockToken as any ) ;
408+ ( tokenRefreshHandler . refreshIdpToken as sinon . SinonStub ) . resolves ( { idToken : 'new-id-token' } ) ; // Missing idpConfigId
409+ auth . setTokenRefreshHandler ( tokenRefreshHandler ) ;
410+ const token = await auth . getFirebaseAccessToken ( ) ;
411+ expect ( token ) . to . be . null ;
412+ expect ( consoleErrorStub ) . to . have . been . calledWith ( 'Token refresh failed:' , sinon . match . instanceOf ( FirebaseError ) ) ;
413+ } ) ;
414+ } ) ;
415+
416+
305417 describe ( '#signOut' , ( ) => {
306418 it ( 'sets currentUser to null, calls remove' , async ( ) => {
307419 await auth . _updateCurrentUser ( testUser ( auth , 'test' ) ) ;
0 commit comments