diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 81ab6492f3f..fc98a258fca 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -676,7 +676,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn - ): Unsubscribe | undefined { + ): Unsubscribe { return this.registerStateListener( this.firebaseTokenSubscription, nextOrObserver, diff --git a/packages/auth/src/core/auth/firebase_internal.test.ts b/packages/auth/src/core/auth/firebase_internal.test.ts index 5954412364e..9e3c606d890 100644 --- a/packages/auth/src/core/auth/firebase_internal.test.ts +++ b/packages/auth/src/core/auth/firebase_internal.test.ts @@ -327,6 +327,72 @@ describe('core/auth/firebase_internal - Regional Firebase Auth', () => { }); }); + context('addAuthTokenListener', () => { + let isProactiveRefresh = false; + const initialToken = 'initial-regional-token'; + const updatedToken = 'updated-regional-token'; + beforeEach(async () => { + isProactiveRefresh = false; + + sinon + .stub(regionalAuth as any, '_startProactiveRefresh') + .callsFake(() => { + isProactiveRefresh = true; + }); + sinon.stub(regionalAuth as any, '_stopProactiveRefresh').callsFake(() => { + isProactiveRefresh = false; + }); + await regionalAuth._updateFirebaseToken({ + token: initialToken, + expirationTime: now + 300_000 + }); + }); + + it('gets called with the current token (or null)', done => { + let firstCall = true; + + regionalAuthInternal.addAuthTokenListener(token => { + if (firstCall) { + firstCall = false; + expect(token).to.eq(initialToken); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + regionalAuth._updateFirebaseToken({ + token: updatedToken, + expirationTime: now + 300_000 + }); + return; + } + + expect(token).to.eq(updatedToken); + expect(isProactiveRefresh).to.be.true; + done(); + }); + }); + + it('gets called on subsequent updates', async () => { + let regionalCount = 0; + regionalAuthInternal.addAuthTokenListener(() => { + regionalCount++; + }); + + await regionalAuth.getFirebaseAccessToken(); + await regionalAuth.getFirebaseAccessToken(); + await regionalAuth.getFirebaseAccessToken(); + await regionalAuth.getFirebaseAccessToken(); + + expect(regionalCount).to.eq(5); + }); + + it('errors if Regional Auth is not initialized', () => { + delete (regionalAuth as unknown as Record)[ + '_initializationPromise' + ]; + expect(() => + regionalAuthInternal.addAuthTokenListener(() => {}) + ).to.throw(FirebaseError, 'auth/dependent-sdk-initialized-before-auth'); + }); + }); + context('getUid', () => { it('throws an error if regionalAuth is initialized', () => { expect(() => regionalAuthInternal.getUid()).to.throw( diff --git a/packages/auth/src/core/auth/firebase_internal.ts b/packages/auth/src/core/auth/firebase_internal.ts index 970e99fa4ec..e2e3ba928f0 100644 --- a/packages/auth/src/core/auth/firebase_internal.ts +++ b/packages/auth/src/core/auth/firebase_internal.ts @@ -18,7 +18,7 @@ import { Unsubscribe } from '@firebase/util'; import { FirebaseAuthInternal } from '@firebase/auth-interop-types'; -import { AuthInternal } from '../../model/auth'; +import { AuthInternal, FirebaseToken } from '../../model/auth'; import { UserInternal } from '../../model/user'; import { _assert } from '../util/assert'; import { AuthErrorCode } from '../errors'; @@ -64,12 +64,28 @@ export class AuthInterop implements FirebaseAuthInternal { return; } - const unsubscribe = this.auth.onIdTokenChanged(user => { - listener( - (user as UserInternal | null)?.stsTokenManager.accessToken || null + let unsubscribe: Unsubscribe; + if (this.auth.tenantConfig) { + unsubscribe = this.auth.onFirebaseTokenChanged( + (firebaseToken: FirebaseToken | null) => { + try { + listener(firebaseToken?.token || null); + } catch (error) { + console.error('Failed to retrieve firebase token:', error); + listener(null); + } + } ); - }); + } else { + unsubscribe = this.auth.onIdTokenChanged(user => { + listener( + (user as UserInternal | null)?.stsTokenManager.accessToken || null + ); + }); + } + this.internalListeners.set(listener, unsubscribe); + // TODO - b/455792813: Handle proactive refresh for R-GCIP Firebase Token. this.updateProactiveRefresh(); } diff --git a/packages/auth/src/model/auth.ts b/packages/auth/src/model/auth.ts index 308231573b0..ef9373c3717 100644 --- a/packages/auth/src/model/auth.ts +++ b/packages/auth/src/model/auth.ts @@ -85,7 +85,7 @@ export interface AuthInternal extends Auth { nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn - ): Unsubscribe | undefined; + ): Unsubscribe; _agentRecaptchaConfig: RecaptchaConfig | null; _tenantRecaptchaConfigs: Record; _projectPasswordPolicy: PasswordPolicy | null;