Inviting a friend to join you in some real time application is an easy concept, but hard to do from the perspective of that real time app. You need near instant response from a friend, and what better way than to find a friend who's online and available through protocols used by chat applications?
For a web application that only requires one request to find all online users, and then another request to send an invitation, a full fledged chat client is overkill. Why build out a fully working client that scales with millions of connections, just to get a buddy list of all your online contacts? Unless you're building Meebo you really don't need this.
I was trying to solve that very problem, invite online friends! Google Contacts Data API did give me all the people in a Google contact list, but provided me nothing other than a last update sort. Last updated contacts wasn't going to cut it. So I turned to writing a XMPP type request.
Since I'm using the Ruby on Rails framework for this application, I decided to use the XMPP4R gem. It's probably the most currently developed and up to date gem out there for XMPP and Ruby.
The Roster Helper that is bundled with XMPP4R has a handy way to get your initial buddy list, online and offline users alike. The problem is, the XML response will not include presence updates. The roster helper does do some kind of presence updates of it's own, but it was too inconsistent to use. The correct way to deal with presence as an XMPP client would be to wait on a callback for presence updates. The initial update would send you all of your online buddy's statuses.
First set up the Jabber client in order to connect to the GTalk XMPP server:
cl = Jabber::Client.new(Jabber::JID::new(email))
cl.connect('talk.google.com', 5222)
cl.auth(password)
This above creates a new client connection with Google Talk. The connect command accepts a server, which for GTalk is 'talk.google.com', and the port 5222. 5222 is the standard for most XMPP servers. Next is the tricky part where you want to write a callback for presence updates:
# get the roster
roster = Jabber::Roster::Helper.new(cl)
mainthread = Thread.current
roster.add_presence_callback { |item,oldpres,pres|
# code I left out in here populates the name, jid, and presence stuff
# see the xmpp protocol response docs for specifics on how to get the info
@list << {:name => name, :jid => jid, :presence => pres.show.to_s}
}
The roster is first initialized, and we set up a presence callback. In this callback, we just populate a list of buddies represented as a hash. Name is their nickname you've given your buddy, the jid is the jabber id. In GTalk, the jid is actually just their email address. Presence hash that I put together is actually their availability status, which can be one of the following: away, do not disturb, extended away, nil. Nil is just available and online.
Notice that we also save the current thread as our main thread, this is important because we're going to block this thread until we get a few seconds worth of initial presence updates:
# send initial presence
cl.send(Jabber::Presence.new.set_show(:dnd))
# continue thread after timeout
t = Thread.new { sleep XMPP_REQUEST_TIMEOUT; mainthread.wakeup;}
# block thread
Thread.stop
To get GTalk to start calling the callback function, send an initial presence of the current user. In this case I'm sending do not disturb using the :dnd symbol. The next section is where I create a new Thread, that sleeps for a specified number of seconds, then wakes the main thread up. What does this all mean? Well keep looking, right after I have that new thread created, I block the current one. The current thread is the main thread, the one that's doing the fetching of online buddies. The client's browser is still waiting until the mainthread starts up again. Notice that we just wait for some amount of seconds before continuing, with NO indication of how many or if we have gotten all of our online buddies. That is the shaky part. I noticed that waiting 2 - 3 seconds gets all of my online friends, and using some "loading" graphic on the browser page keeps people from bailing out. Each callback has it's own thread, so to keep them from spawning over and over again, we need to kill the client connection.
So the main thread blocks right where we say Thread.stop, and continues on to render the page after we wake the main thread up. After we wakeup the thread, we immediately close the client connection, and go on to rendering the page with your populated list of online friends:
# kill client connection
cl.close
In the view code, we simply run through the array of friends and display their statuses. We then allow people to click and invite their friends to the site immediately.
A lot of the code has been stripped out or simplified from what we've actually used, but you'll get the point. If anyone has feedback, suggestions, or comments, please post them! I'd love to see how to better solve this problem.