Skip to content

Commit 63ca4ab

Browse files
WIP
1 parent c4792c6 commit 63ca4ab

File tree

4 files changed

+70
-50
lines changed

4 files changed

+70
-50
lines changed

common/api-review/database.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { EmulatorMockTokenOptions } from '@firebase/util';
88
import { FirebaseApp } from '@firebase/app';
99

10-
// @public
10+
// @public (undocumented)
1111
export function child(parent: Reference, path: string): Reference;
1212

1313
// @public

packages/firestore/src/api/credentials.ts

+55-28
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,6 @@ export class OAuthToken implements Token {
8282
*/
8383
export type CredentialChangeListener = (user: User) => void;
8484

85-
/**
86-
* A Listener for credential change events. The listener blocks until a user
87-
* is available.
88-
*/
89-
export type AsyncCredentialChangeListener = (user: Promise<User>) => void;
90-
9185
/**
9286
* Provides methods for getting the uid and token for the current user and
9387
* listening for changes.
@@ -107,7 +101,10 @@ export interface CredentialsProvider {
107101
* (sign-in / sign-out, token changes). It is immediately called once with the
108102
* initial user.
109103
*/
110-
setChangeListener(changeListener: AsyncCredentialChangeListener): void;
104+
setChangeListener(
105+
asyncQueue: AsyncQueue,
106+
changeListener: CredentialChangeListener
107+
): void;
111108

112109
/** Removes the previously-set change listener. */
113110
removeChangeListener(): void;
@@ -120,22 +117,25 @@ export class EmptyCredentialsProvider implements CredentialsProvider {
120117
* This isn't actually necessary since the UID never changes, but we use this
121118
* to verify the listen contract is adhered to in tests.
122119
*/
123-
private changeListener: AsyncCredentialChangeListener | null = null;
120+
private changeListener: CredentialChangeListener | null = null;
124121

125122
getToken(): Promise<Token | null> {
126123
return Promise.resolve<Token | null>(null);
127124
}
128125

129126
invalidateToken(): void {}
130127

131-
setChangeListener(changeListener: AsyncCredentialChangeListener): void {
128+
setChangeListener(
129+
asyncQueue: AsyncQueue,
130+
changeListener: CredentialChangeListener
131+
): void {
132132
debugAssert(
133133
!this.changeListener,
134134
'Can only call setChangeListener() once.'
135135
);
136136
this.changeListener = changeListener;
137137
// Fire with initial user.
138-
changeListener(Promise.resolve(User.UNAUTHENTICATED));
138+
changeListener(User.UNAUTHENTICATED);
139139
}
140140

141141
removeChangeListener(): void {
@@ -161,23 +161,25 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
161161
private tokenCounter = 0;
162162

163163
/** The listener registered with setChangeListener(). */
164-
private changeListener: AsyncCredentialChangeListener | null = null;
164+
private changeListener: CredentialChangeListener | null = null;
165165

166166
private forceRefresh = false;
167167

168168
private auth: FirebaseAuthInternal | null = null;
169169

170-
private authInitialized = new Deferred<void>();
170+
private asyncQueue: AsyncQueue | null = null;
171+
172+
private authListeners: Array<Deferred> = [];
171173

172174
constructor(authProvider: Provider<FirebaseAuthInternalName>) {
173175
this.tokenListener = () => {
174176
this.tokenCounter++;
175-
this.authInitialized.resolve();
177+
this.authListeners.forEach(listener => listener.resolve());
176178
this.currentUser = this.getUser();
177-
this.receivedInitialUser = true;
178-
if (this.changeListener) {
179-
this.changeListener(Promise.resolve(this.currentUser));
179+
if (this.receivedInitialUser && this.changeListener) {
180+
this.changeListener(this.currentUser);
180181
}
182+
this.receivedInitialUser = true;
181183
};
182184

183185
this.tokenCounter = 0;
@@ -186,15 +188,10 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
186188
logDebug('FirebaseCredentialsProvider', 'Auth detected');
187189
this.auth = auth;
188190
this.receivedInitialUser = false;
189-
// tokenListener/changeListener can be removed by removeChangeListener()
190-
if (this.tokenListener && this.changeListener) {
191+
// tokenListener can be removed by removeChangeListener()
192+
if (this.tokenListener) {
193+
this.awaitToken();
191194
this.auth.addAuthTokenListener(this.tokenListener);
192-
// Block the change listener until auth becomes available
193-
this.changeListener(
194-
new Promise<User>(resolve => {
195-
this.authInitialized.promise.then(() => resolve(this.getUser()));
196-
})
197-
);
198195
}
199196
};
200197

@@ -216,6 +213,8 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
216213
}
217214
}
218215
}, 0);
216+
217+
this.awaitToken();
219218
}
220219

221220
getToken(): Promise<Token | null> {
@@ -263,16 +262,22 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
263262
this.forceRefresh = true;
264263
}
265264

266-
setChangeListener(changeListener: AsyncCredentialChangeListener): void {
265+
setChangeListener(
266+
asyncQueue: AsyncQueue,
267+
changeListener: CredentialChangeListener
268+
): void {
267269
debugAssert(
268270
!this.changeListener,
269271
'Can only call setChangeListener() once.'
270272
);
271273
this.changeListener = changeListener;
274+
this.asyncQueue = asyncQueue;
272275

273276
// Fire the initial event
274277
if (this.receivedInitialUser) {
275-
changeListener(Promise.resolve(this.currentUser));
278+
asyncQueue.enqueueRetryable(() => {
279+
changeListener(this.currentUser);
280+
});
276281
}
277282
}
278283

@@ -296,6 +301,25 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
296301
);
297302
return new User(currentUid);
298303
}
304+
305+
/**
306+
* Blocks the AsyncQueue until the next user is available. This is invoked
307+
* 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.
310+
*/
311+
private awaitToken(): void {
312+
if (this.asyncQueue) {
313+
this.authListeners.push(new Deferred<void>());
314+
this.asyncQueue.enqueueRetryable(async () => {
315+
console.log('Blocking queue');
316+
await Promise.all(this.authListeners.map(deferred => deferred.promise));
317+
if (this.changeListener) {
318+
this.changeListener(this.currentUser);
319+
}
320+
});
321+
}
322+
}
299323
}
300324

301325
// Manual type definition for the subset of Gapi we use.
@@ -358,9 +382,12 @@ export class FirstPartyCredentialsProvider implements CredentialsProvider {
358382
);
359383
}
360384

361-
setChangeListener(changeListener: AsyncCredentialChangeListener): void {
385+
setChangeListener(
386+
asyncQueue: AsyncQueue,
387+
changeListener: CredentialChangeListener
388+
): void {
362389
// Fire with initial uid.
363-
changeListener(Promise.resolve(User.FIRST_PARTY));
390+
changeListener(User.FIRST_PARTY);
364391
}
365392

366393
removeChangeListener(): void {}

packages/firestore/src/core/firestore_client.ts

+6-16
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,15 @@ export class FirestoreClient {
116116
public asyncQueue: AsyncQueue,
117117
private databaseInfo: DatabaseInfo
118118
) {
119-
this.credentials.setChangeListener((userProvider: Promise<User>) => {
120-
this.asyncQueue.enqueueAndForget(async () => {
121-
const user = await userProvider;
122-
logDebug(LOG_TAG, 'Received user=', user.uid);
123-
this.user = user;
124-
this.credentialListener(user);
125-
this.receivedInitialUser.resolve();
126-
});
119+
this.credentials.setChangeListener(asyncQueue, user => {
120+
logDebug(LOG_TAG, 'Received user=', user.uid);
121+
this.user = user;
122+
this.credentialListener(user);
123+
this.receivedInitialUser.resolve();
127124
});
128125
}
129126

130127
async getConfiguration(): Promise<ComponentConfiguration> {
131-
await this.receivedInitialUser.promise;
132-
133128
return {
134129
asyncQueue: this.asyncQueue,
135130
databaseInfo: this.databaseInfo,
@@ -204,12 +199,7 @@ export async function setOfflineComponentProvider(
204199
client.setCredentialChangeListener(user => {
205200
if (!currentUser.isEqual(user)) {
206201
currentUser = user;
207-
client.asyncQueue.enqueueRetryable(async () => {
208-
await localStoreHandleUserChange(
209-
offlineComponentProvider.localStore,
210-
user
211-
);
212-
});
202+
localStoreHandleUserChange(offlineComponentProvider.localStore, user);
213203
}
214204
});
215205

packages/firestore/test/integration/util/internal_helpers.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import * as firestore from '@firebase/firestore-types';
1919

2020
import {
21-
AsyncCredentialChangeListener,
2221
CredentialChangeListener,
2322
CredentialsProvider,
2423
EmptyCredentialsProvider
@@ -36,6 +35,7 @@ import { key } from '../../util/helpers';
3635

3736
import { withTestDbsSettings } from './helpers';
3837
import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS } from './settings';
38+
import { AsyncQueue } from '../../../src/util/async_queue';
3939

4040
export function asyncQueue(db: firestore.FirebaseFirestore): AsyncQueueImpl {
4141
return (db as Firestore)._delegate._queue as AsyncQueueImpl;
@@ -65,14 +65,17 @@ export function withTestDatastore(
6565
}
6666

6767
export class MockCredentialsProvider extends EmptyCredentialsProvider {
68-
private listener: AsyncCredentialChangeListener | null = null;
68+
private listener: CredentialChangeListener | null = null;
6969

7070
triggerUserChange(newUser: User): void {
71-
this.listener!(Promise.resolve(newUser));
71+
this.listener!(newUser);
7272
}
7373

74-
setChangeListener(listener: AsyncCredentialChangeListener): void {
75-
super.setChangeListener(listener);
74+
setChangeListener(
75+
asyncQueue: AsyncQueue,
76+
listener: CredentialChangeListener
77+
): void {
78+
super.setChangeListener(asyncQueue, listener);
7679
this.listener = listener;
7780
}
7881
}

0 commit comments

Comments
 (0)