From c27c172c47816ab07b97b2924be717be10d30662 Mon Sep 17 00:00:00 2001 From: Abe Haskins Date: Sat, 30 Sep 2017 16:35:36 -0700 Subject: [PATCH] Adds presence-firestore --- .../functions/package.json | 5 +- presence-firestore/README.md | 23 +++ presence-firestore/firebase.json | 3 + presence-firestore/functions/index.js | 40 +++++ presence-firestore/functions/package.json | 10 ++ presence-firestore/public/index.html | 40 +++++ presence-firestore/public/index.js | 141 ++++++++++++++++++ 7 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 presence-firestore/README.md create mode 100644 presence-firestore/firebase.json create mode 100644 presence-firestore/functions/index.js create mode 100644 presence-firestore/functions/package.json create mode 100644 presence-firestore/public/index.html create mode 100644 presence-firestore/public/index.js diff --git a/fulltext-search-firestore/functions/package.json b/fulltext-search-firestore/functions/package.json index c6bf45775e..ea0a0612aa 100644 --- a/fulltext-search-firestore/functions/package.json +++ b/fulltext-search-firestore/functions/package.json @@ -9,8 +9,5 @@ "firebase-admin": "^5.0.0", "firebase-functions": "^0.7.0" }, - "private": true, - "devDependencies": { - "prettier": "^1.6.1" - } + "private": true } diff --git a/presence-firestore/README.md b/presence-firestore/README.md new file mode 100644 index 0000000000..dcd5d42cfb --- /dev/null +++ b/presence-firestore/README.md @@ -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. \ No newline at end of file diff --git a/presence-firestore/firebase.json b/presence-firestore/firebase.json new file mode 100644 index 0000000000..8759d50d8d --- /dev/null +++ b/presence-firestore/firebase.json @@ -0,0 +1,3 @@ +{ + "hosting": {"public": "public"} +} diff --git a/presence-firestore/functions/index.js b/presence-firestore/functions/index.js new file mode 100644 index 0000000000..2c6e7c270b --- /dev/null +++ b/presence-firestore/functions/index.js @@ -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] \ No newline at end of file diff --git a/presence-firestore/functions/package.json b/presence-firestore/functions/package.json new file mode 100644 index 0000000000..89381aae8c --- /dev/null +++ b/presence-firestore/functions/package.json @@ -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 +} diff --git a/presence-firestore/public/index.html b/presence-firestore/public/index.html new file mode 100644 index 0000000000..895fe1f854 --- /dev/null +++ b/presence-firestore/public/index.html @@ -0,0 +1,40 @@ + + + + Presence in Cloud Firestore + + +

History of user presence (since this page was opened)

+
+ +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/presence-firestore/public/index.js b/presence-firestore/public/index.js new file mode 100644 index 0000000000..34f35d211f --- /dev/null +++ b/presence-firestore/public/index.js @@ -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 + "
"; + // [END_EXCLUDE] + } + if (change.type === "removed") { + const msg = `User ${change.doc.id} is offline.`; + console.log(msg); + // [START_EXCLUDE] + history.innerHTML += msg + "
" + // [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!"); +}); \ No newline at end of file