View Sidebar
Building a Chrome Extension that Connects to a Facebook App

Building a Chrome Extension that Connects to a Facebook App

December 4, 2012 3:53 amComments are Disabled

While working on Feedhound, we ran into an unexpected problem where we were having trouble loading the Facebook SDK from our extension onto the target page. Since our app depends on being able to run custom social searches based on the users’ feeds, needless to say this was a deal breaker.

The first hurdle was the fact that the window.fbAsyncInit() method will not fire if the site page does not have the same URL as your Facebook app URL. This restriction will hold even if you use a content script to call the JS from your app URL–it reads the domain of the page the script is run on, not the domain of the script source. So even though we could append the SDK to any page we wanted, it never initiated the FB connection. This will be a problem for anyone who is trying to authenticate a Facebook user via a Chrome extension.

The workaround is pretty easy, it just involves inserting an invisible iFrame into the target page, running the Facebook SDK from the iFrame (whose source is your app URL), then using the window.postMessage API to send a message from your iFrame to the parent page, where your content script is running to pick up the message.

Here’s the source for the <YOUR_FACEBOOK_APP_URL>/iframe.html page (replace “<YOUR_APP_ID>” and “<PARENT_PAGE_URL>”):

<!doctype html>
<html lang="en">
<head>
 <meta charset="utf-8">
</head>
<body>
<div id="fb-root"></div>
<script>
 window.fbAsyncInit = function () {
 // init the FB JS SDK
 FB.init({
 appId:'YOUR_APP_ID', // App ID from the App Dashboard
 status:true, // check the login status upon init?
 cookie:true, // set sessions cookies to allow your server to access the session?
 xfbml:true // parse XFBML tags on this page?
 });
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
 var uid = response.authResponse.userID;
 var at = response.authResponse.accessToken;
 // This is your messaging to the parent of the iFrame
 parent.postMessage({connectStatus:"" + response.status + "", userID:"" + uid + "", accessToken:"" + at + ""}, "https://www.<PARENT_PAGE_DOMAIN>"); //This MUST match the root domain where the iFrame will be inserted, or the message won't get passed
 } 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.
 }
 });
};
 // Load the SDK's source Asynchronously
 // Note that the debug version is being actively developed and might
 // contain some type checks that are overly strict.
 // Please report such bugs using the bugs tool.
 (function (d, debug) {
 var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
 if (d.getElementById(id)) {
 return;
 }
 js = d.createElement('script');
 js.id = id;
 js.async = true;
 js.src = "https://connect.facebook.net/en_US/all" + (debug ? "/debug" : "") + ".js";
 ref.parentNode.insertBefore(js, ref);
 }(document, /*debug*/ true));
</script>
</body>
</html>

Here’s the source for your manifest.JSON file:

{
 "name": "Chrome Extension that Connects to a Facebook App",
 "version": "0.1",
 "description": "To connect a Chrome extension to a Facebook app!",
 "content_scripts": [
 {
 "matches": ["*://*.<PARENT_PAGE_DOMAIN>/*"], //The page(s) where your content scripts will fire, which has to match the message passing param in iframe.html
 "css" : ["main.css"],
 "js" : ["main.js"],
 "run_at" : "document_start",
 "all_frames" : false
 }
 ],
 "manifest_version": 2,
 "minimum_chrome_version": "17"
}

Here’s the source for your main.js file:

window.onload = function(){ // this could be done faster with the livequery() plugin for jquery
elt = document.createElement('iframe');
elt.id = 'facebook_load_frame';
elt.src = 'https://<YOUR_FACEBOOK_APP_URL>/iframe.html';
document.getElementsByTagName('body')[0].appendChild(elt);
};
// Message passing API from David Walsh at http://davidwalsh.name/window-iframe
// Create IE + others compatible event handler
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Listen to message from child window
eventer(messageEvent,function(e) {
 console.log("Connection status: "+e.data.connectStatus+"; UserID: "+e.data.userID+"; AccessToken: "+e.data.accessToken);
 //This is the data from the Facebook SDK
},false);

And, to top it off, the main.css file (to be derived via a content script to avoid potential FOUC issues):

#facebook_load_frame {
 width:1px;
 height:1px;
 border:0;
 position:absolute;
 top:0;
 left:0;
 display:none;
}

Finally, make sure that whether you are using a https:// or http:// protocol, that your protocol is consistent throughout. Your iFrame should have the same protocol as the parent page (to avoid “insecure content” warnings) and the Facebook SDK will throw an error if there’s a protocol mismatch as well.

Enjoy!

Comments are closed