Had a fun time adding Facebook chat to a NodeJS-backed site - actual real Facebook chat. That means people on your very own website can chat with their Facebook friends while on your site.
Our good friends at Facebook were kind enough to provide API hooks for authenticating into Facebook Chat using OAuth - SO your Facebook users just need to authorize your app and they can chat thru Facebook on your site. Unfortunately no word yet about opening up Video Chat - hopefully that'll come soon.
The basic idea is this:
FIRST Users approve your app/website with chat permissions with Facebook - your user just needs to do this once. Then in a loop:
- User visits your webpage
- Your page includes a Javascript XMPP client which can handle Facebook OAuth authentication (and the XMPP protocol in general)
- When your user is ready to chat fire up the XMPP client - which is configured to talk to back to your app which will proxy that request to your locally running BOSH server which then proxies the requests off to Facebook
- Profit!
What He Say?
If yer new to this stuff as I was here are some basic terms:
XMPP - a chat protocol that is the standardized version of the 'Jabber' protocol. XMPP is your friend.
BOSH - Bidirectional-streams Over Synchronous HTTP (BOSH) Good god don't actually read that - it's yer standard 'how the monkey do we do bi-directional async message passing over HTTP?' - the gory details do not concern us - it works. Like socket.io for web-based XMPP clients.
It is expected your know your way around authenticating your webapp (or desktop app) via Facebook. Here is where to get started.
User Allows your website to host FB Chat
This is your standard FB authentication as documented here - just make sure to request the 'xmpp_login' permission. I would also recommend getting the 'offline_access' permission while yer at it.
Install a BOSH server
I'm going to use 'node-xmpp-bosh' in keeping with the NodeJS theme BUT any old BOSH server will do (here's one written in Java if that's how you roll). This guy will route XMPP messages between your Javascript XMPP client back and forth to Facebook (or any other XMPP server). This thing can be treated just as a service that needs to be started once & forgotten.
% npm install node-xmpp-bosh You do NOT need to edit its configuration file in /etc/bosh.conf.js - in fact you probably should not change anything in there Frankly to us this thing is just a black box. Here are the most important configuration bits:
port: 5280, host: '0.0.0.0', path: /^\/http-bind\/$/
So it's going to listen on localhost on port 5280 and expects requests of the form /http-bind/* - basically nothing you need to worry about.
You can then either run it in 'standalone' mode - you can start it when your webserver boots just like any other service or start it from the comand line or from within your NodeJS code.
Note is does NOT need to run as root - it's basically a webserver that listens on port 5280 and that's about it.
For grins here's how to start it from within your NodeJS code:
var nxb = require('node-xmpp-bosh'), bosh_server = nxb.start({ port: 5280, no_tls_domains: [ 'chat.facebook.com' ] });
Note Facebook's chat servers do NOT support TLS - so if you are running any other BOSH server ensure where ever the config option is to turn TLS off it's turned off for 'chat.facebook.com' .
Client-side XMPP Library
We need a JS library that supports Facebook's OAuth XMPP authentication - Hey! Here's one Now! JSJaC - besides having an insane name, has Facebook OAuth Authentication baked right in! It also has a Facebook Authentication example in its Facebook example.
JSJaC Configuration
First you need to include the JSJaC Javascript on your page - remember the same origin policy - have your NodeJS webapp serve this file and we'll deal with proxying its messages below:
<script type="text/javascript" src="js/JSJaC.js"></script>
Or wherever you wanna put it. When you're ready to open up a chat connection to Facebook first we create a connection object telling JSJaC where to talk to:
//Setting the XMPP connection configurations var oArgs = { //Set the http-binding address (typically /http-bind/ but changing it for grins httpbase: '/fbchat', //Set the Timerval timerval: 2000 }; //Creation of the XMPP connection object var con = new JSJaCHttpBindingConnection(oArgs);
Once we're got a connection object we can attach event listeners for all interesting XMPP events - like new messages and people coming and going. See the JSJaC documentation for more details.
SO we've created our XMPP connection object - attached event handles for interesting events - now we can connect to Facebook's XMPP server:
/* Setting the connection authType and the Facebook app configuration * that will be used to allow the user login inside the appication * and start to chat with his friends */ var conArgs = { //Setting the authorization type to "x-facebook-platform" - VERY IMPORTANT!!!! authtype: 'x-facebook-platform', //Setting the Facebook Application configuration facebookApp: FB_APP_OBJECT }; //Let's connect to Facebook server con.connect(conArgs);
First do NOT forget to set the 'authtype' key correctly!! If you don't JSJaC will try to use standard XMPP authentication protocols and die an ugly death.
Second - what the heck is FB_APP_OBJECT???
Ok JSJaC (or ANY FB-compliant XMPP client) needs 3 things to authenticate to Facebook's XMPP server using OAuth:
- Yer Facebook API key
- Yer Facebook Application's Secret key
- The Facebook Session key
These are supplied to JSJaC by an object having 3 corresponding functions which return those values:
- getApiKey()
- getApiSecret()
- getSession()
So those first 2 values are standard values you can get from your Facebook Application's page.
The Session key is given by Facebook when your user logs into Facebook (& has granted your app the 'xmpp_login' permission. Now I myself also request the 'offline_access' permission from Facebook so you only have to get this value once & then save it off somewhere for just this very moment. I'm a Redis man myself.)
Your Facebook API key can be hard-coded into your Javascript safely HOWEVER your Application Secret parameter is supposed to be a SECRET so I would not recommend hard-coding that value into your Javascript.
So you should max an Ajax call (ideally over SSL) to get your Application secret and you can grab the user's Session key while you're at it.
ANYWAYS back to FB_APP_OBJECT. FB_APP_OBJECT provides those 3 function mentioned above that provide those values.
Here's the easiest one possible (note all values are totally made up and will not work 'for reals'):
FB_APP_OBJECT = { getApiKey: function( ) { return 'j43983489fj3f498'; }, getApiSecret: function() { return 'e09ef09kef09wefkefw'; }, getSession(): function() { return '38328923dj239d8j2938dj'; } };
Your FB_APP_OBJECT hopefully will be smarter than that!!! So the client side is all done - now we need the server to deal with this...
NodeJS Proxy to BOSH Server
Our JS XMPP client (JSJaC) can only talk to the server/port that served it - ye olde same origin policy. However our NodeJS server in this case is serving this file SO we need to proxy JSJaC's HTTP requests over to our local XMPP BOSH server running on port 5280. Of course you can proxy these requests to a BOSH server running anywhere - ours happens to be running locally.
Here's what our Connect-based NodeJS proxy code looks like:
if (url.pathname == '/fbchat') {
// proxy these connections to localhost:5280 req.on('data', function(chunk) { var options = { host: 'localhost', port: 5280, path: '/http-bind/', method: 'POST' }, fb_req = http.request(options, function(fb_res) { fb_res.on('data', function(fb_data) { //console.log('xmpp response: ' + fb_data.toString()); res.end(fb_data.toString()); }); }); //console.log('xmpp request: ' + chunk.toString()); fb_req.end(chunk.toString()); });
}
So some interesting things here - the XMPP BOSH requests are all POSTs - so we only listen for those. Also we've configured JSJaC to POST back to us a '/fbchat' - could be anything - like '/http-bind' is typical. So as soon as we get POST data from JSJaC we send it to our BOSH server on port 5280 at '/http-bind/' and any data we get back from the BOSH server we pass back to JSJaC.
Fire It All Up
SO fire up our NodeJS server - that will start our BOSH server listening on port 5280. It will serve the JSJaC.js seed file. It should also give JSJaC our Facebook Application secret and the user session for when our user is ready to chat via some Ajax happiness. Obviously anyone can still see these values so SSL for them would be nice any another smart way to get our value out to JSJaC.
User visits our site, gives us permissions to use Facebook chat, which also gives us our Session key that JSJaC needs. Our NodeJS app proxyies JSJaC BOSH POSTs over to our BOSH server and proxies any replies back. Now can then connect to Facebook's chat server and respond to events and send messages all via JSJaC's APIs. And life is good!
Wrapping Up
Now you can write a sweet chat UI (or not!) for your site and users can chat with all their Facebook friends without ever having to leave your wonderful site!!
Since JSJaC is an all-purpose XMPP client your users can also connect to any other XMPP server/chat.
Have fun and I hope you enjoyed my VERY FIRST BLOG POST!!! FTW!!
Great article, thanks!
ReplyDeleteBtw, its now
bosh_server = nxb.start_bosh
instead of
bosh_server = nxb.start
Thanks Afanasy good to know.
ReplyDeleteThanks for the tutorial! This is exactly what I have been looking for. Do you have any project code available for download? I am a noob and would love to see everything all put together.
ReplyDeleteHi,
ReplyDeleteThanks for this post, it's great.
Is the code in this example still up to date with the new versions of node-xmpp-bosh, JSJaC... ?
Hello,
ReplyDeleteI am trying to install node-xmpp-bosh in windows and it's not worked.
I have contact the author and they have tell me that it's not compaitilbe on windows.
Any idea for installing it on windows.
Thanks
Hi Zzo, i'm getting stuck at one of the steps. Can you publish your source code for this project? I want compare code and see where i went wrong. thank u
ReplyDelete