Skip to content

Commit c8f0ee1

Browse files
authored
firebase-ios-sdk/1499: Restart streams on any token change. (#222)
[Port of firebase/firebase-js-sdk#1120] See firebase/firebase-ios-sdk#1499 This reworks our "user listener" into a "credential change listener" that fires on any token change, even if the User did not change. This allows us to restart our streams (but not switch mutation queues, etc.) on token changes.
1 parent 9bcb18d commit c8f0ee1

File tree

7 files changed

+58
-52
lines changed

7 files changed

+58
-52
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/auth/CredentialsProvider.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ public abstract class CredentialsProvider {
2727
public abstract Task<String> getToken();
2828

2929
/**
30-
* Marks the last retrieved token as invalid, making the next {@link #GetToken} request force
30+
* Marks the last retrieved token as invalid, making the next {@link #getToken} request force
3131
* refresh the token.
3232
*/
3333
public abstract void invalidateToken();
3434

3535
/**
36-
* Sets the listener to be notified of user changes (sign-in / sign-out). It is immediately called
37-
* once with the initial user.
36+
* Sets the listener to be notified of credential changes (sign-in / sign-out, token changes). It
37+
* is immediately called once with the initial user.
3838
*/
39-
public abstract void setUserChangeListener(Listener<User> listener);
39+
public abstract void setChangeListener(Listener<User> changeListener);
4040

41-
/** Removes the listener set with {@link #setUserChangeListener}. */
42-
public abstract void removeUserChangeListener();
41+
/** Removes the listener set with {@link #setChangeListener}. */
42+
public abstract void removeChangeListener();
4343
}

firebase-firestore/src/main/java/com/google/firebase/firestore/auth/EmptyCredentialsProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ public Task<String> getToken() {
3232
public void invalidateToken() {}
3333

3434
@Override
35-
public void setUserChangeListener(Listener<User> listener) {
36-
listener.onValue(User.UNAUTHENTICATED);
35+
public void setChangeListener(Listener<User> changeListener) {
36+
changeListener.onValue(User.UNAUTHENTICATED);
3737
}
3838

3939
@Override
40-
public void removeUserChangeListener() {}
40+
public void removeChangeListener() {}
4141
}

firebase-firestore/src/main/java/com/google/firebase/firestore/auth/FirebaseAuthCredentialsProvider.java

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@
2929
/**
3030
* FirebaseAuthCredentialsProvider uses Firebase Auth via {@link FirebaseApp} to get an auth token.
3131
*
32-
* <p>NOTE: To simplify the implementation, it requires that you call {@link #setUserChangeListener}
33-
* no more than once and don't call {@link #getToken} after calling {@link
34-
* #removeUserChangeListener}.
32+
* <p>NOTE: To simplify the implementation, it requires that you call {@link #setChangeListener} no
33+
* more than once and don't call {@link #getToken} after calling {@link #removeChangeListener}.
3534
*
3635
* <p>This class must be implemented to be thread-safe since getToken() and
37-
* set/removeUserChangeListener() are called from the Firestore worker thread, but the getToken()
38-
* Task callbacks and user change notifications will be executed on arbitrary different threads.
36+
* set/removeChangeListener() are called from the Firestore worker thread, but the getToken() Task
37+
* callbacks and user change notifications will be executed on arbitrary different threads.
3938
*/
4039
public final class FirebaseAuthCredentialsProvider extends CredentialsProvider {
4140

@@ -45,18 +44,18 @@ public final class FirebaseAuthCredentialsProvider extends CredentialsProvider {
4544

4645
/**
4746
* The listener registered with FirebaseApp; used to stop receiving auth changes once
48-
* userChangeListener is removed.
47+
* changeListener is removed.
4948
*/
5049
private final FirebaseApp.IdTokenListener idTokenListener;
5150

52-
/** The listener to be notified of user changes (sign-in / sign-out). */
53-
@Nullable private Listener<User> userChangeListener;
51+
/** The listener to be notified of credential changes (sign-in / sign-out, token changes). */
52+
@Nullable private Listener<User> changeListener;
5453

5554
/** The current user as reported to us via our IdTokenListener. */
5655
private User currentUser;
5756

58-
/** Counter used to detect if the user changed while a getToken request was outstanding. */
59-
private int userCounter;
57+
/** Counter used to detect if the token changed while a getToken request was outstanding. */
58+
private int tokenCounter;
6059

6160
private boolean forceRefresh;
6261

@@ -72,20 +71,17 @@ public FirebaseAuthCredentialsProvider(FirebaseApp app) {
7271
idTokenListener =
7372
token -> {
7473
synchronized (this) {
75-
User user = getUser(app);
76-
if (!currentUser.equals(user)) {
77-
currentUser = user;
78-
userCounter++;
74+
currentUser = getUser(app);
75+
tokenCounter++;
7976

80-
if (userChangeListener != null) {
81-
userChangeListener.onValue(currentUser);
82-
}
77+
if (changeListener != null) {
78+
changeListener.onValue(currentUser);
8379
}
8480
}
8581
};
8682

8783
currentUser = getUser(app);
88-
userCounter = 0;
84+
tokenCounter = 0;
8985

9086
FirebaseAppHelper.addIdTokenListener(app, idTokenListener);
9187
}
@@ -96,17 +92,17 @@ public synchronized Task<String> getToken() {
9692
forceRefresh = false;
9793
Task<GetTokenResult> res = FirebaseAppHelper.getToken(app, doForceRefresh);
9894

99-
// Take note of the current value of the userCounter so that this method can fail (with a
100-
// FirebaseFirestoreException) if there is a user change while the request is outstanding.
101-
final int savedCounter = userCounter;
95+
// Take note of the current value of the tokenCounter so that this method can fail (with a
96+
// FirebaseFirestoreException) if there is a token change while the request is outstanding.
97+
final int savedCounter = tokenCounter;
10298
return res.continueWith(
10399
task -> {
104100
synchronized (this) {
105-
// Cancel the request since the user changed while the request was outstanding so the
106-
// response is likely for a previous user (which user, we can't be sure).
107-
if (savedCounter != userCounter) {
101+
// Cancel the request since the token changed while the request was outstanding so the
102+
// response is potentially for a previous user (which user, we can't be sure).
103+
if (savedCounter != tokenCounter) {
108104
throw new FirebaseFirestoreException(
109-
"getToken aborted due to user change", Code.ABORTED);
105+
"getToken aborted due to token change", Code.ABORTED);
110106
}
111107
if (!task.isSuccessful()) {
112108
Exception exception = task.getException();
@@ -131,16 +127,16 @@ public synchronized void invalidateToken() {
131127
}
132128

133129
@Override
134-
public synchronized void setUserChangeListener(@NonNull Listener<User> listener) {
135-
userChangeListener = listener;
130+
public synchronized void setChangeListener(@NonNull Listener<User> changeListener) {
131+
this.changeListener = changeListener;
136132

137133
// Fire the initial event.
138-
listener.onValue(currentUser);
134+
changeListener.onValue(currentUser);
139135
}
140136

141137
@Override
142-
public synchronized void removeUserChangeListener() {
143-
userChangeListener = null;
138+
public synchronized void removeChangeListener() {
139+
changeListener = null;
144140
FirebaseAppHelper.removeIdTokenListener(app, idTokenListener);
145141
}
146142

firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import com.google.firebase.firestore.remote.RemoteSerializer;
5050
import com.google.firebase.firestore.remote.RemoteStore;
5151
import com.google.firebase.firestore.util.AsyncQueue;
52+
import com.google.firebase.firestore.util.Logger;
5253
import io.grpc.Status;
5354
import java.util.Collections;
5455
import java.util.List;
@@ -61,6 +62,8 @@
6162
*/
6263
public final class FirestoreClient implements RemoteStore.RemoteStoreCallback {
6364

65+
private static final String LOG_TAG = "FirestoreClient";
66+
6467
private final DatabaseInfo databaseInfo;
6568
private final CredentialsProvider credentialsProvider;
6669
private final AsyncQueue asyncQueue;
@@ -83,17 +86,21 @@ public FirestoreClient(
8386

8487
TaskCompletionSource<User> firstUser = new TaskCompletionSource<>();
8588
final AtomicBoolean initialized = new AtomicBoolean(false);
86-
credentialsProvider.setUserChangeListener(
89+
credentialsProvider.setChangeListener(
8790
(User user) -> {
8891
if (initialized.compareAndSet(false, true)) {
8992
hardAssert(!firstUser.getTask().isComplete(), "Already fulfilled first user task");
9093
firstUser.setResult(user);
9194
} else {
92-
asyncQueue.enqueueAndForget(() -> syncEngine.handleUserChange(user));
95+
asyncQueue.enqueueAndForget(
96+
() -> {
97+
Logger.debug(LOG_TAG, "Credential changed. Current user: %s", user.getUid());
98+
syncEngine.handleCredentialChange(user);
99+
});
93100
}
94101
});
95102

96-
// Defer initialization until we get the current user from the userChangeListener. This is
103+
// Defer initialization until we get the current user from the changeListener. This is
97104
// guaranteed to be synchronously dispatched onto our worker queue, so we will be initialized
98105
// before any subsequently queued work runs.
99106
asyncQueue.enqueueAndForget(
@@ -118,7 +125,7 @@ public Task<Void> enableNetwork() {
118125

119126
/** Shuts down this client, cancels all writes / listeners, and releases all resources. */
120127
public Task<Void> shutdown() {
121-
credentialsProvider.removeUserChangeListener();
128+
credentialsProvider.removeChangeListener();
122129
return asyncQueue.enqueue(
123130
() -> {
124131
remoteStore.shutdown();
@@ -197,6 +204,7 @@ private void initialize(Context context, User user, boolean usePersistence) {
197204
// Note: The initialization work must all be synchronous (we can't dispatch more work) since
198205
// external write/listen operations could get queued to run before that subsequent work
199206
// completes.
207+
Logger.debug(LOG_TAG, "Initializing. user=%s", user.getUid());
200208

201209
GarbageCollector garbageCollector;
202210
if (usePersistence) {

firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -539,14 +539,17 @@ public Map<DocumentKey, Integer> getCurrentLimboDocuments() {
539539
return new HashMap<>(limboTargetsByKey);
540540
}
541541

542-
public void handleUserChange(User user) {
542+
public void handleCredentialChange(User user) {
543+
boolean userChanged = !currentUser.equals(user);
543544
currentUser = user;
544545

545-
// Notify local store and emit any resulting events from swapping out the mutation queue.
546-
ImmutableSortedMap<DocumentKey, MaybeDocument> changes = localStore.handleUserChange(user);
547-
emitNewSnapshot(changes, /*remoteEvent=*/ null);
546+
if (userChanged) {
547+
// Notify local store and emit any resulting events from swapping out the mutation queue.
548+
ImmutableSortedMap<DocumentKey, MaybeDocument> changes = localStore.handleUserChange(user);
549+
emitNewSnapshot(changes, /*remoteEvent=*/ null);
550+
}
548551

549552
// Notify remote store so it can restart its streams.
550-
remoteStore.handleUserChange(user);
553+
remoteStore.handleCredentialChange();
551554
}
552555
}

firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import android.support.annotation.Nullable;
2020
import com.google.firebase.database.collection.ImmutableSortedSet;
21-
import com.google.firebase.firestore.auth.User;
2221
import com.google.firebase.firestore.core.OnlineState;
2322
import com.google.firebase.firestore.core.Transaction;
2423
import com.google.firebase.firestore.local.LocalStore;
@@ -237,13 +236,13 @@ public void shutdown() {
237236
* <p>In response the remote store tears down streams and clears up any tracked operations that
238237
* should not persist across users. Restarts the streams if appropriate.
239238
*/
240-
public void handleUserChange(User user) {
241-
Logger.debug(LOG_TAG, "Changing user to " + user);
239+
public void handleCredentialChange() {
242240
// If the network has been explicitly disabled, make sure we don't accidentally re-enable it.
243241
if (isNetworkEnabled()) {
244242
// Tear down and re-create our network streams. This will ensure we get a fresh auth token
245243
// for the new user and re-fill the write pipeline with new mutations from the LocalStore
246244
// (since mutations are per-user).
245+
Logger.debug(LOG_TAG, "Restarting streams for new credential.");
247246
disableNetworkInternal();
248247
onlineStateTracker.updateState(OnlineState.UNKNOWN);
249248
enableNetwork();

firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SpecTestCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ private void doChangeUser(@Nullable String uid) throws Exception {
731731
currentUser = new User(uid);
732732
queue.runSync(
733733
() -> {
734-
syncEngine.handleUserChange(currentUser);
734+
syncEngine.handleCredentialChange(currentUser);
735735
});
736736
}
737737

0 commit comments

Comments
 (0)