Facebook Platform updates – w00t!
According to this blog post over at developers.facebook.com, some exciting changes are coming to the Facebook Platform.
I'm a little slow on the uptake here, the blog update I'm referring to is now 10 days old. I haven't been in the Facebook dev world of late, so I hope you'll forgive me for regurgitating old news.
The big update for me is in the 4th paragraph:
We are also moving toward IFrames instead of FBML for both canvas applications and Page tabs. As a part of this process, we will be standardizing on a small set of core FBML tags that will work with both applications on Facebook and external Web pages via our JavaScript SDK, effectively eliminating the technical difference between developing an application on and off Facebook.com.
This is excellent! I have actually been holding my breath for something like this for a while now. The restriction of FBML only content for tabs has been extremely restrictive, here are a handful of reasons why:
- Very strict HTML parsing - because the Platform was rendering tabs inline previously, it of course had to be VERY careful in how it handled offsite data, to protect users from all manner of scams/attacks. Now full flexibility is available because your iFrame is yours to control.
- Insanely strict JS parsing - same as first point, application tabs could only leverage basic Javascript and the unwieldy, poorly documented FBJS (Facebook Javascript). Now, you're free to access manipulate your IFrame document/window objects, DOM manipulate, include third party JS libs, etc, all to your hearts content.
- Embedded media was a pain - Granted, the tab FBML *did* allow you to embed Flash and AIR apps etc, but there were countless threads on the forum outlining issues they were having interacting with the host page, etc.
- Tab activation policies - There were some extremely frustrating rules with the way tabs were allowed to be "activated". No JS/FBJS was allowed to execute until the user had interacted with the tab in some manner, such as focusing a form element or clicking somewhere. This made it very cumbersome to implement any meaningful interactions with the user; alot of obnoxious boilerplate code had to be written for various ways in which you may actually start doing anything meaningful from JS, like AJAX requests.
- ... and lots more Everything from CSS parsing to the occasional time where the tab would just sit in an endless loading display when clicked. When I was writing a tab page I remember bashing my head against the wall trying to get some content to sit nicely in a cross browser fashion... it would have been easy under normal circumstances, but the Platform tab flavour was refusing to accept the *display: inline IE hack in the CSS. Joy.
This is going to really open up some great possibilities for interactive, rich web applications. I really cannot wait for this feature to be rolled out.
This news does come with some disappointment however; the sixth paragraph on the developers blog states the following:
Finally, due to low usage rates, we will remove application tabs from user profiles in the next couple months. Application tabs will continue to be supported on Facebook Pages.
There are plenty of great use cases to have application tabs on a user profile page. My personal Facebook profile has a tab that displays my latest last.fm scrobbles, my latest blog posts, etc. Personally, I believe that if the adoption rate for user profile tabs is low, the Facebook team should be coming up with ways to increase user acceptance of this feature, rather than removing it all together. Besides, the functionality in Facebook Page tabs is pretty much identical to the user profile equivalents... Why not support both?
There's other goodies in the developer blog update too, such as cleaning up the REST API considerably.
All in all, exciting changes coming to the Facebook platform in the coming months!
Facebook Access Tokens from Canvas Apps
UPDATE 26/07/10: looks like the Facebook Platform team is addressing this issue presently. There's a new migration option available to send session data in the request. When I used this parameter it seems that all old session related fb_sig parameters were no longer being sent, so I'd probably avoid using this. Instead refactor your applications to use the new OAuth support for Canvas apps, which is also available to enable in the App Settings Migration tab.
There's an open bug over at the Facebook bug tracker detailing an issue people are having with Canvas pages and the new Graph API. Right now, Facebook canvas applications are posting a session_key to the canvas callback URL. The new OAuth system of course uses the access token system, rather than the session_keys from the old Rest API.
There's a bit of info hiding in the documentation upgrade guide that outlines how to "exchange" a session key for an access token. All you need to do is POST some info to an endpoint and it'll spit back a valid access token.
Like so:
$ch = curl_init("https://graph.facebook.com/oauth/exchange_sessions");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
"type" => "client_cred",
"client_id" => "<your canvas application id>",
"client_secret" => "<your canvas application secret>",
"sessions" => $_REQUEST["fb_sig_session_key"]
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = json_decode(curl_exec($ch));
$accessToken = $result[0]->access_token;
So what's going on here? You're POSTing a request to the Facebook OAuth implementation to exchange your session key for an access token. You provide your application id and secret (important: this needs to be id and secret for the canvas application the request originated from of course) and the session ID of the user. Send this off and a JSON response will come back with the access token. Also, if needed, there's another property passed back - "expires", which will let you know when this access token will die.
Okay, so now you have an access token for use with the new API. Now what?
There's a little bit of fudging required to use this with the PHP SDK. The setSession() method of the PHP SDK doesn't just take an access token, it expects all the other crap that is usually contained in the cookie it saves. The idea behind my solution is that you shouldn't need to modify any base classes. The code below will con the Facebook library into accepting your newly acquired access token:
$session = array(
"uid" => "",
"session_key" => "",
"secret" => "",
"access_token" => $accessToken
);
ksort($session);
$sessionStr = "";
foreach($session as $sessionKey => $sessionValue) $sessionStr .= implode("=", array($sessionKey, $sessionValue));
$session["sig"] = md5($sessionStr."<your app secret>");
$facebook->setSession($session, false);
What this code is doing is constructing a fake session object. Since the only thing the SDK actually looks at in this session data is the access_token, we just fill the other entries with an empty str. Once this is done, we just calculate a signature for the data, as per the Facebook Wiki documentation. Again, we're only doing this because the setSession() method demands it.
Once this is done, you can go ahead and make api calls! Now wasn't that easy?
Also, as a little footnote, you should know that you don't actually have to fudge the session, you can just pass the access token you get into each api call, like so:
$facebook->api(array("method" => "stream.get", "access_token" => $accessToken));
... But this is a bit cumbersome. Also, with the fake session, you can just remove my code later when Facebook starts sending access token in the initial POST data.
You can see this solution in action at this Facebook page. Just click grant access, then click Go.
Hope this helps someone!
Facebook Platform Bugs
Sup peoples.
Please, for the love of god, sign up a Bugzilla account at developers.facebook.com and vote up a few bugs that are really starting to piss me off in the land of Facebook!
Bug #10291 - an annoying bug that prevents proper auto configuration of a Facebook Application using application.setProperties.
Bug #10454 - another fantastic little issue that causes permission dialogs in Profile Tabs to do weird stuff...
Bug #8260 - a bug that prevents a parent Facebook application from modifying the settings of a child application that was created by it.
Just a small taste of the bugs that are plaguing me at the moment.
The Facebook Platform
Over the past few days I've immersed myself in the engaging, complex, diverse, exciting and downright confusing land of Facebook development. I'm currently writing a WordPress plugin that integrates a blog fairly extensively into a Facebook Profile Tab. With the bulk of the functionality now written, I can step back and reflect on the experience as a whole. I'm now going to post some thoughts on it.
In a word - wow. The process has very much been a rollercoaster ride, let me tell you. I picked a bad time to launch into this project - Facebook is in the process of transitioning from "Facebook Connect" to the new Graph/"Facebook Platform/Open Graph/whatever branding, bringing a slew of new APIs, SDKs, and other changes along with it. For example, right now if you visit the Facebook Developers website, they're trying to rework all the documentation, hiding the original Developers Wiki in the process, leaving a HUGE gap in the documentation in the process.
Further, right now the Platform as a whole is riddled with inconsistencies in terminology and functionality. What's worse is the transitory phase is obviously quite involved, and there's bugs all over the place.
That being said, the Platform is a pretty sexy wonderland of functionality. When I wasn't encountering odd issues or server latency dramas, it was pretty cool to work with such a diverse environment. That being said, I haven't actually worked with the Graph API much yet - the application I wrote needed to be integrated into a profile tab, which still only supports FBML/FBJS - no iframe support yet.
I'm going to be posting alot of articles here over the coming weeks/months with nifty things I've discovered and "gotchas" I've worked around in the land of Facebook. Also, I'll be putting more info up here soon for the plugin I'm developing that I mentioned earlier, currently dubbed "Faceblog" - not sure if I'm gonna have troubles with this name yet
Stay tuned!
How to access Facebook API from Chrome Extension
So following on from my previous post, I decided I wanted to try two new things at once: Facebook API and a foray into developing a Google Chrome extension. With the notification API that will (hopefully) twig on in other major browsers, I'm writing an extension that will display a desktop notification when a Facebook notification is received. All that will be required is for Chrome to be running.
Step 1 was to play around with Chrome extensions. I haven't worked with plugins/extensions in any other browser, but I have to say.... Google NAILED this. It's so intuitive and straightforward. So easy to debug, so much power. I love you Google.
Anyway. Step 2 was where things got interesting - connecting to Facebook via the Javascript API and Facebook Connect. This proved to be quite complex. Facebook Connect has this fascination with the whole cross domain communication channel (XD for shortness) nonsense. Basically, this requirements essentially means to use the Javascript API you need to be hosting it on a standard web server to be able to add the xd_receiver.html file in a public-facing area where Facebook can see it.
After digging around the site for a while, I came across their fancy new Open Source Javascript SDK, I couldn't help but notice this didn't make a mention of the XD setup anywhere, so I assumed perhaps I might be able to work with this system better. As it happens, my suspicions were correct. The new JS library has some crazy voodoo magic set up so that an XD file isn't required, there's some kind of "XD proxy" running on the Facebook servers which will send login session data along the line to the JS library, using either document.postMessage or some Flash workaround thingy (not really sure how that works, don't particularly care.)
I immediately tried to use it in a Chrome extension, but alas! There was a few issues. I came up with workarounds for both though.
Issue #1 - the API does the following in the constructor:
_domain: {
api : window.location.protocol + '//api.facebook.com/',
cdn : (window.location.protocol == 'https:'
? 'https://s-static.ak.fbcdn.net/'
: 'http://static.ak.fbcdn.net/'),
www : window.location.protocol + '//www.facebook.com/'
},
Great! That would be fantastic if our chrome extension was running on http, but the protocol when running from options or background.html pages is "chrome-extension://". So when the API went to make with the server side communications, it was trying to remote to chrome-extension://api.facebook.com, which of course is not really gonna work.
My solution to this was to just fudge it by putting the following snippet in before using the FB lib anywhere:
FB._domain = {
api : 'https://api.facebook.com/',
cdn : 'https://s-static.ak.fbcdn.net/',
www : 'https://www.facebook.com/'
};
With that change, the API won't have any more tantrums when trying to phone home. Easy!
Issue #2 - Facebook Connect is still broken.
This one was the biggie. Essentially, even though the new API does some cool hocus pocus with XD, it's still referencing the "origin domain" in the request, this original domain is of course just going to be the extension URL, which is not terribly useful, as Facebook will freak out when it gets a request coming from an invalid URL. I tried fudging the origin to a valid domain (dodgy I know, I was getting desperate). Interestingly, Facebook was fine with this, but of course when the request came back to the browser and the XD proxy tries to postMessage() the session data back, the browser freaks out as it looks like an XSS attack.
There might be other ways around this drama, but I opted for what I feel to be a fairly elegant solution.
Essentially, I opted to "pretend" I'm something of a desktop application trying to authenticate with Facebook application (which is true in a sense, I suppose). This Developer Wiki page gave me some insight into how to authenticate my application with the FB user the old-fashioned way.
The idea is this: popup the login/app-authenticate page manually with a special URL, setting the return URL to a random dummy Facebook page they have running for desktop app clients: http://www.facebook.com/connect/login_success.html. When the user visits the login page, once they have logged in and allowed the app access (or if they are already logged in and have already allowed the app), they are redirected to a page that has the session data in the querystring encoded in JSON. Login achieved.
So I set about doing this in my extension, simple enough to start with:
var win = window.open("http://www.facebook.com/login.php?api_key=
Great! If I was a *real* desktop application and I was running a Webkit/IE/whatever browser instance as some kind of evil overlord, I could just detect when the browser redirects to login_sucess.html, grab the querystring, parse the session data out of it, and be on my merry way! I'm running in a browser though, so how about I just access the child window that I opened with window.open, access the location.search property and parse that? "NO", says the magical little pixies living inside the browser, "That would be against my strict Same Origin Policy (SOP, kinda like ... sop story, teehee)!!!".
Fair enough. There was an easy enough workaround though, I just embedded a content script via the Extension to sniff out the session data when it became available, and send it to the main extension. Like so:
Add the trigger for the content script to the extension manifest.json file:
"content_scripts": [
{
"matches": ["http://www.facebook.com/connect/login_success.html*"],
"js": ["prototype.js", "intercept_session.js"]
}
],
I threw prototype in there just for convenience sake, as you'll see in the next step.
Then intercept_session.js looks like this:
var params = window.location.search.toQueryParams();
if(!params.session) return;
var session = JSON.parse(params.session);
chrome.extension.sendRequest({message: "setSession", session: session}, function() {
window.close();
});
What this code does is parse the querystring (using Prototype), then check if the session data is present. If it is, parse the JSON into the session object and send it off to the extension Background Page via the extension message passing system to be saved.
The background page simply has this:
var session = null;
if(localStorage.session)
{
session = JSON.parse(localStorage.session);
}
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
if(!request.message)
return;
switch(request.message)
{
case "setSession":
{
localStorage.session = JSON.stringify(request.session);
session = request.session;
sendResponse();
break;
}
case "getSession":
{
sendResponse(session);
break;
}
}
});
Again, pretty straightforward stuff. I'm using the groovy new HTML5 local storage to remember the session data even if the browser is closed. The message handler simply listening for a session to be passed to it. When a session is provided, it will save it to local storage and a local variable. The getSession functionality is so other areas of your chrome extension can retrieve the session as needed (for example if you have a popup and want to query FB from there or something). You could obviously use this session anywhere as needed.
And that's that! From here you can make API calls to your hearts content. There's obviously some important things left out here, stale checking of the session when the background page loads up for example. Also, requesting extended permissions is not covered here, but it's pretty much the same as how the login deal works anyway. You would just update the login_success.html intercept script to check if this was a response for extended permissions, and check the querystring to ensure the permissions were supplied.
I've cooked up a quick little demonstration of this stuffs. Click here to install a demo extension that will add a button to the right of your address bar, which will show some clickable icons of 5 of your friends in a popup. You can also see the code for this extension here.
Time for me to go finish this extension!
