Skip to content

Commit 7e467a0

Browse files
committed
Change listeners
1 parent 7d821e4 commit 7e467a0

File tree

5 files changed

+81
-2
lines changed

5 files changed

+81
-2
lines changed

packages-exp/auth-exp/src/core/auth/auth_impl.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
import { getApp } from '@firebase/app-exp';
1919
import { FirebaseApp } from '@firebase/app-types-exp';
20+
import {
21+
CompleteFn, createSubscribe, ErrorFn, NextFn, Observer, Subscribe, Unsubscribe
22+
} from '@firebase/util';
2023

21-
import { Auth, Config, Dependencies } from '../../model/auth';
24+
import { Auth, Config, Dependencies, NextOrObserver } from '../../model/auth';
2225
import { User } from '../../model/user';
2326
import { AuthErrorCode } from '../errors';
2427
import { Persistence } from '../persistence';
@@ -37,6 +40,13 @@ class AuthImpl implements Auth {
3740
currentUser: User | null = null;
3841
private operations = Promise.resolve();
3942
private persistenceManager?: PersistenceUserManager;
43+
private authStateSubscription = new Subscription<User>(this);
44+
private idTokenSubscription = new Subscription<User>(this);
45+
private isInitialized = false;
46+
47+
// Tracks the last notified UID for state change listeners to prevent
48+
// repeated calls to the callbacks
49+
private lastNotifiedUid: string|undefined = undefined;
4050

4151
constructor(
4252
public readonly name: string,
@@ -57,6 +67,9 @@ class AuthImpl implements Auth {
5767
if (storedUser) {
5868
await this.directlySetCurrentUser(storedUser);
5969
}
70+
71+
this.isInitialized = true;
72+
this._notifyStateListeners();
6073
});
6174
}
6275

@@ -74,6 +87,47 @@ class AuthImpl implements Auth {
7487
});
7588
}
7689

90+
onAuthStateChanged(nextOrObserver: NextOrObserver<User>,
91+
error?: ErrorFn,
92+
completed?: CompleteFn): Unsubscribe {
93+
return this.registerStateListener(this.authStateSubscription, nextOrObserver, error, completed);
94+
}
95+
96+
onIdTokenChange(nextOrObserver: NextOrObserver<User>,
97+
error?: ErrorFn,
98+
completed?: CompleteFn): Unsubscribe {
99+
return this.registerStateListener(this.idTokenSubscription, nextOrObserver, error, completed);
100+
}
101+
102+
_notifyStateListeners(): void {
103+
if (!this.isInitialized) {
104+
return;
105+
}
106+
107+
this.idTokenSubscription.next(this.currentUser);
108+
109+
if (this.lastNotifiedUid !== this.currentUser?.uid) {
110+
this.lastNotifiedUid = this.currentUser?.uid;
111+
this.authStateSubscription.next(this.currentUser);
112+
}
113+
}
114+
115+
private registerStateListener(subscription: Subscription<User>, nextOrObserver: NextOrObserver<User>,
116+
error?: ErrorFn,
117+
completed?: CompleteFn): Unsubscribe {
118+
if (this.isInitialized) {
119+
const cb = typeof nextOrObserver === 'function' ? nextOrObserver : nextOrObserver.next;
120+
// The callback needs to be called asynchronously per the spec
121+
Promise.resolve().then(() => cb(this.currentUser));
122+
}
123+
124+
if (typeof nextOrObserver === 'function') {
125+
return subscription.addObserver(nextOrObserver, error, completed);
126+
} else {
127+
return subscription.addObserver(nextOrObserver);
128+
}
129+
}
130+
77131
/**
78132
* Unprotected (from race conditions) method to set the current user. This
79133
* should only be called from within a queued callback. This is necessary
@@ -87,6 +141,8 @@ class AuthImpl implements Auth {
87141
} else {
88142
await this.assertedPersistence.removeCurrentUser();
89143
}
144+
145+
this._notifyStateListeners();
90146
}
91147

92148
private queue(action: AsyncAction): Promise<void> {
@@ -120,3 +176,17 @@ export function initializeAuth(
120176

121177
return new AuthImpl(app.name, config, hierarchy);
122178
}
179+
180+
/** Helper class to wrap subscriber logic */
181+
class Subscription<T> {
182+
private observer: Observer<T|null> | null = null;
183+
readonly addObserver: Subscribe<T|null> = createSubscribe(
184+
observer => this.observer = observer,
185+
);
186+
187+
constructor(readonly auth: Auth) {}
188+
189+
get next(): NextFn<T|null> {
190+
return assert(this.observer, this.auth.name).next;
191+
}
192+
}

packages-exp/auth-exp/src/core/user/user_impl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class UserImpl implements User {
4242

4343
uid: string;
4444
auth: Auth;
45+
emailVerified = false;
4546

4647
// Optional fields from UserInfo
4748
displayName: string | null;

packages-exp/auth-exp/src/model/auth.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { CompleteFn, ErrorFn, NextFn, Observer, Unsubscribe } from '@firebase/util';
19+
1820
import { Persistence } from '../core/persistence';
1921
import { User } from './user';
2022

2123
export type AppName = string;
2224
export type ApiKey = string;
2325
export type AuthDomain = string;
26+
export type NextOrObserver<T> = NextFn<T|null> | Observer<T|null>;
2427

2528
export interface Config {
2629
apiKey: ApiKey;
@@ -38,6 +41,8 @@ export interface Auth {
3841
setPersistence(persistence: Persistence): Promise<void>;
3942
updateCurrentUser(user: User | null): Promise<void>;
4043
signOut(): Promise<void>;
44+
onAuthStateChanged(nextOrObserver: NextOrObserver<User>, error?: ErrorFn, completed?: CompleteFn): Unsubscribe;
45+
onIdTokenChange(nextOrObserver: NextOrObserver<User>, error?: ErrorFn, completed?: CompleteFn): Unsubscribe;
4146
}
4247

4348
export interface Dependencies {

packages-exp/auth-exp/src/model/user.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface UserInfo {
3131
export interface User extends UserInfo {
3232
providerId: ProviderId.FIREBASE;
3333
refreshToken: string;
34+
emailVerified: boolean;
3435

3536
getIdToken(forceRefresh?: boolean): Promise<string>;
3637
getIdTokenResult(forceRefresh?: boolean): Promise<IdTokenResult>;

packages-exp/auth-exp/test/mock_auth.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export const mockAuth: Auth = {
3535
currentUser: null,
3636
async setPersistence() {},
3737
async updateCurrentUser() {},
38-
async signOut() {}
38+
async signOut() {},
39+
onAuthStateChanged() {return () => {}},
40+
onIdTokenChange() { return () => {}},
3941
};
4042

4143
export function testUser(uid: string, email?: string): User {

0 commit comments

Comments
 (0)