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 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
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
23 changes: 18 additions & 5 deletions presence-firestore/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ const firestore = admin.firestore();

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

const deviceKey = context.params.deviceId;

// 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 userDeviceCollectionRef = userFirestoreRef.collection(`devices`);
const userDeviceStatusFirestoreRef = userDeviceCollectionRef.doc(deviceKey);

// 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 userDeviceStatusFirestoreRef.delete();

if ((await userDeviceCollectionRef.get()).empty) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async/await syntax targets ES2016+, which is inconsistent with the rest of the file. Looking for whether ES5 is the target, or ES2016+

return userFirestoreRef.delete();
}
} else {
await userFirestoreRef.set({ uid: context.params.uid });
return userDeviceStatusFirestoreRef.set(status.devices[deviceKey]);
}
return null;
});
// [END presence_sync_function]
50 changes: 28 additions & 22 deletions presence-firestore/public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ 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 deviceId
var deviceStatusDatabaseRef = firebase.database().ref('/status/' + uid + '/devices').push();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sessions or connections is probably more accurate than devices, since the id isn't based on the device id.


// We'll create two constants which we will write to
// the Realtime database when this device is offline
Expand Down Expand Up @@ -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() {
deviceStatusDatabaseRef.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);
deviceStatusDatabaseRef.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 deviceStatusDatabaseRef = await firebase.database().ref('/status/' + uid + '/devices').push();
var deviceId = deviceStatusDatabaseRef.key;

var isOfflineForDatabase = {
state: 'offline',
Expand All @@ -80,34 +82,39 @@ 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 devicesCollectionRef = userFirestoreRef.collection('devices');
var deviceStatusFirestoreRef = devicesCollectionRef.doc(deviceId);


// 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);
firebase.database().ref('.info/connected').on('value', async function(snapshot) {
if (snapshot.val() === false) {
// Instead of simply returning, we'll also remove the device id from the user,
// and if no devices are left, we should delete the user as well.
// This ensures that our Firestore cache is aware that the device and/or user has been deleted
deviceStatusFirestoreRef.delete();
const deviceCollection = await devicesCollectionRef.get();
if(deviceCollection.empty) {
userFirestoreRef.delete();
}
return;
};

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

// We'll also add Firestore set here for when we come online.
userStatusFirestoreRef.set(isOnlineForFirestore);
userFirestoreRef.set({ uid });
deviceStatusFirestoreRef.set(isOnlineForFirestore);
});
});
// [END rtdb_and_local_fs_presence]
Expand All @@ -116,7 +123,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 +133,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