Skip to content

Commit c27c172

Browse files
committed
Adds presence-firestore
1 parent 41c9686 commit c27c172

File tree

7 files changed

+258
-4
lines changed

7 files changed

+258
-4
lines changed

fulltext-search-firestore/functions/package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,5 @@
99
"firebase-admin": "^5.0.0",
1010
"firebase-functions": "^0.7.0"
1111
},
12-
"private": true,
13-
"devDependencies": {
14-
"prettier": "^1.6.1"
15-
}
12+
"private": true
1613
}

presence-firestore/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Presence in Firestore
2+
3+
This template shows you how to build a presence (understanding which users are online / offline) in Cloud Firestore and Realtime Database.
4+
5+
## Functions Code
6+
7+
See file [functions/index.js](functions/index.js) for the code.
8+
9+
The dependencies are listed in [functions/package.json](functions/package.json).
10+
11+
## Sample Database Structure
12+
13+
As an example we'll be using a secure note structure:
14+
15+
```
16+
/status
17+
/UID_A
18+
state: "online"
19+
/UID_B
20+
state: "offline"
21+
```
22+
23+
Whenever a new note is created or modified a Function sends the content to be indexed to the Algolia instance.

presence-firestore/firebase.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"hosting": {"public": "public"}
3+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// [START presence_sync_function]
2+
const functions = require('firebase-functions');
3+
const Firestore = require('@google-cloud/firestore');
4+
5+
// Since this code will be running in the Cloud Functions enviornment
6+
// we call initialize Firestore without any arguments because it
7+
// detects authentication from the environment.
8+
const firestore = new Firestore();
9+
10+
// Create a new function which is triggered on changes to /status/{uid}
11+
// Note: This is a Realtime Database trigger, *not* Cloud Firestore.
12+
exports.onUserStatusChanged = functions.database
13+
.ref("/status/{uid}").onUpdate((event) => {
14+
// Get the data written to Realtime Database
15+
const eventStatus = event.data.val();
16+
17+
// Then use other event data to create a reference to the
18+
// corresponding Firestore document.
19+
const userStatusFirestoreRef = firestore.doc(`status/${event.params.uid}`);
20+
21+
// It is likely that the Realtime Database change that triggered
22+
// this event has already been overwritten by a fast change in
23+
// online / offline status, so we'll re-read the current data
24+
// and compare the timestamps.
25+
return event.data.ref.once("value").then((statusSnapshot) => {
26+
return statusSnapshot.val();
27+
}).then((status) => {
28+
console.log(status, eventStatus);
29+
// If the current timestamp for this data is newer than
30+
// the data that triggered this event, we exit this function.
31+
if (status.last_changed > eventStatus.last_changed) return;
32+
33+
// Otherwise, we convert the last_changed field to a Date
34+
eventStatus.last_changed = new Date(eventStatus.last_changed);
35+
36+
// ... and write it to Firestore.
37+
return userStatusFirestoreRef.set(eventStatus);
38+
});
39+
});
40+
// [END presence_sync_function]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "presence-firestore",
3+
"description": "Presence for Firestore",
4+
"dependencies": {
5+
"@google-cloud/firestore": "^0.7.0",
6+
"firebase-admin": "^5.0.0",
7+
"firebase-functions": "^0.7.0"
8+
},
9+
"private": true
10+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!-- /**
2+
* Copyright 2017 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/ -->
16+
<html>
17+
<head>
18+
<title>Presence in Cloud Firestore</title>
19+
</head>
20+
<body>
21+
<h3>History of user presence (since this page was opened)</h3>
22+
<div id="history">
23+
24+
</div>
25+
<!-- Import and configure the Firebase SDK -->
26+
<!-- These scripts are made available when the app is served or deployed on Firebase Hosting -->
27+
<!-- If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup -->
28+
<!-- <script src="/__/firebase/4.5.0/firebase-app.js"></script>
29+
<script src="/__/firebase/4.5.0/firebase-auth.js"></script>
30+
<script src="/__/firebase/0.5,0/firebase-firestore.js"></script> -->
31+
32+
<script src="https://www.gstatic.com/firebasejs/4.1.2/firebase.js"></script>
33+
<script src="https://storage.cloud.google.com/firebase-preview-drop/web/firestore/0.5.0/firestore.js"></script>
34+
35+
36+
<script src="/__/firebase/init.js"></script>
37+
38+
<script src="./index.js"></script>
39+
</body>
40+
</html>

presence-firestore/public/index.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
function rtdb_presence() {
2+
// [START rtdb_presence]
3+
// Fetch the current user's ID from Firebase Authentication.
4+
const uid = firebase.auth().currentUser.uid;
5+
6+
// Create a reference to this user's specific status node.
7+
// This is where we will store data about being online/offline.
8+
const userStatusDatabaseRef = firebase.database().ref(`/status/${uid}`);
9+
10+
// We'll create two constants which we will write to
11+
// the Realtime database when this device is offline
12+
// or online.
13+
const isOfflineForDatabase = {
14+
state: "offline",
15+
last_changed: firebase.database.ServerValue.TIMESTAMP,
16+
};
17+
18+
const isOnlineForDatabase = {
19+
state: "online",
20+
last_changed: firebase.database.ServerValue.TIMESTAMP,
21+
};
22+
23+
// Create a reference to the special ".info/connected" path in
24+
// Realtime Database. This path returns `true` when connected
25+
// and `false` when disconnected.
26+
firebase.database().ref(".info/connected").on("value", function (snapshot) {
27+
// If we're not currently connected, don't do anything.
28+
if (snapshot.val() == false) {
29+
return;
30+
};
31+
32+
// If we are currently connected, then use the 'onDisconnect()'
33+
// method to add a set which will only trigger once this
34+
// client has disconnected by closing the app,
35+
// losing internet, or any other means.
36+
userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function () {
37+
// The promise returned from .onDisconnect().set() will
38+
// resolve as soon as the server acknowledges the onDisconnect()
39+
// request, NOT once we've actually disconnected:
40+
// https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect
41+
42+
// We can now safely set ourselves as "online" knowing that the
43+
// server will mark us as offline once we lose connection.
44+
userStatusDatabaseRef.set(isOnlineForDatabase);
45+
});
46+
});
47+
// [END rtdb_presence]
48+
}
49+
50+
function rtdb_and_local_fs_presence() {
51+
// [START rtdb_and_local_fs_presence]
52+
// [START_EXCLUDE]
53+
const uid = firebase.auth().currentUser.uid;
54+
const userStatusDatabaseRef = firebase.database().ref(`/status/${uid}`);
55+
56+
const isOfflineForDatabase = {
57+
state: "offline",
58+
last_changed: firebase.database.ServerValue.TIMESTAMP,
59+
};
60+
61+
const isOnlineForDatabase = {
62+
state: "online",
63+
last_changed: firebase.database.ServerValue.TIMESTAMP,
64+
};
65+
66+
// [END_EXCLUDE]
67+
const userStatusFirestoreRef = firebase.firestore().doc(`/status/${uid}`);
68+
69+
// Firestore uses a different server timestamp value, so we'll
70+
// create two more constants for Firestore state.
71+
const isOfflineForFirestore = {
72+
state: "offline",
73+
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
74+
};
75+
76+
const isOnlineForFirestore = {
77+
state: "online",
78+
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
79+
};
80+
81+
firebase.database().ref(".info/connected").on("value", function (snapshot) {
82+
if (snapshot.val() == false) {
83+
// Instead of simply returning, we'll also set Firestore's state
84+
// to "offline". This ensures that our Firestore cache is aware
85+
// of the switch to "offline."
86+
userStatusFirestoreRef.set(isOfflineForFirestore);
87+
return;
88+
};
89+
90+
userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function () {
91+
userStatusDatabaseRef.set(isOnlineForDatabase);
92+
93+
// We'll also add Firestore set here for when we come online.
94+
userStatusFirestoreRef.set(isOnlineForFirestore);
95+
});
96+
});
97+
// [END rtdb_and_local_fs_presence]
98+
}
99+
100+
function fs_listen() {
101+
// [START fs_onsnapshot]
102+
userStatusFirestoreRef.onSnapshot(function (doc) {
103+
const isOnline = doc.data().state == "online";
104+
// ... use isOnline
105+
});
106+
// [END fs_onsnapshot]
107+
}
108+
109+
function fs_listen_online() {
110+
const history = document.querySelector("#history");
111+
// [START fs_onsnapshot_online]
112+
firebase.firestore().collection("status")
113+
.where("state", "==", "online")
114+
.onSnapshot(function (snapshot) {
115+
snapshot.docChanges.forEach(function(change) {
116+
if (change.type === "added") {
117+
const msg = `User ${change.doc.id} is online.`;
118+
console.log(msg);
119+
// [START_EXCLUDE]
120+
history.innerHTML += msg + "<br />";
121+
// [END_EXCLUDE]
122+
}
123+
if (change.type === "removed") {
124+
const msg = `User ${change.doc.id} is offline.`;
125+
console.log(msg);
126+
// [START_EXCLUDE]
127+
history.innerHTML += msg + "<br />"
128+
// [END_EXCLUDE]
129+
}
130+
});
131+
});
132+
// [END fs_onsnapshot_online]
133+
}
134+
135+
firebase.auth().signInAnonymously().then(function () {
136+
rtdb_and_local_fs_presence();
137+
fs_listen_online();
138+
}).catch(function (err) {
139+
console.warn(err);
140+
console.warn("Please enable Anonymous Authentication in your Firebase project!");
141+
});

0 commit comments

Comments
 (0)