This post will teach you how to build a fully working presence system using Firebase.
Imagine you have some exciting news you really want to share with a friend. You jump on your favorite social network, open the friends list, and quickly scan for your friend's name. There's a red dot next to it indicating that she's busy - no matter - this is important! You double click to open a chat window...
A lot of us do this little routine every day. Knowing if somebody is currently online or offline is called "presence" and is a pretty basic feature in most collaborative applications (such as chat). What most of us don't realize, however, is that while the feature may appear simple, building the infrastructure to support it from scratch can be very difficult.
Have no fear! Firebase makes adding presence to your application very easy.
A presence system is simply a way of sharing your status with other users. At a basic level, this status could be something simple like whether a given user is currently online or offline. Firebase is great at doing exactly that - sharing state.
The naïve approach - Online / Offline State
The first step when building a presence system is for each client to be able to detect when it is online and offline. Firebase provides a convenient way to do this via the special .info/connected path. Great!
var amOnline = new Firebase('https://<demo>.firebaseio.com/.info/connected'); var userRef = new Firebase('https://<demo>.firebaseio.com/presence/' + userid); amOnline.on('value', function(snapshot) { if (snapshot.val()) { // User is online. userRef.set(true); } else { // User is offline. // WARNING: This won't work! See an explanation below. userRef.set(false); } });
Simple enough, right? The 'value' callback is automatically triggered by Firebase whenever the connection state changes (e.g. a user loses their internet connection) and we can set a boolean value for each user as appropriate.
This won't work in practice though - can you figure out why?
When the user goes offline, our callback will be triggered, but by then it's too late to notify other clients because we aren't connected to the network anymore.
Online / Offline State Done The Right Way
So what do we do? We can solve the offline problem by utilizing a special feature in Firebase known as onDisconnect - a function that tells the Firebase server to do something when it notices a client isn't connected anymore.
This is exactly what we want - we need to instruct the Firebase server to set the user's presence boolean to false when it detects that the client went offline:
false
var amOnline = new Firebase('https://<demo>.firebaseio.com/.info/connected'); var userRef = new Firebase('https://<demo>.firebaseio.com/presence/' + userid); amOnline.on('value', function(snapshot) { if (snapshot.val()) { userRef.onDisconnect().remove(); userRef.set(true); } });
Notice how much smaller this code is. The nice thing about onDisconnect() is that Firebase will automatically handle all of the nasty corner cases for you, like unclean disconnects.
onDisconnect()
A few points to note here:
We don't need to handle an else case. We simply use onDisconnect() to setup a trigger every time the user comes online.
else
The onDisconnect() call is before the call to set() itself. This is to avoid a race condition where you set the user's presence to true and the client disconnects before the onDisconnect() operation takes effect, leaving a ghost user.
set()
The call to onDisconnect().remove() is made every time the user comes online. This is because onDisconnect() operations are performed only once.
onDisconnect().remove()
We store the presence bit for a user independently on a top-level key instead of with the user record itself. This lets us quickly obtain a list of users who are currently online or offline without having to enumerate all user records. This is an example of denormalization, an important principle we've discussed earlier.
There's no special code in the callback for when the user goes offline. This isn't necessary as onDisconnect() will ensure that the presence bit will be updated when the client disconnects.
Hello, are you there? (Idle Status)
You're telling your friend the good news and are having a great conversation. 10 minutes later, you notice your friend isn't responding anymore. Perhaps she had to leave her desk... wouldn't it be great if you were notified somehow that the user had become "idle"?
It's easy to add features like this if we store text instead of a boolean. To detect the idle state of a user, we'll make use of a library called idle.js:
<script src='https://www.firebase.com/js/libs/idle.js'></script> <script> var amOnline = new Firebase('https://<demo>.firebaseio.com/.info/connected'); var userRef = new Firebase('https://<demo>.firebaseio.com/presence/' + userid); amOnline.on('value', function(snapshot) { if (snapshot.val()) { userRef.onDisconnect().set('☆ offline'); userRef.set('★ online'); } }); document.onIdle = function () { userRef.set('☆ idle'); } document.onAway = function () { userRef.set('☄ away'); } document.onBack = function (isIdle, isAway) { userRef.set('★ online'); } </script>
Last seen at...
Let's say you want to share the news with another friend, but notice he's currently offline. Well, there's a big difference between someone being offline for the last 5 minutes and 5 hours. I almost always want to know when a friend was last seen online.
You can make use of Firebase's server-side timestamp feature to implement this. We can store the boolean value true when a particular user is online but set it to a timestamp when the user disconnects via use of onDisconnect and Firebase.ServerValue.Timestamp:
true
onDisconnect
Firebase.ServerValue.Timestamp
var amOnline = new Firebase('https://<demo>.firebaseio.com/.info/connected'); var userRef = new Firebase('https://<demo>.firebaseio.com/presence/' + userid); amOnline.on('value', function(snapshot) { if (snapshot.val()) { userRef.onDisconnect().set(Firebase.ServerValue.TIMESTAMP); userRef.set(true); } });
In your UI, you can now obtain the last time a particular user was online:
var userRef = new Firebase('https://<demo>.firebaseio.com/presence/' + userid); userRef.on('value', function(snapshot) { if (snapshot.val() === true) { // User is online, update UI. } else { // User logged off at snapshot.val() - seconds since epoch. } });
Session History
I can always tell that a friend is having a busy day if he comes online for 10 minutes at a time and keeps going offline. Wouldn't it be cool if we could keep a log of every user's session and know how long each session lasted?
Firebase makes that easy too. We can simply create a new entry in the user's presence 'log' every time they come online by using the convenient push() function and storing the current timestamp. Then, by using an onDisconnect operation, we can record the end time as well:
push()
var amOnline = new Firebase('https://<demo>.firebaseio.com/.info/connected'); var userRef = new Firebase('https://<demo>.firebaseio.com/presence' + userid); amOnline.on('value', function(snapshot) { if (snapshot.val()) { var sessionRef = userRef.push(); sessionRef.child('ended').onDisconnect().set(Firebase.ServerValue.TIMESTAMP); sessionRef.child('began').set(Firebase.ServerValue.TIMESTAMP); } });
Now, by enumerating all the children of https://<my-firebase>.firebaseio.com/presence/<userid> you can display a record of all the user's past sessions. The time at which each session began is stored at https://<my-firebase>.firebaseio.com/presence/<userid>/<sessionid>/began.
https://<my-firebase>.firebaseio.com/presence/<userid>
https://<my-firebase>.firebaseio.com/presence/<userid>/<sessionid>/began
That's It!
You now have a fully working presence system. Don't forget to check out the presence example of the code listed in this blog post. We've also updated our documentation on managing presence to include information on the server timestamps feature. Let us know via email, our Google Group, or comment on this blog if you're building something similar or have found other ways to build a presence system with Firebase.
It’s been two months since we launched Firepad, our open-source collaborative code and rich-text editor built on Firebase. Our mission has always been to empower devs with powerful collaborative editing features at their fingertips. Today we’re excited to announce two new features that make Firepad even more powerful.
List Support
Ordered and unordered lists have been the #1 feature request since we launched Firepad. They have been on our roadmap since the beginning, but there were a number of technical challenges to be solved before we could add them. I'm happy to report these challenges are behind us, and we now have rich support for ordered and unordered lists with arbitrary nesting. Take a look:
HTML Import
Until now, there was no easy way to get existing HTML content into Firepad. This often made it difficult to integrate Firepad into your application if you had existing content stored as HTML. With the advent of list support, we’ve also added HTML import functionality. Just use the firepad.setHtml() method on the API!
What’s Next?
We’re continuing to invest in improving Firepad’s rich-text features as well as evolving the docs and API to make sure that Firepad can be easily integrated into new and existing apps by developers of any skill level. We’d love to hear what you want to see next! Email us or open an issue on GitHub.
If you're in town for WWDC, we'd love to have you over at the Firebase offices for our very first ever WWDC Office Hours. Earlier this year we joined the Apple ecosystem by taking the covers off of the Firebase Objective-C client and it's been a fun ride! (If you haven't already, take a look at our Xcode templates that makes it easy to get started with Firebase).
Come by and meet the Firebase team and other developers building realtime apps.
Firebase WWDC Office Hours Meetup
188 King St. PH8San Francisco CA,94107United Statesdirections
I'm happy to be representing Firebase all week at WWDC. If you're around and want to chat about apps and APIs, you can find me on Twitter @vikrum5000 or drop me an email at vikrum@firebase.com.