@@ -100,6 +100,8 @@ export interface CredentialsProvider {
100100 * Specifies a listener to be notified of credential changes
101101 * (sign-in / sign-out, token changes). It is immediately called once with the
102102 * initial user.
103+ *
104+ * The change listener is invoked on the provided AsyncQueue.
103105 */
104106 setChangeListener (
105107 asyncQueue : AsyncQueue ,
@@ -135,7 +137,10 @@ export class EmptyCredentialsProvider implements CredentialsProvider {
135137 ) ;
136138 this . changeListener = changeListener ;
137139 // Fire with initial user.
138- changeListener ( User . UNAUTHENTICATED ) ;
140+ asyncQueue . enqueueRetryable ( ( ) => {
141+ changeListener ( User . FIRST_PARTY ) ;
142+ return Promise . resolve ( ) ;
143+ } ) ;
139144 }
140145
141146 removeChangeListener ( ) : void {
@@ -152,7 +157,21 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
152157
153158 /** Tracks the current User. */
154159 private currentUser : User = User . UNAUTHENTICATED ;
155- private receivedInitialUser : boolean = false ;
160+
161+ /**
162+ * Promise that allows blocking on the next tokenChange event. The Promise is
163+ * re-assgined in `awaitTokenAndRaiseInitialEvent()` to allow blocking on
164+ * an a "lazily" loaded Auth instance. In this case, `this.receivedUser`
165+ * resolves first when the SDK first detects that there is no synchronous
166+ * auth, and then gets re-assigned and re-resolved when auth is loaded.
167+ */
168+ private receivedUser = new Deferred ( ) ;
169+
170+ /**
171+ * Whether the initial token even has been raised. This can go back to `false`
172+ * if Firestore first starts without Auth and Auth is loaded later.
173+ */
174+ private initialEventRaised : boolean = false ;
156175
157176 /**
158177 * Counter used to detect if the token changed while a getToken request was
@@ -169,28 +188,27 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
169188
170189 private asyncQueue : AsyncQueue | null = null ;
171190
172- private authListeners : Array < Deferred > = [ ] ;
173-
174191 constructor ( authProvider : Provider < FirebaseAuthInternalName > ) {
175192 this . tokenListener = ( ) => {
176193 this . tokenCounter ++ ;
177- this . authListeners . forEach ( listener => listener . resolve ( ) ) ;
194+ this . receivedUser . resolve ( ) ;
178195 this . currentUser = this . getUser ( ) ;
179- if ( this . receivedInitialUser && this . changeListener ) {
196+ if ( this . initialEventRaised && this . changeListener ) {
197+ // We only invoke the change listener here if the initial event has been
198+ // raised. The initial event itself is invoked synchronously in
199+ // `awaitTokenAndRaiseInitialEvent()`.
180200 this . changeListener ( this . currentUser ) ;
181201 }
182- this . receivedInitialUser = true ;
183202 } ;
184203
185204 this . tokenCounter = 0 ;
186205
187206 const registerAuth = ( auth : FirebaseAuthInternal ) : void => {
188207 logDebug ( 'FirebaseCredentialsProvider' , 'Auth detected' ) ;
189208 this . auth = auth ;
190- this . receivedInitialUser = false ;
191209 // tokenListener can be removed by removeChangeListener()
192210 if ( this . tokenListener ) {
193- this . awaitToken ( ) ;
211+ this . awaitTokenAndRaiseInitialEvent ( ) ;
194212 this . auth . addAuthTokenListener ( this . tokenListener ) ;
195213 }
196214 } ;
@@ -214,7 +232,7 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
214232 }
215233 } , 0 ) ;
216234
217- this . awaitToken ( ) ;
235+ this . awaitTokenAndRaiseInitialEvent ( ) ;
218236 }
219237
220238 getToken ( ) : Promise < Token | null > {
@@ -272,13 +290,6 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
272290 ) ;
273291 this . changeListener = changeListener ;
274292 this . asyncQueue = asyncQueue ;
275-
276- // Fire the initial event
277- if ( this . receivedInitialUser ) {
278- asyncQueue . enqueueRetryable ( ( ) => {
279- changeListener ( this . currentUser ) ;
280- } ) ;
281- }
282293 }
283294
284295 removeChangeListener ( ) : void {
@@ -305,20 +316,29 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
305316 /**
306317 * Blocks the AsyncQueue until the next user is available. This is invoked
307318 * on SDK start to wait for the first user token (or `null` if Auth is not yet
308- * loaded). If Auth is loaded after Firestore, `awaitToken()` is also used to
309- * block Firestore until auth is fully initialized.
319+ * loaded). If Auth is loaded after Firestore,
320+ * `awaitTokenAndRaiseInitialEvent()` is also used to block Firestore until
321+ * Auth is fully initialized.
322+ *
323+ * This function also invokes the change listener synchronously once a token
324+ * is available.
310325 */
311- private awaitToken ( ) : void {
326+ private awaitTokenAndRaiseInitialEvent ( ) : void {
327+ this . initialEventRaised = false ;
312328 if ( this . asyncQueue ) {
313- this . authListeners . push ( new Deferred < void > ( ) ) ;
329+ // Create a new deferred Promise that gets resolved when we receive the
330+ // next token. Ensure that all previous Promises also get resolved.
331+ const awaitToken = new Deferred < void > ( ) ;
332+ awaitToken . promise . then ( ( ) => awaitToken . resolve ( ) ) ;
333+ this . receivedUser = awaitToken ;
314334 this . asyncQueue . enqueueRetryable ( async ( ) => {
315- console . log ( 'Blocking queue' ) ;
316- await Promise . all ( this . authListeners . map ( deferred => deferred . promise ) ) ;
335+ await awaitToken . promise ;
317336 if ( this . changeListener ) {
318337 this . changeListener ( this . currentUser ) ;
319338 }
320339 } ) ;
321340 }
341+ this . initialEventRaised = true ;
322342 }
323343}
324344
@@ -387,7 +407,10 @@ export class FirstPartyCredentialsProvider implements CredentialsProvider {
387407 changeListener : CredentialChangeListener
388408 ) : void {
389409 // Fire with initial uid.
390- changeListener ( User . FIRST_PARTY ) ;
410+ asyncQueue . enqueueRetryable ( ( ) => {
411+ changeListener ( User . FIRST_PARTY ) ;
412+ return Promise . resolve ( ) ;
413+ } ) ;
391414 }
392415
393416 removeChangeListener ( ) : void { }
0 commit comments