@@ -23,8 +23,10 @@ import { Provider } from '@firebase/component';
23
23
24
24
import { User } from '../auth/user' ;
25
25
import { hardAssert , debugAssert } from '../util/assert' ;
26
+ import { AsyncQueue } from '../util/async_queue' ;
26
27
import { Code , FirestoreError } from '../util/error' ;
27
28
import { logDebug } from '../util/log' ;
29
+ import { Deferred } from '../util/promise' ;
28
30
29
31
// TODO(mikelehen): This should be split into multiple files and probably
30
32
// moved to an auth/ folder to match other platforms.
@@ -78,7 +80,7 @@ export class OAuthToken implements Token {
78
80
* token and may need to invalidate other state if the current user has also
79
81
* changed.
80
82
*/
81
- export type CredentialChangeListener = ( user : User ) => void ;
83
+ export type CredentialChangeListener = ( user : User ) => Promise < void > ;
82
84
83
85
/**
84
86
* Provides methods for getting the uid and token for the current user and
@@ -98,8 +100,13 @@ export interface CredentialsProvider {
98
100
* Specifies a listener to be notified of credential changes
99
101
* (sign-in / sign-out, token changes). It is immediately called once with the
100
102
* initial user.
103
+ *
104
+ * The change listener is invoked on the provided AsyncQueue.
101
105
*/
102
- setChangeListener ( changeListener : CredentialChangeListener ) : void ;
106
+ setChangeListener (
107
+ asyncQueue : AsyncQueue ,
108
+ changeListener : CredentialChangeListener
109
+ ) : void ;
103
110
104
111
/** Removes the previously-set change listener. */
105
112
removeChangeListener ( ) : void ;
@@ -120,14 +127,17 @@ export class EmptyCredentialsProvider implements CredentialsProvider {
120
127
121
128
invalidateToken ( ) : void { }
122
129
123
- setChangeListener ( changeListener : CredentialChangeListener ) : void {
130
+ setChangeListener (
131
+ asyncQueue : AsyncQueue ,
132
+ changeListener : CredentialChangeListener
133
+ ) : void {
124
134
debugAssert (
125
135
! this . changeListener ,
126
136
'Can only call setChangeListener() once.'
127
137
) ;
128
138
this . changeListener = changeListener ;
129
139
// Fire with initial user.
130
- changeListener ( User . UNAUTHENTICATED ) ;
140
+ asyncQueue . enqueueRetryable ( ( ) => changeListener ( User . UNAUTHENTICATED ) ) ;
131
141
}
132
142
133
143
removeChangeListener ( ) : void {
@@ -155,14 +165,17 @@ export class EmulatorCredentialsProvider implements CredentialsProvider {
155
165
156
166
invalidateToken ( ) : void { }
157
167
158
- setChangeListener ( changeListener : CredentialChangeListener ) : void {
168
+ setChangeListener (
169
+ asyncQueue : AsyncQueue ,
170
+ changeListener : CredentialChangeListener
171
+ ) : void {
159
172
debugAssert (
160
173
! this . changeListener ,
161
174
'Can only call setChangeListener() once.'
162
175
) ;
163
176
this . changeListener = changeListener ;
164
177
// Fire with initial user.
165
- changeListener ( this . token . user ) ;
178
+ asyncQueue . enqueueRetryable ( ( ) => changeListener ( this . token . user ) ) ;
166
179
}
167
180
168
181
removeChangeListener ( ) : void {
@@ -175,11 +188,13 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
175
188
* The auth token listener registered with FirebaseApp, retained here so we
176
189
* can unregister it.
177
190
*/
178
- private tokenListener : ( ( token : string | null ) => void ) | null = null ;
191
+ private tokenListener : ( ) => void ;
179
192
180
193
/** Tracks the current User. */
181
194
private currentUser : User = User . UNAUTHENTICATED ;
182
- private receivedInitialUser : boolean = false ;
195
+
196
+ /** Promise that allows blocking on the first `tokenListener` event. */
197
+ private receivedInitialUser = new Deferred ( ) ;
183
198
184
199
/**
185
200
* Counter used to detect if the token changed while a getToken request was
@@ -188,44 +203,55 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
188
203
private tokenCounter = 0 ;
189
204
190
205
/** The listener registered with setChangeListener(). */
191
- private changeListener : CredentialChangeListener | null = null ;
206
+ private changeListener : CredentialChangeListener = ( ) => Promise . resolve ( ) ;
207
+
208
+ private invokeChangeListener = false ;
192
209
193
210
private forceRefresh = false ;
194
211
195
- private auth : FirebaseAuthInternal | null ;
212
+ private auth : FirebaseAuthInternal | null = null ;
213
+
214
+ private asyncQueue : AsyncQueue | null = null ;
196
215
197
216
constructor ( authProvider : Provider < FirebaseAuthInternalName > ) {
198
217
this . tokenListener = ( ) => {
199
218
this . tokenCounter ++ ;
200
219
this . currentUser = this . getUser ( ) ;
201
- this . receivedInitialUser = true ;
202
- if ( this . changeListener ) {
203
- this . changeListener ( this . currentUser ) ;
220
+ this . receivedInitialUser . resolve ( ) ;
221
+ if ( this . invokeChangeListener ) {
222
+ this . asyncQueue ! . enqueueRetryable ( ( ) =>
223
+ this . changeListener ( this . currentUser )
224
+ ) ;
204
225
}
205
226
} ;
206
227
207
- this . tokenCounter = 0 ;
208
-
209
- this . auth = authProvider . getImmediate ( { optional : true } ) ;
228
+ const registerAuth = ( auth : FirebaseAuthInternal ) : void => {
229
+ logDebug ( 'FirebaseCredentialsProvider' , 'Auth detected' ) ;
230
+ this . auth = auth ;
231
+ this . awaitTokenAndRaiseInitialEvent ( ) ;
232
+ this . auth . addAuthTokenListener ( this . tokenListener ) ;
233
+ } ;
210
234
211
- if ( this . auth ) {
212
- this . auth . addAuthTokenListener ( this . tokenListener ! ) ;
213
- } else {
214
- // if auth is not available, invoke tokenListener once with null token
215
- this . tokenListener ( null ) ;
216
- authProvider . get ( ) . then (
217
- auth => {
218
- this . auth = auth ;
219
- if ( this . tokenListener ) {
220
- // tokenListener can be removed by removeChangeListener()
221
- this . auth . addAuthTokenListener ( this . tokenListener ) ;
222
- }
223
- } ,
224
- ( ) => {
225
- /* this.authProvider.get() never rejects */
235
+ authProvider . onInit ( auth => registerAuth ( auth ) ) ;
236
+
237
+ // Our users can initialize Auth right after Firestore, so we give it
238
+ // a chance to register itself with the component framework before we
239
+ // determine whether to start up in unauthenticated mode.
240
+ setTimeout ( ( ) => {
241
+ if ( ! this . auth ) {
242
+ const auth = authProvider . getImmediate ( { optional : true } ) ;
243
+ if ( auth ) {
244
+ registerAuth ( auth ) ;
245
+ } else if ( this . invokeChangeListener ) {
246
+ // If auth is still not available, invoke the change listener once
247
+ // with null token
248
+ logDebug ( 'FirebaseCredentialsProvider' , 'Auth not yet detected' ) ;
249
+ this . asyncQueue ! . enqueueRetryable ( ( ) =>
250
+ this . changeListener ( this . currentUser )
251
+ ) ;
226
252
}
227
- ) ;
228
- }
253
+ }
254
+ } , 0 ) ;
229
255
}
230
256
231
257
getToken ( ) : Promise < Token | null > {
@@ -273,25 +299,21 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
273
299
this . forceRefresh = true ;
274
300
}
275
301
276
- setChangeListener ( changeListener : CredentialChangeListener ) : void {
277
- debugAssert (
278
- ! this . changeListener ,
279
- 'Can only call setChangeListener() once.'
280
- ) ;
302
+ setChangeListener (
303
+ asyncQueue : AsyncQueue ,
304
+ changeListener : CredentialChangeListener
305
+ ) : void {
306
+ debugAssert ( ! this . asyncQueue , 'Can only call setChangeListener() once.' ) ;
307
+ this . invokeChangeListener = true ;
308
+ this . asyncQueue = asyncQueue ;
281
309
this . changeListener = changeListener ;
282
-
283
- // Fire the initial event
284
- if ( this . receivedInitialUser ) {
285
- changeListener ( this . currentUser ) ;
286
- }
287
310
}
288
311
289
312
removeChangeListener ( ) : void {
290
313
if ( this . auth ) {
291
314
this . auth . removeAuthTokenListener ( this . tokenListener ! ) ;
292
315
}
293
- this . tokenListener = null ;
294
- this . changeListener = null ;
316
+ this . changeListener = ( ) => Promise . resolve ( ) ;
295
317
}
296
318
297
319
// Auth.getUid() can return null even with a user logged in. It is because
@@ -306,6 +328,21 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
306
328
) ;
307
329
return new User ( currentUid ) ;
308
330
}
331
+
332
+ /**
333
+ * Blocks the AsyncQueue until the next user is available. This function also
334
+ * invokes `this.changeListener` immediately once the token is available.
335
+ */
336
+ private awaitTokenAndRaiseInitialEvent ( ) : void {
337
+ if ( this . invokeChangeListener ) {
338
+ this . invokeChangeListener = false ; // Prevent double-firing of the listener
339
+ this . asyncQueue ! . enqueueRetryable ( async ( ) => {
340
+ await this . receivedInitialUser . promise ;
341
+ await this . changeListener ( this . currentUser ) ;
342
+ this . invokeChangeListener = true ;
343
+ } ) ;
344
+ }
345
+ }
309
346
}
310
347
311
348
// Manual type definition for the subset of Gapi we use.
@@ -368,9 +405,12 @@ export class FirstPartyCredentialsProvider implements CredentialsProvider {
368
405
) ;
369
406
}
370
407
371
- setChangeListener ( changeListener : CredentialChangeListener ) : void {
408
+ setChangeListener (
409
+ asyncQueue : AsyncQueue ,
410
+ changeListener : CredentialChangeListener
411
+ ) : void {
372
412
// Fire with initial uid.
373
- changeListener ( User . FIRST_PARTY ) ;
413
+ asyncQueue . enqueueRetryable ( ( ) => changeListener ( User . FIRST_PARTY ) ) ;
374
414
}
375
415
376
416
removeChangeListener ( ) : void { }
0 commit comments