As I was playing with Pusher and WebSockets, I realized that there was one thing I can not do with Pusher : dealing with user status when a new user join a channel. ## The problem I was coding a small messaging app, with user connection to a private channel, and managing user statuses between online, away and offline. All was fine until I connect user Anatol, mark him as away, and connect user Paul. From Paul’s point of view, Anatol was… online Hu, Oh… ## The reason When joining a channel, Pusher return a list of users connected to this channel, but all it could tell us is “User Paul is connected, user Anatol is connected”. Argh, I want to know if Anatol is online or away when I connect as Paul. So how can we do that ? ## The solution The only way I’ve find, confirmed by the Pusher support team (thanks to us for validating this solution), is to store user status in database through REST.

In practice, let’s see how to do this:

// We will call a presence channel to add security to our app, so we have to give the auth page to pusher
var pusher = new Pusher('YOUR_PUSHER_APP_KEY', { authEndpoint: '/pusher_auth' });
// To define a presence channel simply add 'presence-' to the name of your channel
var channel = pusher.subscribe('presence-messages');
// User connect to the channel    
channel.bind('pusher:subscription_succeeded',  function(members){
	// For each member we get the current status, stored in database.
    // As I have only 2 users in my app it's fine, but a better way to do this would be to
    //	add members to an array and ask the database for the array of users instead of requesting user by user
    members.each(function(member){
        $.ajax({
            type: 'POST',
            url: 'get_status', // The url to ask the database
            data: 'user_id='+member.id, // data passed to the url, could be an array of datas
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            },
            success: function(status) {
                addUser(member.id, member.info.name, status); // Call the function addUser to display user
            }
        });
    });
});

// Simple function to display users with correct status
function addUser(user_id, user_name, status) {
	// Your logic to display user list, with status
}

// Manage visibility to change user status
// (probably the best solution to detect if the window is activated or not, find on Stackoverflow)
var hidden, visibilityChange;
if (typeof document.hidden !== "undefined") { /* Opera 12.10 and Firefox 18 and later support */
    hidden = "hidden";
    visibilityChange = "visibilitychange";
} else if (typeof document.mozHidden !== "undefined") {
    hidden = "mozHidden";
    visibilityChange = "mozvisibilitychange";
} else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden";
    visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
    hidden = "webkitHidden";
    visibilityChange = "webkitvisibilitychange";
}
// Add an event listener
document.addEventListener(visibilityChange, handleVisibilityChange, false);

// When user click on another tab in the browser, we consider him to be `away`
function handleVisibilityChange() {
    if (document[hidden]) {
        // Document is hidden, change user status to away and push it to the other users
        var triggered = channel.trigger('client-userstatus', { id: user_id, status: 'away' });
        // And update database
        updateStatus(user_id, 0);
    } else {
        // Document is shown, change user status to online and push it to the other users
        var triggered = channel.trigger('client-userstatus', { id: user_id, status: 'online' });
        // And update database
        updateStatus(user_id, 1);
    }
}

// Update user status (success and error methods are only for debugging)
function updateStatus(user_id, code_status) {
    $.ajax({
        type: 'POST',
        url: 'update_status',
        data: 'user_id='+user_id+'&code_status='+code_status,
        headers: {
            'X-Requested-With': 'XMLHttpRequest'
        }, success: function(){
            console.log('User status updated !');
        }, error : function(){
            console.log('User status update fails :(');
        }
    });
}

So, what have we done here ?

  • Anatol connects to the app,
  • Anatol goes away, trough Pusher we trigger this change to inform all the connected users,
  • We Post to database this change (0->1 for status),
  • Paul connects to the app,
  • We Get Anatol status (0 == ‘away’), and change is state to away in the user list,
  • When Anatol get back, is status is updated through Pusher.

Did you know another way to do this ? Feel free to share.