Saturday, October 23, 2010

Inside Facebook's JavaScript SDK code...

Over the past 2 weeks, I've been curious to figure out how Facebook's JavaScript SDK is implemented. The documentation and source code is discussed at http://github.com/facebook/connect-js/. The JavaScript library that you are supposed to add is located at: http://connect.facebook.net/en_US/all.js.

1. First, you can use Rhino and JS Beautifier (http://jsbeautifier.org/) to de-minify the http://connect.facebook.net/en_US/all.js file. Using the uncompressed version allows you not only review the source code but also enables you to set breakpoints and add alert statements to get a better understanding how the JavaScript code works.

2. Once you de-minify the code, you'll notice that Facebook's JavaScript SDK attempts to modularize the different aspects of its code. There are several different modules included with the all.js file. Among the notable modules and their accompanying utils inside this file are:

FB - getLoginStatus(), getSession(), login(), logout()
Array (array utils) - indexOf, merge, filter, keys, map, forEach
QS (query string utils) - encode, decode
Content (DOM-related utils) - append, appendHidden, insertIFrame, postTarget
Flash (Flash-related utils) - init, hasMinVersion, onReady
JSON - stringify, parse, flatten
ApiServer - graph, rest, oauthRequest, jsonp, flash
EventProvider - subscribers, subscribe, unsubscribe, monitor, clear, fire
Intl (international utils) - _endsInPunct, _tx, tx
String - trim, format, escapeHTML, quote
Dom - containsCss, addCss, removeCss, getStyle, setStyle, addScript, addCssRulse, getBrowserType, getViewPortInfo, ready
Dialog - _findRoot, _showLoader, _hideLoader, _makeActive, _lowerActive, _removeStacked, create, show, remove
XD (cross-domain utils) - init, resolveRelation, handler, recv, PostMessage.init, PostMessage.onMessage, Flash.init, Flash.onMessage, Fragment.checkandDispatch
Arbiter - inform
UiServer - genericTransform, prepareCall, getDisplayMode, getXdRelation, popup, hidden, iframe, async, _insertIframe, _triggerDefault, _popupMonitor, _xdChannelHandler, _xdNextHandler, _xdRecv, _xdResult
Auth - setSession, xdHandler, xdResponseWrapper
UIServer.Methods - permissions.request, auth.logout, auth.status
Canvas - setSize, setAutoResize

Inside all.js, you'll notice inside the source code the extensive use of FB.provide(), which is essentially a fancy classical object-oriented way to attach these modules to the FB object. For instance, FB.provide('Array', { ... }) is simply a way to add the Array object and its accompanying functions (i.e. FB.Array.indexOf()). (You can convince yourself by reviewing the provide() and create() functions inside the file to see how things work):
create: function(c, h) {
    var e = window.FB,
        d = c ? c.split('.') : [],
        a = d.length;
    for (var b = 0; b < a; b++) {
      var g = d[b];
      var f = e[g];
      if (!f) {
        f = (h && b + 1 == a) ? h : {};
        e[g] = f;
      }
      e = f;
    }
    return e;
  },

 provide: function(c, b, a) {
    return FB.copy(typeof c == 'string' ? FB.create(c) : c, b, a);
  },
3. When you call FB.init(), several things happen:
 a. The code verifies that you have provide an API key parameter (i.e. FB.init ( {apiKey : '12345'});).
 b. The function init() tries to load the fbs_ cookie that relates to this API key, setting FB.Cookie._enabled to be true, attempts to find a cookie that matches fbs_ + API_KEY for the domain, and sets the FB._session according to the cookie.
 c. The code then invokes getLoginStatus(), which attempts to run the auth.status function.
FB.ui({
      method: 'auth.status',
      display: 'hidden'
    }, c);
d. Inside the auth.status function, you will see a few notable things:
 1. A cross-domain handler is used (FB.Auth.xdHandler). These handlers will be used later to deal with the results rom invoking extern/login_status.php.
2. Three different callbacks are added: no_session, no_user, ok_session. Each of these callbacks translate to results of notConnected, unknown, and connected, respectively.
3. The callbacks each return a function called xdResponseWrapper() that is used to parse the JSON response from the result and used to update the FB._session object.
'auth.status': {
    url: 'extern/login_status.php',
    transform: function(a) {
      var b = a.cb,
          c = a.id,
          d = FB.Auth.xdHandler;
      delete a.cb;
      FB.copy(a.params, {
        no_session: d(b, c, 'parent', false, 'notConnected'),
        no_user: d(b, c, 'parent', false, 'unknown'),
        ok_session: d(b, c, 'parent', false, 'connected'),
        session_version: 3,
        extern: FB._inCanvas ? 0 : 2
      });
      return a;
In fact, if you were to step through this code, you could see that there are three callbacks:
no_user="http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f146913dd972a&origin=http%3A%2F%2Fdev.myhost.com%2Ff14444845a8136&relation=parent&transport=postmessage&frame=f87b5165d6fd26"
 
no_session="http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f3424882dbe5a&origin=http%3A%2F%2Fdev.myhost.com%2Ff14444845a8136&relation=parent&transport=postmessage&frame=f87b5165d6fd26"

no_user="http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f3424882dbe5c&origin=http%3A%2F%2Fdev.myhost.com%2Ff14444845a8136&relation=parent&transport=postmessage&frame=f87b5165d6fd26"
Each of these callbacks (cb=xxxxxxx) refer to a separate JavaScript function that will be invoked depending on the result returned by the cross-domain auth.status request. The all.js will insert a hidden iframe with a pointer to these callbacks. If we use the Firebug console and search for $('iframe') objects, you would see something similar:
<iframe scrolling="no" id="f1970fe755e1e58" name="f1a7f022a28bd14" style="border: medium none; overflow: hidden;" class="FB_UI_Hidden" src="http://www.facebook.com/extern/login_status.php?access_token=false&api_key=022046fa222ebeac8bdc99ec4ebdf8b2&display=hidden&extern=2&locale=en_US&method=auth.status&next=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df191b56da21a52a%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dopener%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26%26result%3D%2522xxRESULTTOKENxx%2522&no_session=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df3424882dbe5a%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dparent%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26&no_user=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df146913dd972a%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dparent%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26&ok_session=http%3A%2F%2Fstatic.ak.fbcdn.net%2Fconnect%2Fxd_proxy.php%23cb%3Df1ce4645681670e%26origin%3Dhttp%253A%252F%252Fdev.myhost.com%252Ff14444845a8136%26relation%3Dparent%26transport%3Dpostmessage%26frame%3Df87b5165d6fd26&sdk=joey&session_version=3"></iframe>
If you were to copy the entire src tag, do a wget "" -O /tmp/bla, you would see that the code returned would have an XD Proxy code that will execute doFragmentSend() when loaded. In other words, the IFrame will load this JavaScript code from www.facebook.com. What actually happens is that this iframe will redirect to xd_proxy.php, which will provide the frame of the callback to invoke in the cb= code. The redirect URL looks similar to the following:
http://static.ak.fbcdn.net/connect/xd_proxy.php#cb=f28ec7bcadf45c&origin=http%3A%2F%2Fdev.myhost.com%2Ffeb2a54d47bbae&relation=parent&transport=postmessage&frame=f2b29b433ebc468
Inside the xd_proxy.php code, these are the critical lines for the cross-domain request. Since params.relation is equal to 'parent' (relation=parent in the URL string), the resolveRelation() will return back window.parent and invoke the window.parent.postMessage() function that was added by the all.js file.
// either send the message via postMessage, or via Flash
  if (params.transport == 'postmessage') {
    resolveRelation(params.relation).postMessage(fragment, params.origin);
When the postMessage() gets invoked, an event listener is created for receiving the related event.data, which is then used to decode the URL query string and extract the cb= callback. The source code. This callback function is then execute (assuming the callback exists):
recv: function(b) {
    if (typeof b == 'string') b = FB.QS.decode(b);
    var a = FB.XD._callbacks[b.cb];
    if (!FB.XD._forever[b.cb]) delete FB.XD._callbacks[b.cb];
    a && a(b);
  },
  PostMessage: {
    init: function() {
      var a = FB.XD.PostMessage.onMessage;
      window.addEventListener ? window.addEventListener('message', a, false) : window.attachEvent('onmessage', a);
    },
    onMessage: function(event) {
      FB.XD.recv(event.data);
    }
  },
One side effect is that if the no_user or no_session callback is invoked, then the function that tries to parse the response and invoke the FB.Auth.setSession() will fail.
xdResponseWrapper: function(a, c, b) {
    return function(d) {
      try {
        b = FB.JSON.parse(d.session);
      } catch (f) {}
      if (b) c = 'connected';
      var e = FB.Auth.setSession(b || null, c);
      e.perms = d && d.perms || null;
      a && a(e);
    };

No comments:

Post a Comment