Skip to content

Commit

Permalink
Adds presence-firestore
Browse files Browse the repository at this point in the history
  • Loading branch information
abeisgoat committed Sep 30, 2017
1 parent 41c9686 commit c27c172
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 4 deletions.
5 changes: 1 addition & 4 deletions fulltext-search-firestore/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@
"firebase-admin": "^5.0.0",
"firebase-functions": "^0.7.0"
},
"private": true,
"devDependencies": {
"prettier": "^1.6.1"
}
"private": true
}
23 changes: 23 additions & 0 deletions presence-firestore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Presence in Firestore

This template shows you how to build a presence (understanding which users are online / offline) in Cloud Firestore and Realtime Database.

## Functions Code

See file [functions/index.js](functions/index.js) for the code.

The dependencies are listed in [functions/package.json](functions/package.json).

## Sample Database Structure

As an example we'll be using a secure note structure:

```
/status
/UID_A
state: "online"
/UID_B
state: "offline"
```

Whenever a new note is created or modified a Function sends the content to be indexed to the Algolia instance.
3 changes: 3 additions & 0 deletions presence-firestore/firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"hosting": {"public": "public"}
}
40 changes: 40 additions & 0 deletions presence-firestore/functions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// [START presence_sync_function]
const functions = require('firebase-functions');
const Firestore = require('@google-cloud/firestore');

// Since this code will be running in the Cloud Functions enviornment
// we call initialize Firestore without any arguments because it
// detects authentication from the environment.
const firestore = new Firestore();

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

// Then use other event data to create a reference to the
// corresponding Firestore document.
const userStatusFirestoreRef = firestore.doc(`status/${event.params.uid}`);

// It is likely that the Realtime Database change that triggered
// this event has already been overwritten by a fast change in
// online / offline status, so we'll re-read the current data
// and compare the timestamps.
return event.data.ref.once("value").then((statusSnapshot) => {
return statusSnapshot.val();
}).then((status) => {
console.log(status, eventStatus);
// If the current timestamp for this data is newer than
// the data that triggered this event, we exit this function.
if (status.last_changed > eventStatus.last_changed) return;

// 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);
});
});
// [END presence_sync_function]
10 changes: 10 additions & 0 deletions presence-firestore/functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "presence-firestore",
"description": "Presence for Firestore",
"dependencies": {
"@google-cloud/firestore": "^0.7.0",
"firebase-admin": "^5.0.0",
"firebase-functions": "^0.7.0"
},
"private": true
}
40 changes: 40 additions & 0 deletions presence-firestore/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!-- /**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ -->
<html>
<head>
<title>Presence in Cloud Firestore</title>
</head>
<body>
<h3>History of user presence (since this page was opened)</h3>
<div id="history">

</div>
<!-- Import and configure the Firebase SDK -->
<!-- These scripts are made available when the app is served or deployed on Firebase Hosting -->
<!-- If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup -->
<!-- <script src="/__/firebase/4.5.0/firebase-app.js"></script>
<script src="/__/firebase/4.5.0/firebase-auth.js"></script>
<script src="/__/firebase/0.5,0/firebase-firestore.js"></script> -->

<script src="https://www.gstatic.com/firebasejs/4.1.2/firebase.js"></script>
<script src="https://storage.cloud.google.com/firebase-preview-drop/web/firestore/0.5.0/firestore.js"></script>


<script src="/__/firebase/init.js"></script>

<script src="./index.js"></script>
</body>
</html>
141 changes: 141 additions & 0 deletions presence-firestore/public/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
function rtdb_presence() {
// [START rtdb_presence]
// Fetch the current user's ID from Firebase Authentication.
const uid = firebase.auth().currentUser.uid;

// Create a reference to this user's specific status node.
// This is where we will store data about being online/offline.
const userStatusDatabaseRef = firebase.database().ref(`/status/${uid}`);

// We'll create two constants which we will write to
// the Realtime database when this device is offline
// or online.
const isOfflineForDatabase = {
state: "offline",
last_changed: firebase.database.ServerValue.TIMESTAMP,
};

const isOnlineForDatabase = {
state: "online",
last_changed: firebase.database.ServerValue.TIMESTAMP,
};

// Create a reference to the special ".info/connected" path in
// Realtime Database. This path returns `true` when connected
// and `false` when disconnected.
firebase.database().ref(".info/connected").on("value", function (snapshot) {
// If we're not currently connected, don't do anything.
if (snapshot.val() == false) {
return;
};

// If we are currently connected, then use the 'onDisconnect()'
// 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 () {
// 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);
});
});
// [END rtdb_presence]
}

function rtdb_and_local_fs_presence() {
// [START rtdb_and_local_fs_presence]
// [START_EXCLUDE]
const uid = firebase.auth().currentUser.uid;
const userStatusDatabaseRef = firebase.database().ref(`/status/${uid}`);

const isOfflineForDatabase = {
state: "offline",
last_changed: firebase.database.ServerValue.TIMESTAMP,
};

const isOnlineForDatabase = {
state: "online",
last_changed: firebase.database.ServerValue.TIMESTAMP,
};

// [END_EXCLUDE]
const userStatusFirestoreRef = firebase.firestore().doc(`/status/${uid}`);

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

const 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);
return;
};

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

// We'll also add Firestore set here for when we come online.
userStatusFirestoreRef.set(isOnlineForFirestore);
});
});
// [END rtdb_and_local_fs_presence]
}

function fs_listen() {
// [START fs_onsnapshot]
userStatusFirestoreRef.onSnapshot(function (doc) {
const isOnline = doc.data().state == "online";
// ... use isOnline
});
// [END fs_onsnapshot]
}

function fs_listen_online() {
const 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") {
const msg = `User ${change.doc.id} is online.`;
console.log(msg);
// [START_EXCLUDE]
history.innerHTML += msg + "<br />";
// [END_EXCLUDE]
}
if (change.type === "removed") {
const msg = `User ${change.doc.id} is offline.`;
console.log(msg);
// [START_EXCLUDE]
history.innerHTML += msg + "<br />"
// [END_EXCLUDE]
}
});
});
// [END fs_onsnapshot_online]
}

firebase.auth().signInAnonymously().then(function () {
rtdb_and_local_fs_presence();
fs_listen_online();
}).catch(function (err) {
console.warn(err);
console.warn("Please enable Anonymous Authentication in your Firebase project!");
});

0 comments on commit c27c172

Please sign in to comment.