Skip to content

Commit 44f5c2f

Browse files
Firestore can go back to blocking for auth
1 parent 63ca4ab commit 44f5c2f

File tree

3 files changed

+50
-38
lines changed

3 files changed

+50
-38
lines changed

packages/firestore/src/api/credentials.ts

+47-24
Original file line numberDiff line numberDiff line change
@@ -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 {}

packages/firestore/src/core/firestore_client.ts

+2-13
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ export class FirestoreClient {
9898
private user = User.UNAUTHENTICATED;
9999
private readonly clientId = AutoId.newId();
100100
private credentialListener: CredentialChangeListener = () => {};
101-
private readonly receivedInitialUser = new Deferred<void>();
102101

103102
offlineComponents?: OfflineComponentProvider;
104103
onlineComponents?: OnlineComponentProvider;
@@ -120,7 +119,6 @@ export class FirestoreClient {
120119
logDebug(LOG_TAG, 'Received user=', user.uid);
121120
this.user = user;
122121
this.credentialListener(user);
123-
this.receivedInitialUser.resolve();
124122
});
125123
}
126124

@@ -137,10 +135,6 @@ export class FirestoreClient {
137135

138136
setCredentialChangeListener(listener: (user: User) => void): void {
139137
this.credentialListener = listener;
140-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
141-
this.receivedInitialUser.promise.then(() =>
142-
this.credentialListener(this.user)
143-
);
144138
}
145139

146140
/**
@@ -198,8 +192,8 @@ export async function setOfflineComponentProvider(
198192
let currentUser = configuration.initialUser;
199193
client.setCredentialChangeListener(user => {
200194
if (!currentUser.isEqual(user)) {
201-
currentUser = user;
202195
localStoreHandleUserChange(offlineComponentProvider.localStore, user);
196+
currentUser = user;
203197
}
204198
});
205199

@@ -229,12 +223,7 @@ export async function setOnlineComponentProvider(
229223
// The CredentialChangeListener of the online component provider takes
230224
// precedence over the offline component provider.
231225
client.setCredentialChangeListener(user =>
232-
client.asyncQueue.enqueueRetryable(() =>
233-
remoteStoreHandleCredentialChange(
234-
onlineComponentProvider.remoteStore,
235-
user
236-
)
237-
)
226+
remoteStoreHandleCredentialChange(onlineComponentProvider.remoteStore, user)
238227
);
239228
client.onlineComponents = onlineComponentProvider;
240229
}

packages/firestore/test/integration/api/batch_writes.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ apiDescribe('Database batch writes', (persistence: boolean) => {
4848
});
4949
});
5050

51-
it.only('can set documents with merge', () => {
51+
it('can set documents with merge', () => {
5252
return integrationHelpers.withTestDoc(persistence, doc => {
5353
return doc.firestore
5454
.batch()

0 commit comments

Comments
 (0)