@@ -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,20 @@ 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 ( ( ) => {
141
+ changeListener ( User . FIRST_PARTY ) ;
142
+ return Promise . resolve ( ) ;
143
+ } ) ;
131
144
}
132
145
133
146
removeChangeListener ( ) : void {
@@ -175,11 +188,25 @@ 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 ) | null = null ;
179
192
180
193
/** Tracks the current User. */
181
194
private currentUser : User = User . UNAUTHENTICATED ;
182
- private receivedInitialUser : boolean = false ;
195
+
196
+ /**
197
+ * Promise that allows blocking on the next `tokenChange` event. The Promise
198
+ * is re-assgined in `awaitTokenAndRaiseInitialEvent()` to allow blocking on
199
+ * an a lazily loaded Auth instance. In this case, `this.receivedUser`
200
+ * resolves once when the SDK first detects that there is no synchronous
201
+ * Auth, and then gets re-created and resolves again once Auth is loaded.
202
+ */
203
+ private receivedUser = new Deferred ( ) ;
204
+
205
+ /**
206
+ * Whether the initial token event has been raised. This can go back to
207
+ * `false` if Firestore first starts without Auth and Auth is loaded later.
208
+ */
209
+ private initialEventRaised : boolean = false ;
183
210
184
211
/**
185
212
* Counter used to detect if the token changed while a getToken request was
@@ -188,44 +215,62 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
188
215
private tokenCounter = 0 ;
189
216
190
217
/** The listener registered with setChangeListener(). */
191
- private changeListener : CredentialChangeListener | null = null ;
218
+ private changeListener : CredentialChangeListener = ( ) => Promise . resolve ( ) ;
192
219
193
220
private forceRefresh = false ;
194
221
195
- private auth : FirebaseAuthInternal | null ;
222
+ private auth : FirebaseAuthInternal | null = null ;
223
+
224
+ private asyncQueue : AsyncQueue | null = null ;
196
225
197
226
constructor ( authProvider : Provider < FirebaseAuthInternalName > ) {
198
227
this . tokenListener = ( ) => {
199
228
this . tokenCounter ++ ;
229
+ this . receivedUser . resolve ( ) ;
200
230
this . currentUser = this . getUser ( ) ;
201
- this . receivedInitialUser = true ;
202
- if ( this . changeListener ) {
203
- this . changeListener ( this . currentUser ) ;
231
+ if ( this . initialEventRaised && this . asyncQueue ) {
232
+ // We only invoke the change listener here if the initial event has been
233
+ // raised. The initial event itself is invoked synchronously in
234
+ // `awaitTokenAndRaiseInitialEvent()`.
235
+ this . asyncQueue . enqueueRetryable ( ( ) => {
236
+ this . changeListener ( this . currentUser ) ;
237
+ return Promise . resolve ( ) ;
238
+ } ) ;
204
239
}
205
240
} ;
206
241
207
242
this . tokenCounter = 0 ;
208
243
209
- this . auth = authProvider . getImmediate ( { optional : true } ) ;
244
+ const registerAuth = ( auth : FirebaseAuthInternal ) : void => {
245
+ logDebug ( 'FirebaseCredentialsProvider' , 'Auth detected' ) ;
246
+ this . auth = auth ;
247
+ if ( this . tokenListener ) {
248
+ // tokenListener can be removed by removeChangeListener()
249
+ this . awaitTokenAndRaiseInitialEvent ( ) ;
250
+ this . auth . addAuthTokenListener ( this . tokenListener ) ;
251
+ }
252
+ } ;
210
253
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 */
254
+ authProvider . onInit ( auth => registerAuth ( auth ) ) ;
255
+
256
+ // Our users can initialize Auth right after Firestore, so we give it
257
+ // a chance to register itself with the component framework before we
258
+ // determine whether to start up in unauthenticated mode.
259
+ setTimeout ( ( ) => {
260
+ if ( ! this . auth ) {
261
+ const auth = authProvider . getImmediate ( { optional : true } ) ;
262
+ if ( auth ) {
263
+ registerAuth ( auth ) ;
264
+ } else if ( this . tokenListener ) {
265
+ // If auth is still not available, invoke tokenListener once with null
266
+ // token
267
+ logDebug ( 'FirebaseCredentialsProvider' , 'Auth not yet detected' ) ;
268
+ this . tokenListener ( ) ;
226
269
}
227
- ) ;
228
- }
270
+ }
271
+ } , 0 ) ;
272
+
273
+ this . awaitTokenAndRaiseInitialEvent ( ) ;
229
274
}
230
275
231
276
getToken ( ) : Promise < Token | null > {
@@ -273,25 +318,21 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
273
318
this . forceRefresh = true ;
274
319
}
275
320
276
- setChangeListener ( changeListener : CredentialChangeListener ) : void {
277
- debugAssert (
278
- ! this . changeListener ,
279
- 'Can only call setChangeListener() once.'
280
- ) ;
321
+ setChangeListener (
322
+ asyncQueue : AsyncQueue ,
323
+ changeListener : CredentialChangeListener
324
+ ) : void {
325
+ debugAssert ( ! this . asyncQueue , 'Can only call setChangeListener() once.' ) ;
326
+ this . asyncQueue = asyncQueue ;
281
327
this . changeListener = changeListener ;
282
-
283
- // Fire the initial event
284
- if ( this . receivedInitialUser ) {
285
- changeListener ( this . currentUser ) ;
286
- }
287
328
}
288
329
289
330
removeChangeListener ( ) : void {
290
331
if ( this . auth ) {
291
332
this . auth . removeAuthTokenListener ( this . tokenListener ! ) ;
292
333
}
293
334
this . tokenListener = null ;
294
- this . changeListener = null ;
335
+ this . changeListener = ( ) => Promise . resolve ( ) ;
295
336
}
296
337
297
338
// Auth.getUid() can return null even with a user logged in. It is because
@@ -306,6 +347,33 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
306
347
) ;
307
348
return new User ( currentUid ) ;
308
349
}
350
+
351
+ /**
352
+ * Blocks the AsyncQueue until the next user is available. This is invoked
353
+ * on SDK start to wait for the first user token (or `null` if Auth is not yet
354
+ * loaded). If Auth is loaded after Firestore,
355
+ * `awaitTokenAndRaiseInitialEvent()` is also used to block Firestore until
356
+ * Auth is fully initialized.
357
+ *
358
+ * This function also invokes the change listener synchronously once a token
359
+ * is available.
360
+ */
361
+ private awaitTokenAndRaiseInitialEvent ( ) : void {
362
+ this . initialEventRaised = false ;
363
+ if ( this . asyncQueue ) {
364
+ // Create a new deferred Promise that gets resolved when we receive the
365
+ // next token. Ensure that all previous Promises also get resolved.
366
+ const awaitToken = new Deferred < void > ( ) ;
367
+ void awaitToken . promise . then ( ( ) => awaitToken . resolve ( ) ) ;
368
+ this . receivedUser = awaitToken ;
369
+
370
+ this . asyncQueue . enqueueRetryable ( async ( ) => {
371
+ await awaitToken . promise ;
372
+ await this . changeListener ( this . currentUser ) ;
373
+ } ) ;
374
+ }
375
+ this . initialEventRaised = true ;
376
+ }
309
377
}
310
378
311
379
// Manual type definition for the subset of Gapi we use.
@@ -368,9 +436,15 @@ export class FirstPartyCredentialsProvider implements CredentialsProvider {
368
436
) ;
369
437
}
370
438
371
- setChangeListener ( changeListener : CredentialChangeListener ) : void {
439
+ setChangeListener (
440
+ asyncQueue : AsyncQueue ,
441
+ changeListener : CredentialChangeListener
442
+ ) : void {
372
443
// Fire with initial uid.
373
- changeListener ( User . FIRST_PARTY ) ;
444
+ asyncQueue . enqueueRetryable ( ( ) => {
445
+ changeListener ( User . FIRST_PARTY ) ;
446
+ return Promise . resolve ( ) ;
447
+ } ) ;
374
448
}
375
449
376
450
removeChangeListener ( ) : void { }
0 commit comments