@@ -87,6 +87,15 @@ export type CredentialChangeListener = (user: User) => Promise<void>;
87
87
* listening for changes.
88
88
*/
89
89
export interface CredentialsProvider {
90
+ /**
91
+ * Starts the credentials provider and specifies a listener to be notified of
92
+ * credential changes (sign-in / sign-out, token changes). It is immediately
93
+ * called once with the initial user.
94
+ *
95
+ * The change listener is invoked on the provided AsyncQueue.
96
+ */
97
+ start ( asyncQueue : AsyncQueue , changeListener : CredentialChangeListener ) : void ;
98
+
90
99
/** Requests a token for the current user. */
91
100
getToken ( ) : Promise < Token | null > ;
92
101
@@ -96,53 +105,26 @@ export interface CredentialsProvider {
96
105
*/
97
106
invalidateToken ( ) : void ;
98
107
99
- /**
100
- * Specifies a listener to be notified of credential changes
101
- * (sign-in / sign-out, token changes). It is immediately called once with the
102
- * initial user.
103
- *
104
- * The change listener is invoked on the provided AsyncQueue.
105
- */
106
- setChangeListener (
107
- asyncQueue : AsyncQueue ,
108
- changeListener : CredentialChangeListener
109
- ) : void ;
110
-
111
- /** Removes the previously-set change listener. */
112
- removeChangeListener ( ) : void ;
108
+ shutdown ( ) : void ;
113
109
}
114
110
115
111
/** A CredentialsProvider that always yields an empty token. */
116
112
export class EmptyCredentialsProvider implements CredentialsProvider {
117
- /**
118
- * Stores the listener registered with setChangeListener()
119
- * This isn't actually necessary since the UID never changes, but we use this
120
- * to verify the listen contract is adhered to in tests.
121
- */
122
- private changeListener : CredentialChangeListener | null = null ;
123
-
124
113
getToken ( ) : Promise < Token | null > {
125
114
return Promise . resolve < Token | null > ( null ) ;
126
115
}
127
116
128
117
invalidateToken ( ) : void { }
129
118
130
- setChangeListener (
119
+ start (
131
120
asyncQueue : AsyncQueue ,
132
121
changeListener : CredentialChangeListener
133
122
) : void {
134
- debugAssert (
135
- ! this . changeListener ,
136
- 'Can only call setChangeListener() once.'
137
- ) ;
138
- this . changeListener = changeListener ;
139
123
// Fire with initial user.
140
124
asyncQueue . enqueueRetryable ( ( ) => changeListener ( User . UNAUTHENTICATED ) ) ;
141
125
}
142
126
143
- removeChangeListener ( ) : void {
144
- this . changeListener = null ;
145
- }
127
+ shutdown ( ) : void { }
146
128
}
147
129
148
130
/**
@@ -165,7 +147,7 @@ export class EmulatorCredentialsProvider implements CredentialsProvider {
165
147
166
148
invalidateToken ( ) : void { }
167
149
168
- setChangeListener (
150
+ start (
169
151
asyncQueue : AsyncQueue ,
170
152
changeListener : CredentialChangeListener
171
153
) : void {
@@ -178,80 +160,146 @@ export class EmulatorCredentialsProvider implements CredentialsProvider {
178
160
asyncQueue . enqueueRetryable ( ( ) => changeListener ( this . token . user ) ) ;
179
161
}
180
162
181
- removeChangeListener ( ) : void {
163
+ shutdown ( ) : void {
182
164
this . changeListener = null ;
183
165
}
184
166
}
185
167
168
+ /** Credential provider for the Lite SDK. */
169
+ export class LiteCredentialsProvider implements CredentialsProvider {
170
+ private auth : FirebaseAuthInternal | null = null ;
171
+
172
+ constructor ( authProvider : Provider < FirebaseAuthInternalName > ) {
173
+ authProvider . onInit ( auth => {
174
+ this . auth = auth ;
175
+ } ) ;
176
+ }
177
+
178
+ getToken ( ) : Promise < Token | null > {
179
+ if ( ! this . auth ) {
180
+ return Promise . resolve ( null ) ;
181
+ }
182
+
183
+ return this . auth . getToken ( ) . then ( tokenData => {
184
+ if ( tokenData ) {
185
+ hardAssert (
186
+ typeof tokenData . accessToken === 'string' ,
187
+ 'Invalid tokenData returned from getToken():' + tokenData
188
+ ) ;
189
+ return new OAuthToken (
190
+ tokenData . accessToken ,
191
+ new User ( this . auth ! . getUid ( ) )
192
+ ) ;
193
+ } else {
194
+ return null ;
195
+ }
196
+ } ) ;
197
+ }
198
+
199
+ invalidateToken ( ) : void { }
200
+
201
+ start (
202
+ asyncQueue : AsyncQueue ,
203
+ changeListener : CredentialChangeListener
204
+ ) : void { }
205
+
206
+ shutdown ( ) : void { }
207
+ }
208
+
186
209
export class FirebaseCredentialsProvider implements CredentialsProvider {
187
210
/**
188
211
* The auth token listener registered with FirebaseApp, retained here so we
189
212
* can unregister it.
190
213
*/
191
- private tokenListener : ( ) => void ;
214
+ private tokenListener ! : ( ) => void ;
192
215
193
216
/** Tracks the current User. */
194
217
private currentUser : User = User . UNAUTHENTICATED ;
195
218
196
- /** Promise that allows blocking on the initialization of Firebase Auth. */
197
- private authDeferred = new Deferred ( ) ;
198
-
199
219
/**
200
220
* Counter used to detect if the token changed while a getToken request was
201
221
* outstanding.
202
222
*/
203
223
private tokenCounter = 0 ;
204
224
205
- /** The listener registered with setChangeListener(). */
206
- private changeListener ?: CredentialChangeListener ;
207
-
208
225
private forceRefresh = false ;
209
226
210
227
private auth : FirebaseAuthInternal | null = null ;
211
228
212
- private asyncQueue : AsyncQueue | null = null ;
229
+ constructor ( private authProvider : Provider < FirebaseAuthInternalName > ) { }
230
+
231
+ start (
232
+ asyncQueue : AsyncQueue ,
233
+ changeListener : CredentialChangeListener
234
+ ) : void {
235
+ let lastTokenId = - 1 ;
236
+
237
+ // A change listener that prevents double-firing for the same token change.
238
+ const guardedChangeListener : ( user : User ) => Promise < void > = user => {
239
+ if ( this . tokenCounter !== lastTokenId ) {
240
+ lastTokenId = this . tokenCounter ;
241
+ return changeListener ( user ) ;
242
+ } else {
243
+ return Promise . resolve ( ) ;
244
+ }
245
+ } ;
246
+
247
+ // A promise that can be waited on to block on the next token change.
248
+ // This promise is re-created after each change.
249
+ let nextToken = new Deferred < void > ( ) ;
213
250
214
- constructor ( authProvider : Provider < FirebaseAuthInternalName > ) {
215
251
this . tokenListener = ( ) => {
216
252
this . tokenCounter ++ ;
217
253
this . currentUser = this . getUser ( ) ;
218
- this . authDeferred . resolve ( ) ;
219
- if ( this . changeListener ) {
220
- this . asyncQueue ! . enqueueRetryable ( ( ) =>
221
- this . changeListener ! ( this . currentUser )
222
- ) ;
223
- }
254
+ nextToken . resolve ( ) ;
255
+ nextToken = new Deferred < void > ( ) ;
256
+ asyncQueue . enqueueRetryable ( ( ) =>
257
+ guardedChangeListener ( this . currentUser )
258
+ ) ;
224
259
} ;
225
260
226
261
const registerAuth = ( auth : FirebaseAuthInternal ) : void => {
227
- logDebug ( 'FirebaseCredentialsProvider' , 'Auth detected' ) ;
228
- this . auth = auth ;
229
- this . auth . addAuthTokenListener ( this . tokenListener ) ;
262
+ asyncQueue . enqueueRetryable ( async ( ) => {
263
+ logDebug ( 'FirebaseCredentialsProvider' , 'Auth detected' ) ;
264
+ this . auth = auth ;
265
+ this . auth . addAuthTokenListener ( this . tokenListener ) ;
266
+
267
+ // Call the change listener inline to block on the user change.
268
+ await nextToken . promise ;
269
+ await guardedChangeListener ( this . currentUser ) ;
270
+ } ) ;
230
271
} ;
231
272
232
- authProvider . onInit ( auth => registerAuth ( auth ) ) ;
273
+ this . authProvider . onInit ( auth => registerAuth ( auth ) ) ;
233
274
234
275
// Our users can initialize Auth right after Firestore, so we give it
235
276
// a chance to register itself with the component framework before we
236
277
// determine whether to start up in unauthenticated mode.
237
278
setTimeout ( ( ) => {
238
279
if ( ! this . auth ) {
239
- const auth = authProvider . getImmediate ( { optional : true } ) ;
280
+ const auth = this . authProvider . getImmediate ( { optional : true } ) ;
240
281
if ( auth ) {
241
282
registerAuth ( auth ) ;
242
283
} else {
243
284
// If auth is still not available, proceed with `null` user
244
285
logDebug ( 'FirebaseCredentialsProvider' , 'Auth not yet detected' ) ;
245
- this . authDeferred . resolve ( ) ;
286
+ nextToken . resolve ( ) ;
287
+ nextToken = new Deferred < void > ( ) ;
246
288
}
247
289
}
248
290
} , 0 ) ;
291
+
292
+ asyncQueue . enqueueRetryable ( async ( ) => {
293
+ // Call the change listener inline to block on the user change.
294
+ await nextToken . promise ;
295
+ await guardedChangeListener ( this . currentUser ) ;
296
+ } ) ;
249
297
}
250
298
251
299
getToken ( ) : Promise < Token | null > {
252
300
debugAssert (
253
301
this . tokenListener != null ,
254
- 'getToken cannot be called after listener removed .'
302
+ 'FirebaseCredentialsProvider not started .'
255
303
) ;
256
304
257
305
// Take note of the current value of the tokenCounter so that this method
@@ -293,26 +341,10 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
293
341
this . forceRefresh = true ;
294
342
}
295
343
296
- setChangeListener (
297
- asyncQueue : AsyncQueue ,
298
- changeListener : CredentialChangeListener
299
- ) : void {
300
- debugAssert ( ! this . asyncQueue , 'Can only call setChangeListener() once.' ) ;
301
- this . asyncQueue = asyncQueue ;
302
-
303
- // Blocks the AsyncQueue until the next user is available.
304
- this . asyncQueue ! . enqueueRetryable ( async ( ) => {
305
- await this . authDeferred . promise ;
306
- await changeListener ( this . currentUser ) ;
307
- this . changeListener = changeListener ;
308
- } ) ;
309
- }
310
-
311
- removeChangeListener ( ) : void {
344
+ shutdown ( ) : void {
312
345
if ( this . auth ) {
313
346
this . auth . removeAuthTokenListener ( this . tokenListener ! ) ;
314
347
}
315
- this . changeListener = ( ) => Promise . resolve ( ) ;
316
348
}
317
349
318
350
// Auth.getUid() can return null even with a user logged in. It is because
@@ -389,15 +421,15 @@ export class FirstPartyCredentialsProvider implements CredentialsProvider {
389
421
) ;
390
422
}
391
423
392
- setChangeListener (
424
+ start (
393
425
asyncQueue : AsyncQueue ,
394
426
changeListener : CredentialChangeListener
395
427
) : void {
396
428
// Fire with initial uid.
397
429
asyncQueue . enqueueRetryable ( ( ) => changeListener ( User . FIRST_PARTY ) ) ;
398
430
}
399
431
400
- removeChangeListener ( ) : void { }
432
+ shutdown ( ) : void { }
401
433
402
434
invalidateToken ( ) : void { }
403
435
}
0 commit comments