diff --git a/common/api-review/auth.api.md b/common/api-review/auth.api.md index 4c1a00d154b..3701696ab12 100644 --- a/common/api-review/auth.api.md +++ b/common/api-review/auth.api.md @@ -92,6 +92,8 @@ export interface Auth { languageCode: string | null; readonly name: string; onAuthStateChanged(nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; + // (undocumented) + onFirebaseTokenChanged(nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; onIdTokenChanged(nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; setPersistence(persistence: Persistence): Promise; readonly settings: AuthSettings; diff --git a/docs-devsite/auth.auth.md b/docs-devsite/auth.auth.md index 6a73129640a..9fec50cfb8c 100644 --- a/docs-devsite/auth.auth.md +++ b/docs-devsite/auth.auth.md @@ -42,6 +42,7 @@ export interface Auth | [authStateReady()](./auth.auth.md#authauthstateready) | returns a promise that resolves immediately when the initial auth state is settled. When the promise resolves, the current user might be a valid user or null if the user signed out. | | [beforeAuthStateChanged(callback, onAbort)](./auth.auth.md#authbeforeauthstatechanged) | Adds a blocking callback that runs before an auth state change sets a new user. | | [onAuthStateChanged(nextOrObserver, error, completed)](./auth.auth.md#authonauthstatechanged) | Adds an observer for changes to the user's sign-in state. | +| [onFirebaseTokenChanged(nextOrObserver, error, completed)](./auth.auth.md#authonfirebasetokenchanged) | | | [onIdTokenChanged(nextOrObserver, error, completed)](./auth.auth.md#authonidtokenchanged) | Adds an observer for changes to the signed-in user's ID token. | | [setPersistence(persistence)](./auth.auth.md#authsetpersistence) | Changes the type of persistence on the Auth instance. | | [signOut()](./auth.auth.md#authsignout) | Signs out the current user. This does not automatically revoke the user's ID token. | @@ -227,6 +228,26 @@ onAuthStateChanged(nextOrObserver: NextOrObserver, error?: ErrorFn, [Unsubscribe](./util.md#unsubscribe) +## Auth.onFirebaseTokenChanged() + +Signature: + +```typescript +onFirebaseTokenChanged(nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| nextOrObserver | [NextOrObserver](./auth.md#nextorobserver)<[FirebaseToken](./auth.firebasetoken.md#firebasetoken_interface) \| null> | | +| error | [ErrorFn](./util.md#errorfn) | | +| completed | [CompleteFn](./util.md#completefn) | | + +Returns: + +[Unsubscribe](./util.md#unsubscribe) + ## Auth.onIdTokenChanged() Adds an observer for changes to the signed-in user's ID token. diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 20df2390774..8c1e7577fb7 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -107,6 +107,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { private redirectPersistenceManager?: PersistenceUserManager; private authStateSubscription = new Subscription(this); private idTokenSubscription = new Subscription(this); + private firebaseTokenSubscription = new Subscription(this); private readonly beforeStateQueue = new AuthMiddlewareQueue(this); private redirectUser: UserInternal | null = null; private isProactiveRefreshEnabled = false; @@ -195,6 +196,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } await this.initializeCurrentUser(popupRedirectResolver); + await this.initializeFirebaseToken(); this.lastNotifiedUid = this.currentUser?.uid || null; @@ -403,6 +405,12 @@ export class AuthImpl implements AuthInternal, _FirebaseService { return this.directlySetCurrentUser(user); } + private async initializeFirebaseToken(): Promise { + this.firebaseToken = + (await this.persistenceManager?.getFirebaseToken()) ?? null; + this.firebaseTokenSubscription.next(this.firebaseToken); + } + useDeviceLanguage(): void { this.languageCode = _getUserLanguage(); } @@ -461,6 +469,12 @@ export class AuthImpl implements AuthInternal, _FirebaseService { firebaseToken: FirebaseToken | null ): Promise { this.firebaseToken = firebaseToken; + this.firebaseTokenSubscription.next(firebaseToken); + if (firebaseToken) { + await this.assertedPersistence.setFirebaseToken(firebaseToken); + } else { + await this.assertedPersistence.removeFirebaseToken(); + } } async signOut(): Promise { @@ -577,6 +591,29 @@ export class AuthImpl implements AuthInternal, _FirebaseService { ); } + onFirebaseTokenChanged( + nextOrObserver: NextOrObserver, + error?: ErrorFn, + completed?: CompleteFn + ): Unsubscribe { + if (typeof nextOrObserver === 'function') { + const unsubscribe = this.firebaseTokenSubscription.addObserver( + nextOrObserver, + error, + completed + ); + return () => { + unsubscribe(); + }; + } else { + const unsubscribe = + this.firebaseTokenSubscription.addObserver(nextOrObserver); + return () => { + unsubscribe(); + }; + } + } + beforeAuthStateChanged( callback: (user: User | null) => void | Promise, onAbort?: () => void diff --git a/packages/auth/src/core/persistence/persistence_user_manager.ts b/packages/auth/src/core/persistence/persistence_user_manager.ts index 580aaad3b25..3981a60efe1 100644 --- a/packages/auth/src/core/persistence/persistence_user_manager.ts +++ b/packages/auth/src/core/persistence/persistence_user_manager.ts @@ -17,6 +17,7 @@ import { getAccountInfo } from '../../api/account_management/account'; import { ApiKey, AppName, AuthInternal } from '../../model/auth'; +import { FirebaseToken } from '../../model/public_types'; import { UserInternal } from '../../model/user'; import { PersistedBlob, PersistenceInternal } from '../persistence'; import { UserImpl } from '../user/user_impl'; @@ -27,7 +28,8 @@ export const enum KeyName { AUTH_USER = 'authUser', AUTH_EVENT = 'authEvent', REDIRECT_USER = 'redirectUser', - PERSISTENCE_USER = 'persistence' + PERSISTENCE_USER = 'persistence', + PERSISTENCE_TOKEN = 'persistence-token' } export const enum Namespace { PERSISTENCE = 'firebase' @@ -44,6 +46,7 @@ export function _persistenceKeyName( export class PersistenceUserManager { private readonly fullUserKey: string; private readonly fullPersistenceKey: string; + private readonly firebaseTokenPersistenceKey: string; private readonly boundEventHandler: () => void; private constructor( @@ -58,6 +61,11 @@ export class PersistenceUserManager { config.apiKey, name ); + this.firebaseTokenPersistenceKey = _persistenceKeyName( + KeyName.PERSISTENCE_TOKEN, + config.apiKey, + name + ); this.boundEventHandler = auth._onStorageEvent.bind(auth); this.persistence._addListener(this.fullUserKey, this.boundEventHandler); } @@ -66,6 +74,32 @@ export class PersistenceUserManager { return this.persistence._set(this.fullUserKey, user.toJSON()); } + setFirebaseToken(firebaseToken: FirebaseToken): Promise { + return this.persistence._set(this.firebaseTokenPersistenceKey, { + token: firebaseToken.token, + expirationTime: firebaseToken.expirationTime + }); + } + + async getFirebaseToken(): Promise { + const blob = await this.persistence._get( + this.firebaseTokenPersistenceKey + ); + if (!blob) { + return null; + } + const token = blob.token as string; + const expirationTime = blob.expirationTime as number; + return { + token, + expirationTime + }; + } + + removeFirebaseToken(): Promise { + return this.persistence._remove(this.firebaseTokenPersistenceKey); + } + async getCurrentUser(): Promise { const blob = await this.persistence._get( this.fullUserKey diff --git a/packages/auth/src/model/public_types.ts b/packages/auth/src/model/public_types.ts index 1643d8f0997..363db64379f 100644 --- a/packages/auth/src/model/public_types.ts +++ b/packages/auth/src/model/public_types.ts @@ -280,6 +280,12 @@ export interface Auth { callback: (user: User | null) => void | Promise, onAbort?: () => void ): Unsubscribe; + + onFirebaseTokenChanged( + nextOrObserver: NextOrObserver, + error?: ErrorFn, + completed?: CompleteFn + ): Unsubscribe; /** * Adds an observer for changes to the signed-in user's ID token. *