Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed onDisconnect logic to use deviceId #1010

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion presence-firestore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ To deploy the sample to your Firebase app,
1. Run `npm install` to install dependencies for the server-side [functions](functions/) as detailed above.
2. From this top-level sample directory, deploy the `Realtime Database` trigger defined in [functions](functions/) to `Firebase Functions` and the [public](public/) directory app to `Firebase Hosting`.

Assumimg you've created a Firebase application called `firebase-example-123` (make sure it's upgraded to the Spark plan and that `Anonymous Authentication` are enabled).
Assumimg you've created a Firebase application called `firebase-example-123` (make sure it's upgraded to the Blaze plan and that `Anonymous Authentication` are enabled).

```sh
firebase use --add firebase-example-123
Expand Down
25 changes: 19 additions & 6 deletions presence-firestore/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@ admin.initializeApp();
// detects authentication from the environment.
const firestore = admin.firestore();

// Create a new function which is triggered on changes to /status/{uid}
// Create a new function which is triggered on changes to /status/{uid}/sessions/{sessionId}
// Note: This is a Realtime Database trigger, *not* Firestore.
exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
exports.onUserStatusChanged = functions.database.ref('/status/{uid}/sessions/{sessionId}').onUpdate(
async (change, context) => {
// Get the data written to Realtime Database
const eventStatus = change.after.val();

const sessionId = context.params.sessionId;

// Then use other event data to create a reference to the
// corresponding Firestore document.
const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`);
const userFirestoreRef = firestore.doc(`status/${context.params.uid}`);
const userSessionCollectionRef = userFirestoreRef.collection('sessions');
const sessionStatusFirestoreRef = userSessionCollectionRef.doc(sessionId);

// It is likely that the Realtime Database change that triggered
// this event has already been overwritten by a fast change in
Expand All @@ -47,11 +51,20 @@ exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
if (status.last_changed > eventStatus.last_changed) {
return null;
}

// Otherwise, we convert the last_changed field to a Date
eventStatus.last_changed = new Date(eventStatus.last_changed);

// ... and write it to Firestore.
return userStatusFirestoreRef.set(eventStatus);
if(status.state === 'offline') {
// ... and write it to Firestore.
await sessionStatusFirestoreRef.delete();

if ((await userSessionCollectionRef.get()).empty) {
return userFirestoreRef.delete();
}
} else { // TODO(mtewani): Do we really need to handle this?
await userFirestoreRef.set({ uid: context.params.uid });
return sessionStatusFirestoreRef.set(status);
}
return null;
});
// [END presence_sync_function]
56 changes: 33 additions & 23 deletions presence-firestore/public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ function rtdb_presence() {

// Create a reference to this user's specific status node.
// This is where we will store data about being online/offline.
var userStatusDatabaseRef = firebase.database().ref('/status/' + uid);
// `push()` will add a new key to the user's path, acting as a sessionId
var sessionStatusDatabaseRef = firebase.database().ref('/status/' + uid + '/sessions').push();

// We'll create two constants which we will write to
// the Realtime database when this device is offline
// the Realtime database when this session is offline
// or online.
var isOfflineForDatabase = {
state: 'offline',
Expand All @@ -49,25 +50,26 @@ function rtdb_presence() {
// method to add a set which will only trigger once this
// client has disconnected by closing the app,
// losing internet, or any other means.
userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
sessionStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
// The promise returned from .onDisconnect().set() will
// resolve as soon as the server acknowledges the onDisconnect()
// request, NOT once we've actually disconnected:
// https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect

// We can now safely set ourselves as 'online' knowing that the
// server will mark us as offline once we lose connection.
userStatusDatabaseRef.set(isOnlineForDatabase);
sessionStatusDatabaseRef.set(isOnlineForDatabase);
});
});
// [END rtdb_presence]
}

function rtdb_and_local_fs_presence() {
async function rtdb_and_local_fs_presence() {
// [START rtdb_and_local_fs_presence]
// [START_EXCLUDE]
var uid = firebase.auth().currentUser.uid;
var userStatusDatabaseRef = firebase.database().ref('/status/' + uid);
var sessionStatusDatabaseRef = await firebase.database().ref('/status/' + uid + '/sessions').push();
var sessionId = sessionStatusDatabaseRef.key;

var isOfflineForDatabase = {
state: 'offline',
Expand All @@ -80,34 +82,43 @@ function rtdb_and_local_fs_presence() {
};

// [END_EXCLUDE]
var userStatusFirestoreRef = firebase.firestore().doc('/status/' + uid);
var userFirestoreRef = firebase.firestore().collection('status').doc(uid);
await userFirestoreRef.set({ uid }); // In case the user's id document doesn't already exist.

var sessionsCollectionRef = userFirestoreRef.collection('sessions');
var sessionStatusFirestoreRef = sessionsCollectionRef.doc(sessionId);


// Firestore uses a different server timestamp value, so we'll
// create two more constants for Firestore state.
var isOfflineForFirestore = {
state: 'offline',
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

var isOnlineForFirestore = {
state: 'online',
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

firebase.database().ref('.info/connected').on('value', function(snapshot) {
if (snapshot.val() == false) {
// Instead of simply returning, we'll also set Firestore's state
// to 'offline'. This ensures that our Firestore cache is aware
// of the switch to 'offline.'
userStatusFirestoreRef.set(isOfflineForFirestore);
// When a user goes offline, it reads from the cache to know how many sessions are left.
// This listener is to ensure that the full session cache is populated and can be read from when offline.
sessionsCollectionRef.onSnapshot(() => {});

firebase.database().ref('.info/connected').on('value', async function(snapshot) {
if (snapshot.val() === false) {
// Instead of simply returning, we'll also remove the session id from the user,
// and if no sessions are left, we should delete the user as well.
// This ensures that our Firestore cache is aware that the session and/or user has been deleted
sessionStatusFirestoreRef.delete();
const sessionCollection = await sessionsCollectionRef.get();
if(sessionCollection.empty) {
userFirestoreRef.delete();
}
return;
};

userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
userStatusDatabaseRef.set(isOnlineForDatabase);
sessionStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
sessionStatusDatabaseRef.set(isOnlineForDatabase);

// We'll also add Firestore set here for when we come online.
userStatusFirestoreRef.set(isOnlineForFirestore);
userFirestoreRef.set({ uid });
sessionStatusFirestoreRef.set(isOnlineForFirestore);
});
});
// [END rtdb_and_local_fs_presence]
Expand All @@ -116,7 +127,7 @@ function rtdb_and_local_fs_presence() {
function fs_listen() {
// [START fs_onsnapshot]
userStatusFirestoreRef.onSnapshot(function(doc) {
var isOnline = doc.data().state == 'online';
var isOnline = !doc.empty;
// ... use isOnline
});
// [END fs_onsnapshot]
Expand All @@ -126,7 +137,6 @@ function fs_listen_online() {
var history = document.querySelector('#history');
// [START fs_onsnapshot_online]
firebase.firestore().collection('status')
.where('state', '==', 'online')
.onSnapshot(function(snapshot) {
snapshot.docChanges().forEach(function(change) {
if (change.type === 'added') {
Expand Down