Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect when facebook's FB.init is complete

Update on Jan 04, 2012

It seems like you can't just call FB-dependent methods (for example FB.getAuthResponse()) right after FB.init() like before, as FB.init() seems to be asynchronous now. Wrapping your code into FB.getLoginStatus() response seems to do the trick of detecting when API is fully ready:

window.fbAsyncInit = function() {
    FB.init({
        //...
    });

    FB.getLoginStatus(function(response){
        runFbInitCriticalCode(); 
    });

};  

or if using fbEnsureInit() implementation from below:

window.fbAsyncInit = function() {
    FB.init({
        //...
    });

    FB.getLoginStatus(function(response){
        fbApiInit = true;
    });

};  

Original Post:

If you want to just run some script when FB is initialized you can put some callback function inside fbAsyncInit:

  window.fbAsyncInit = function() {
    FB.init({
      appId  : '<?php echo $conf['fb']['appid']; ?>',
      status : true, // check login status
      cookie : true, // enable cookies to allow the server to access the session
      xfbml  : true  // parse XFBML
    });
    FB.Canvas.setAutoResize();

    runFbInitCriticalCode(); //function that contains FB init critical code
  };

If you want exact replacement of FB.ensureInit then you would have to write something on your own as there is no official replacement (big mistake imo). Here is what I use:

  window.fbAsyncInit = function() {
    FB.init({
      appId  : '<?php echo $conf['fb']['appid']; ?>',
      status : true, // check login status
      cookie : true, // enable cookies to allow the server to access the session
      xfbml  : true  // parse XFBML
    });
    FB.Canvas.setAutoResize();

    fbApiInit = true; //init flag
  };

  function fbEnsureInit(callback) {
        if(!window.fbApiInit) {
            setTimeout(function() {fbEnsureInit(callback);}, 50);
        } else {
            if(callback) {
                callback();
            }
        }
    }

Usage:

fbEnsureInit(function() {
    console.log("this will be run once FB is initialized");
});

Actually Facebook has already provided a mechanism to subscribe to authentication events.

In your case you are using "status: true" which means that FB object will request Facebook for user's login status.

FB.init({
    appId  : '<?php echo $conf['fb']['appid']; ?>',
    status : true, // check login status
    cookie : true, // enable cookies to allow the server to access the session
    xfbml  : true  // parse XFBML
});

By calling "FB.getLoginStatus()" you are running the same request again.

Instead you could use FB.Event.subscribe to subscribe to auth.statusChange or auth.authResponseChange event BEFORE you call FB.init

FB.Event.subscribe('auth.statusChange', function(response) {
    if(response.status == 'connected') {
        runFbInitCriticalCode();
    }
});

FB.init({
    appId  : '<?php echo $conf['fb']['appid']; ?>',
    status : true, // check login status
    cookie : true, // enable cookies to allow the server to access the session
    xfbml  : true  // parse XFBML
});

Most likely, when using "status: false" you can run any code right after FB.init, because there will be no asynchronous calls.


Here is a solution in case you use jquery and Facebook Asynchronous Lazy Loading:

// listen to an Event
$(document).bind('fbInit',function(){
    console.log('fbInit complete; FB Object is Available');
});

// FB Async
window.fbAsyncInit = function() {
    FB.init({appId: 'app_id', 
         status: true, 
         cookie: true,
         oauth:true,
         xfbml: true});

    $(document).trigger('fbInit'); // trigger event
};

Another way to check if FB has initialized is by using the following code:

ns.FBInitialized = function () {
    return typeof (FB) != 'undefined' && window.fbAsyncInit.hasRun;
};

Thus in your page ready event you could check ns.FBInitialized and defer the event to later phase by using setTimeOut.


While some of the above solutions work, I thought I'd post our eventual solution - which defines a 'ready' method that will fire as soon as FB is initialized and ready to go. It has the advantage over other solutions that it's safe to call either before or after FB is ready.

It can be used like so:

f52.fb.ready(function() {
    // safe to use FB here
});

Here's the source file (note that it's defined within a 'f52.fb' namespace).

if (typeof(f52) === 'undefined') { f52 = {}; }
f52.fb = (function () {

    var fbAppId = f52.inputs.base.fbAppId,
        fbApiInit = false;

    var awaitingReady = [];

    var notifyQ = function() {
        var i = 0,
            l = awaitingReady.length;
        for(i = 0; i < l; i++) {
            awaitingReady[i]();
        }
    };

    var ready = function(cb) {
        if (fbApiInit) {
            cb();
        } else {
            awaitingReady.push(cb);
        }
    };

    window.fbAsyncInit = function() {
        FB.init({
            appId: fbAppId,
            xfbml: true,
            version: 'v2.0'
        });

        FB.getLoginStatus(function(response){
            fbApiInit = true;
            notifyQ();
        });
    };

    return {
        /**
         * Fires callback when FB is initialized and ready for api calls.
         */
        'ready': ready
    };

})();

I've avoided using setTimeout by using a global function:

EDIT NOTE: I've updated the following helper scripts and created a class that easier/simpler to use; check it out here ::: https://github.com/tjmehta/fbExec.js

window.fbAsyncInit = function() {
    FB.init({
        //...
    });
    window.fbApiInit = true; //init flag
    if(window.thisFunctionIsCalledAfterFbInit)
        window.thisFunctionIsCalledAfterFbInit();
};

fbEnsureInit will call it's callback after FB.init

function fbEnsureInit(callback){
  if(!window.fbApiInit) {
    window.thisFunctionIsCalledAfterFbInit = callback; //find this in index.html
  }
  else{
    callback();
  }
}

fbEnsureInitAndLoginStatus will call it's callback after FB.init and after FB.getLoginStatus

function fbEnsureInitAndLoginStatus(callback){
  runAfterFbInit(function(){
    FB.getLoginStatus(function(response){
      if (response.status === 'connected') {
        // the user is logged in and has authenticated your
        // app, and response.authResponse supplies
        // the user's ID, a valid access token, a signed
        // request, and the time the access token
        // and signed request each expire
        callback();

      } else if (response.status === 'not_authorized') {
        // the user is logged in to Facebook,
        // but has not authenticated your app

      } else {
        // the user isn't logged in to Facebook.

      }
    });
  });
}

fbEnsureInit example usage:

(FB.login needs to be run after FB has been initialized)

fbEnsureInit(function(){
    FB.login(
       //..enter code here
    );
});

fbEnsureInitAndLogin example usage:

(FB.api needs to be run after FB.init and FB user must be logged in.)

fbEnsureInitAndLoginStatus(function(){
    FB.api(
       //..enter code here
    );
});

Instead of using any setTimeout or setInterval I would stick to deferred objects (implementation by jQuery here). It's still tricky to resolve queue in proper moment, because init don't have callbacks but combining result with event subscription (as someone pointed before me), should do the trick and be close enough.

Pseudo-snippet would look as follows:

FB.Event.subscribe('auth.statusChange', function(response) {
   if (response.authResponse) {
       // user has auth'd your app and is logged into Facebook
   } else {
       // user has not auth'd your app, or is not logged into Facebook
   }
   DeferredObject.resolve();
});