I am using this code to detect user presence in my Android app in the background using a service:
final FirebaseAuth mAuth = FirebaseAuth.getInstance();
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myConnectionsRef = database.getReference("connected-users").child(mAuth.getCurrentUser().getUid());
final DatabaseReference lastOnlineRef = database.getReference("/registered-users/").child(mAuth.getCurrentUser().getUid()).child("/lastOnline");
final DatabaseReference connectedRef = database.getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
DatabaseReference con = myConnectionsRef.push();
lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
con.onDisconnect().removeValue();
con.setValue(Boolean.TRUE);
Log.i("CONNECTED", "true");
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
The problem seems to be every hour, from what I have found, the authentication token is retrieved again, and during that, it creates a new entry into the node showing the user is connected. That entry does not get deleted, thus creating a ghost user.
I've tried multiple solutions such as adding my SHA1 key and using the new google-services.json file to my project with little success. It seems like the problem is the authentication though, as when I remove the authentication rule from my database, this no longer happens.
Here's some logs that are showing the problem:
I/art: Enabling alloc tracker (65536 entries of 16 frames, taking 8MB)
W/PersistentConnection: pc_0 - Auth token revoked: expired_token (Auth token is expired.)
D/EventRaiser: Raising 1 event(s)
D/EventRaiser: Raising /.info/connected: VALUE: false
D/Persistence: Starting transaction.
D/Persistence: Persisted a total of 1 rows and deleted 0 rows for a set at /registered-users/....../lastOnline in 2ms
D/Persistence: Transaction completed. Elapsed: 38ms
D/RepoOperation: Aborting transactions for path: /registered-users/....../lastOnline. Affected: /registered-users/....../lastOnline
D/Persistence: Starting transaction.
D/Persistence: Persisted a total of 1 rows and deleted 1 rows for a set at /connected-users/....../-KZLNBlImkX978ftO6z1 in 2ms
D/Persistence: Transaction completed. Elapsed: 6ms
D/RepoOperation: Aborting transactions for path: /connected-users/....../-KZLNBlImkX978ftO6z1. Affected: /connected-users/....../-KZLNBlImkX978ftO6z1
W/DynamiteModule: Local module descriptor class for com.google.firebase.auth not found.
W/DynamiteModule: Local module descriptor class for com.google.firebase.auth not found.
D/FirebaseAuth: Notifying listeners about user ( ...... ).
D/FirebaseApp: Notifying auth state listeners.
D/FirebaseApp: Notified 1 auth state listeners.
D/RepoOperation: Auth token changed, triggering auth token refresh
D/EventRaiser: Raising 1 event(s)
D/EventRaiser: Raising /.info/connected: VALUE: true
I/CONNECTED: true
D/RepoOperation: set: /connected-users/....../-KZLTZmyGMvWPdJKmdeV
D/DataOperation: set: /connected-users/....../-KZLTZmyGMvWPdJKmdeV true
D/Persistence: Starting transaction.
D/Persistence: Persisted user overwrite in 1ms
D/Persistence: Transaction completed. Elapsed: 8ms
D/RepoOperation: Aborting transactions for path: /connected-users/....../-KZLTZmyGMvWPdJKmdeV. Affected: /connected-users/....../-KZLTZmyGMvWPdJKmdeV
D/Persistence: Starting transaction.
D/Persistence: Deleted 1 write(s) with writeId 2 in 1ms
D/Persistence: Persisted a total of 1 rows and deleted 0 rows for a set at /connected-users/....../-KZLTZmyGMvWPdJKmdeV in 7ms
D/Persistence: Transaction completed. Elapsed: 34ms
W/DynamiteModule: Local module descriptor class for com.google.firebase.auth not found.
My initial authentication is through a custom token.
Your understanding is exactly correct. We're working on making changes to the SDK to alleviate this problem, but currently there is an issue where when your auth token expires, we end up reconnecting to the Database with a new token but because the old auth token had already expired, your onDisconnect() operations from the reconnect are executed unauthenticated, and may therefore fail.
A few potential workarounds include:
You could manually force a token refresh prior to the 60 minute expiration. You could do this by calling FirebaseAuth.getInstance().getCurrentUser().getToken(true)
periodically. We'll automatically use the new token with the Database, so the old one won't expire.
Open up your security rules so that the onDisconnect().removeValue() can complete without authentication. This is obviously not ideal. The fact that the push-id is not easily guessable by other clients might mean there's a way to do this safely, but you'd have to prevent other clients from reading /registered-users/$uid/ (since then they'd know the push-id), which probably defeats the purpose of your presence information. :-/
You could have the client remember its "old" push-id and delete it after a reconnect (though there's still a potential race condition if the client really goes away before it reconnects.
You could write your client to automatically detect / delete stale presence entries (e.g. based on timestamp).
Hope this gives you some help. Sorry for the bug. We are looking into a fix in the clients, but I'm not sure when it'll land.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With