forked from firebase/functions-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
258 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"hosting": {"public": "public"} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!"); | ||
}); |