@@ -60,6 +60,7 @@ export class AuthImpl implements Auth, _FirebaseService {
60
60
private idTokenSubscription = new Subscription < externs . User > ( this ) ;
61
61
private redirectUser : User | null = null ;
62
62
private isProactiveRefreshEnabled = false ;
63
+ private redirectInitializerError : Error | null = null ;
63
64
64
65
// Any network calls will set this to true and prevent subsequent emulator
65
66
// initialization
@@ -109,17 +110,21 @@ export class AuthImpl implements Auth, _FirebaseService {
109
110
return ;
110
111
}
111
112
112
- await this . initializeCurrentUser ( ) ;
113
+ await this . initializeCurrentUser ( popupRedirectResolver ) ;
113
114
114
115
if ( this . _deleted ) {
115
116
return ;
116
117
}
117
118
118
119
this . _isInitialized = true ;
119
- this . notifyAuthListeners ( ) ;
120
120
} ) ;
121
121
122
- return this . _initializationPromise ;
122
+ // After initialization completes, throw any error caused by redirect flow
123
+ return this . _initializationPromise . then ( ( ) => {
124
+ if ( this . redirectInitializerError ) {
125
+ throw this . redirectInitializerError ;
126
+ }
127
+ } ) ;
123
128
}
124
129
125
130
/**
@@ -149,18 +154,40 @@ export class AuthImpl implements Auth, _FirebaseService {
149
154
150
155
// Update current Auth state. Either a new login or logout.
151
156
await this . _updateCurrentUser ( user ) ;
152
- // Notify external Auth changes of Auth change event.
153
- this . notifyAuthListeners ( ) ;
154
157
}
155
158
156
- private async initializeCurrentUser ( ) : Promise < void > {
157
- const storedUser = ( await this . assertedPersistence . getCurrentUser ( ) ) as User | null ;
159
+ private async initializeCurrentUser (
160
+ popupRedirectResolver ?: externs . PopupRedirectResolver
161
+ ) : Promise < void > {
162
+ // First check to see if we have a pending redirect event.
163
+ let storedUser = ( await this . assertedPersistence . getCurrentUser ( ) ) as User | null ;
164
+ if ( popupRedirectResolver ) {
165
+ await this . getOrInitRedirectPersistenceManager ( ) ;
166
+ const redirectUserEventId = this . redirectUser ?. _redirectEventId ;
167
+ const storedUserEventId = storedUser ?. _redirectEventId ;
168
+ const result = await this . tryRedirectSignIn ( popupRedirectResolver ) ;
169
+
170
+ // If the stored user (i.e. the old "currentUser") has a redirectId that
171
+ // matches the redirect user, then we want to initially sign in with the
172
+ // new user object from result.
173
+ // TODO(samgho): More thoroughly test all of this
174
+ if (
175
+ ( ! redirectUserEventId || redirectUserEventId === storedUserEventId ) &&
176
+ result ?. user
177
+ ) {
178
+ storedUser = result . user as User ;
179
+ }
180
+ }
181
+
182
+ // If no user in persistence, there is no current user. Set to null.
158
183
if ( ! storedUser ) {
159
- return this . directlySetCurrentUser ( storedUser ) ;
184
+ return this . directlySetCurrentUser ( null ) ;
160
185
}
161
186
162
187
if ( ! storedUser . _redirectEventId ) {
163
188
// This isn't a redirect user, we can reload and bail
189
+ // This will also catch the redirected user, if available, as that method
190
+ // strips the _redirectEventId
164
191
return this . reloadAndSetCurrentUserOrClear ( storedUser ) ;
165
192
}
166
193
@@ -182,6 +209,42 @@ export class AuthImpl implements Auth, _FirebaseService {
182
209
return this . reloadAndSetCurrentUserOrClear ( storedUser ) ;
183
210
}
184
211
212
+ private async tryRedirectSignIn (
213
+ redirectResolver : externs . PopupRedirectResolver
214
+ ) : Promise < externs . UserCredential | null > {
215
+ // The redirect user needs to be checked (and signed in if available)
216
+ // during auth initialization. All of the normal sign in and link/reauth
217
+ // flows call back into auth and push things onto the promise queue. We
218
+ // need to await the result of the redirect sign in *inside the promise
219
+ // queue*. This presents a problem: we run into deadlock. See:
220
+ // ┌> [Initialization] ─────┐
221
+ // ┌> [<other queue tasks>] │
222
+ // └─ [getRedirectResult] <─┘
223
+ // where [] are tasks on the queue and arrows denote awaits
224
+ // Initialization will never complete because it's waiting on something
225
+ // that's waiting for initialization to complete!
226
+ //
227
+ // Instead, this method calls getRedirectResult() (stored in
228
+ // _completeRedirectFn) with an optional parameter that instructs all of
229
+ // the underlying auth operations to skip anything that mutates auth state.
230
+
231
+ let result : externs . UserCredential | null = null ;
232
+ try {
233
+ // We know this._popupRedirectResolver is set since redirectResolver
234
+ // is passed in. The _completeRedirectFn expects the unwrapped extern.
235
+ result = await this . _popupRedirectResolver ! . _completeRedirectFn (
236
+ this ,
237
+ redirectResolver ,
238
+ true
239
+ ) ;
240
+ } catch ( e ) {
241
+ this . redirectInitializerError = e ;
242
+ await this . _setRedirectUser ( null ) ;
243
+ }
244
+
245
+ return result ;
246
+ }
247
+
185
248
private async reloadAndSetCurrentUserOrClear ( user : User ) : Promise < void > {
186
249
try {
187
250
await _reloadWithoutSaving ( user ) ;
@@ -243,6 +306,7 @@ export class AuthImpl implements Auth, _FirebaseService {
243
306
{ appName : this . name }
244
307
) ;
245
308
}
309
+
246
310
return this . queue ( async ( ) => {
247
311
await this . directlySetCurrentUser ( user as User | null ) ;
248
312
this . notifyAuthListeners ( ) ;
@@ -335,8 +399,11 @@ export class AuthImpl implements Auth, _FirebaseService {
335
399
}
336
400
337
401
async _redirectUserForId ( id : string ) : Promise < User | null > {
338
- // Make sure we've cleared any pending ppersistence actions
339
- await this . queue ( async ( ) => { } ) ;
402
+ // Make sure we've cleared any pending persistence actions if we're not in
403
+ // the initializer
404
+ if ( this . _isInitialized ) {
405
+ await this . queue ( async ( ) => { } ) ;
406
+ }
340
407
341
408
if ( this . _currentUser ?. _redirectEventId === id ) {
342
409
return this . _currentUser ;
0 commit comments