17
17
18
18
import { getApp } from '@firebase/app-exp' ;
19
19
import { FirebaseApp } from '@firebase/app-types-exp' ;
20
+ import {
21
+ CompleteFn , createSubscribe , ErrorFn , NextFn , Observer , Subscribe , Unsubscribe
22
+ } from '@firebase/util' ;
20
23
21
- import { Auth , Config , Dependencies } from '../../model/auth' ;
24
+ import { Auth , Config , Dependencies , NextOrObserver } from '../../model/auth' ;
22
25
import { User } from '../../model/user' ;
23
26
import { AuthErrorCode } from '../errors' ;
24
27
import { Persistence } from '../persistence' ;
@@ -37,6 +40,13 @@ class AuthImpl implements Auth {
37
40
currentUser : User | null = null ;
38
41
private operations = Promise . resolve ( ) ;
39
42
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 ;
40
50
41
51
constructor (
42
52
public readonly name : string ,
@@ -57,6 +67,9 @@ class AuthImpl implements Auth {
57
67
if ( storedUser ) {
58
68
await this . directlySetCurrentUser ( storedUser ) ;
59
69
}
70
+
71
+ this . isInitialized = true ;
72
+ this . _notifyStateListeners ( ) ;
60
73
} ) ;
61
74
}
62
75
@@ -74,6 +87,47 @@ class AuthImpl implements Auth {
74
87
} ) ;
75
88
}
76
89
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
+
77
131
/**
78
132
* Unprotected (from race conditions) method to set the current user. This
79
133
* should only be called from within a queued callback. This is necessary
@@ -87,6 +141,8 @@ class AuthImpl implements Auth {
87
141
} else {
88
142
await this . assertedPersistence . removeCurrentUser ( ) ;
89
143
}
144
+
145
+ this . _notifyStateListeners ( ) ;
90
146
}
91
147
92
148
private queue ( action : AsyncAction ) : Promise < void > {
@@ -120,3 +176,17 @@ export function initializeAuth(
120
176
121
177
return new AuthImpl ( app . name , config , hierarchy ) ;
122
178
}
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
+ }
0 commit comments