blog
Authorizing Facebook Applications in AIR

I was asked to make AIR compatibile version of FacebookOAuthGraph class. It was a nice little challenge for me, where I learn some new things about AIR. E.g. how easy it is to define the JavaScript callback directly from ActionScipt (HTMLLoader.window.methodName)… There are more ways to make authorization process work for you, I have chosen the one with popup window. In order to make FacebookOAuthGraph work for AIR, the short extending is required, instead of creating popup window from JavaScript, popup is created by HTMLLoader.createRootWindow (no ExternalInterface required)…
Lets start with the main AIR Application file:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
applicationComplete="init()">
<s:layout>
<s:VerticalLayout />
</s:layout>
... here comes the same content as in:
"http://blog.yoz.sk/2010/05/facebook-graph-api-and-oauth-2-and-flash/"
...
just change redirectURI to:
"http://blog.yoz.sk/examples/FacebookOAuthGraph/aircallback.html"
...
</s:WindowedApplication>Do not bother with callback.html (you do not need any, if you do, create an empty .html file) and make your Facebook class look something like this:
package
{
import flash.display.NativeWindowInitOptions;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.html.HTMLLoader;
import flash.net.URLRequest;
public class Facebook extends FacebookOAuthGraph
{
public var loader:HTMLLoader;
public function Facebook()
{
super();
}
override public function connect():void
{
var options:NativeWindowInitOptions = new NativeWindowInitOptions();
var boundaries:Rectangle = new Rectangle(200,200,500,500);
loader = HTMLLoader.createRootWindow(true, options, true, boundaries);
loader.load(new URLRequest(authorizationURL));
loader.addEventListener(Event.LOCATION_CHANGE, onLocationChange);
}
private function onLocationChange(event:Event):void
{
var hash:String = locationExtractHash(loader.location);
var error:String = locationExtractError(loader.location);
if(hash)
confirmConnection(hash);
if(hash || error)
destroyLoader();
}
protected function locationExtractHash(url:String):String
{
var index:int = url.indexOf("#");
if(index <= -1)
return null;
var hash:String = url.substr(index + 1);
return hashToToken(hash) ? hash : null;
}
protected function locationExtractError(url:String):String
{
var index:int = url.indexOf("?");
if(index <= -1)
return null;
var query:String = url.substr(index + 1);
var variables:URLVariables = new URLVariables(query);
return variables.hasOwnProperty("error") ? variables.error : null;
}
private function destroyLoader():void
{
loader.removeEventListener(Event.LOCATION_CHANGE, onLocationChange);
loader.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
loader.stage.nativeWindow.close();
loader = null;
}
}
}How does this works? You do not need any callback.html, just generate some redirect_uri that correctly targets your domain, it can be anything, even 404.html, or asdf-foo-bar.html file that does not exist. Once the Facebook, redirects you back to the redirect_uri, the url contains access_token within url hash
http://domain/whatever-or-404.html#access_token=123… now, if it does, it is parsed by locationExtractHash(), if it does not, the url contains error (user clicked Cancel button), the url contains error:
http://domain/whatever-or-404.html?error_reason=user_denied&error=access_denied&error_description=The+user+denied+your+request.In case of error locationExtractError() parses and error. In both cases (error or access_token), the HTMLLoader window is closed and token is handled via confirmConnection(). Yeah, this way you can authorize !ANY! existing facebook application for your AIR app.
Depricated version using JavaScript in callback
Keep the changes in main AIR Application file (see above), and make small changes in aircallback.html, in fact it got even simplier comparing to the original:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="sk" lang="sk" dir="ltr">
<head>
<script type="text/javascript">
<!--
confirmFacebookConnection(window.location.hash);
//-->
</script>
</head>
<body>
<p>You may now close this window.</p>
</body>
</html>… finally some method changes in FacebookOAuthGraph class:
package
{
import flash.display.NativeWindowInitOptions;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.html.HTMLLoader;
import flash.net.URLRequest;
import sk.yoz.net.FacebookOAuthGraph;
public class Facebook extends FacebookOAuthGraph
{
public var loader:HTMLLoader;
public function Facebook()
{
super();
}
override public function connect():void
{
var options:NativeWindowInitOptions = new NativeWindowInitOptions();
var boundaries:Rectangle = new Rectangle(200,200,500,500);
loader = HTMLLoader.createRootWindow(true, options, true, boundaries);
loader.load(new URLRequest(authorizationURL));
loader.addEventListener(Event.HTML_DOM_INITIALIZE, onDOMInit);
}
private function onDOMInit(event:Event):void
{
loader.window[jsConfirm] = confirmConnection;
}
override public function confirmConnection(hash:String):void
{
super.confirmConnection(hash);
loader.stage.nativeWindow.close();
}
}
}What it does is, it pushes JavaScript callback into HTMLLoader that is called via aircallback.html JavaScript…
Where to go from here:
Making ActionScript objects available to JavaScript
Creating Windows in HTML and JavaScript AIR Applications
How to access an iframe in an Adobe AIR application?