
As previously mentioned, facebook released a new Graph API. It is based on OAuth 2.0 protocol (old authorization token also works). While it is fresh thing, there is no much ActionScript stuff around, so I came with FacebookOAuthGraph class. This class is meant to be used as an abstract class, while it contains just the basic authentication algorithm and call method to request data. It stores access token in SharedObject, so next time you came into app, you get connected on background without noticing (no popup etc.). Your token should expire in 24 hours.
Here is the code for the following flex app, to make it work, get latest FacebookOAuthGraph and FacebookOAuthGraphEvent classes.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
applicationComplete="init()">
<mx:Script>
<![CDATA[
import sk.yoz.events.FacebookOAuthGraphEvent;
import sk.yoz.net.FacebookOAuthGraph;
// facebook Application ID
private var clientId:String = "268718683475";
// path to our callback
private var redirectURI:String =
"http://blog.yoz.sk/examples/FacebookOAuthGraph/callback.html";
// required extended permissions
private var scope:String = "publish_stream,user_photos,user_photo_video_tags";
private var facebook:FacebookOAuthGraph = new FacebookOAuthGraph();
[Bindable] private var connected:Boolean;
private function init():void
{
facebook.clientId = clientId;
facebook.redirectURI = redirectURI;
facebook.scope = scope;
facebook.useSecuredPath = true;
facebook.addEventListener(FacebookOAuthGraphEvent.AUTHORIZED, authorized);
facebook.autoConnect(parameters);
log.text += "checkSavedToken()\n";
}
private function connect():void
{
facebook.connect();
log.text += "connect()\n";
}
private function authorized(event:FacebookOAuthGraphEvent):void
{
connected = true;
log.text += "authorized\n";
}
private function call(path:String, binary:Boolean):void
{
var loader:URLLoader = facebook.call(path);
loader.dataFormat = binary
? URLLoaderDataFormat.BINARY
: URLLoaderDataFormat.TEXT;
loader.addEventListener(FacebookOAuthGraphEvent.DATA, callComplete);
log.text += "call(" + path + ")\n";
}
private function changeStatus(message:String):void
{
var data:URLVariables = new URLVariables();
data.message = message;
var method:String = URLRequestMethod.POST;
var loader:URLLoader = facebook.call("me/feed", data, method);
loader.addEventListener(FacebookOAuthGraphEvent.DATA, callComplete);
log.text += "changeStatus(" + message + ")\n";
}
private function callComplete(event:FacebookOAuthGraphEvent):void
{
log.text += "call completed -> see result\n";
if(event.rawData is ByteArray)
{
var loader:Loader = new Loader();
loader.loadBytes(event.rawData as ByteArray);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function():void
{
image.source = loader;
});
}
else
{
result.text = event.rawData.toString();
}
}
]]>
</mx:Script>
<mx:HBox>
<mx:Button click="connect()" label="connect" />
<mx:Text text="{connected ? 'connected' : 'not connected'}" />
</mx:HBox>
<mx:HBox visible="{!connected}" includeInLayout="{!connected}">
<mx:Text text="#access_token=121161974560905%7C2..." />
<mx:TextInput id="hash" />
<mx:Button label="add hash" click="facebook.confirmConnection(hash.text)"/>
</mx:HBox>
<mx:HBox>
<mx:TextInput id="path" text="me" />
<mx:Button label="call" click="call(path.text, false)" enabled="{connected}"/>
<mx:Spacer width="20" />
<mx:TextInput id="path2" text="me/picture" />
<mx:Button label="call binary" click="call(path2.text, true)" enabled="{connected}"/>
</mx:HBox>
<mx:HBox>
<mx:TextInput id="status" text="testing FacebookOAuthGraph" />
<mx:Button label="change status" click="changeStatus(status.text)" enabled="{connected}"/>
</mx:HBox>
<mx:HDividedBox width="100%" height="100%">
<mx:TextArea width="30%" height="100%" id="log"/>
<mx:TextArea width="70%" height="100%" id="result"/>
<mx:Image id="image" />
</mx:HDividedBox>
</mx:Application>
Make sure your html wrapper defines correct allowScriptAccess and both id and name for <object> tag. This enables ExternalInterface.objectID. With swfobject use:
var params = {
allowScriptAccess: "sameDomain"
};
var attributes = {
id: "FacebookOAuthGraphTest",
name: "FacebookOAuthGraphTest"
};
swfobject.embedSWF("FacebookOAuthGraphTest.swf", "alternative", "100%", "100%", "10.0.0",
"expressInstall.swf", flashvars, params, attributes);
callback.html pushes url hash into flash app. When running this application from desktop (creating/debugging), your callback.html located on public domain has no access to its opener (different domain – XSS), so you need to pass access_token manualy into <TextInput id=”hash”>, but once your flash application is on the same domain with callback, it works automaticaly.
callback.html:
<!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">
<!--
if(window.opener && window.opener.confirmFacebookConnection)
{
window.opener.confirmFacebookConnection(window.location.hash);
self.close();
}
//-->
</script>
</head>
<body>
<p>You may now close this window.</p>
</body>
</html>
Click connect and allow facebook application. Facebook redirects to callback.html that pastes hash into flash and closes popup. Now you are authenticated. Next time you visit this flash application (refresh this page) you will get authenticated in background (if your access token is still valid). Notice, some graph api calls returns JSON objects (me), other may return binary data (me/picture). For now it may take some time to finish calls (5 second or more), but I hope facebook will soon make it fast.
You get your JSON decoded data via event.data. Just make sure you do not try to decode ByteArray (eg. me/picture)
// call("me")
private function callComplete(event:FacebookOAuthGraphEvent):void
{
trace(event.data.name);
}
Some calls to test:
Friends: me/friends
News feed: me/home
Profile feed (Wall): me/feed
Likes: me/likes
Movies: me/movies
Books: me/books
Notes: me/notes
Photos: me/photos
Videos: me/videos
Events: me/events
Groups: me/groups
Additional information about my facebook app:
Application ID: 268718683475
Connect URL: http://blog.yoz.sk/examples/FacebookOAuthGraph/
Base Domain: yoz.sk
FBML/iframe: iframe
Developer Mode: Off
Application Type: Desktop
Private Install: No
Based on facebook access token, it should be valid approximately for 24 hours. Notice the bold part, works as expiration time (unix time format). Use FacebookOAuthGraph.tokenToExpiration() to parse Date:
access_token=268718683475%7C2.w3fjqz80Xi1CdBXt7Ygh6A__.86400.1273158000-1215645368%7CDGv2l2HtTymd6cM6Fy_8k6P_8CQ.
Important update: May 19, 2010: Application is working again. Due to massive support in bug tracker (thanks all for your votes), facebook devs changed secured crossdomain.xml, so it allows unsecured requests. changeStatus() method added into example and some minor changes in FacebookOAuthGraph class (please update).
Here is what happend and why this app was not working for a while:
- May 11, 2010: facebook changed rules, so requests on unsecured graph service (via http://graph.facebook.com…) were limited to just those without access_token parameter. Requets to secured service (https://graph.facebook.com) resulted in security violation due to missing secure=”false” parameter in crossdomain.xml
- May 12, 2010: bug submitted
- May 12, 2010 – May 14, 2010: massive bug voting
- May 14, 2010: Bug confirmed by facebook dev team
- May 19, 2010: Bug fixed, crossdomain file changed
I am glad that facebook devs listens and care.
FAQ
How can I use this class in my iframe canvas page?
Please read article Authorizing Iframe Facebook Applications For Graph API
When I connect with this app in one browser (firefox), and I run another browser (chrome) I get automatically connected. Why?
Your authorization token is stored in SharedObject, that is OS-user persistent (eg. windows user). No matter what browser you run, all of those reads data from one SharedObject. If you need cookie or session persistent authorization, please extend my class and override methods that use SharedObject.
How to add request parameters into my call?
Pass URLVariables object as a second parameter to call() method:
// eg. in order to make this call "me/picture?type=large", do the following:
var data:URLVariables = new URLVariables();
data.type = "large"
call("me/picture", data);
Why I can not access me/photos?
Facebook has changed rules again. You also need “user_photo_video_tags” permission within your app. I have already added it into my app, so please remove your browser cache, refresh and click connect button again (even if you are connected already). Now it should work.
How to upload photo with graph api?
This is a piece of working code from Sean, thnx Sean:
// MultipartURLLoader by Eugene Zatepyakin can be found here: http://bit.ly/9wx4q7
public function uploadImageCall(path:String,ba:ByteArray,message:String,token:String=null):MultipartURLLoader
{
var mpLoader:MultipartURLLoader = new MultipartURLLoader();
mpLoader.addVariable("message", message);
mpLoader.addFile(ba, "image.jpg", "image");
loaderAddListeners(mpLoader.loader);
mpLoader.load(apiSecuredPath + "/"+ path + "&access_token="+ _token);
return mpLoader;
}
Can I make fql calls with this class?
However graph api does not implement fql calls, you can make fql calls using this class. The resulted data are not JSON but XML:
var data:URLVariables = new URLVariables();
data.query = "SELECT uid, name FROM user WHERE uid = XXX"; // insert your uid
var loader:URLLoader = facebook.call("method/fql.query", data,
URLRequestMethod.POST, null, "https://api.facebook.com");
loader.addEventListener(FacebookOAuthGraphEvent.DATA, fqlComplete);
private function fqlComplete(event:FacebookOAuthGraphEvent):void{
var xml:XML = new XML(event.rawData);
For fqlComplete method, please read Parsing FQL result article.
Can I post feeds with attachments?
Graph API has not documented attachment functionality for feeds, anyway, you can publish streams with attachments with this class as simple as:
var media:Object = {};
media.type = "flash";
media.swfsrc = "http://zombo.com/inrozxa.swf";
media.imgsrc = "http://blog.yoz.sk/wp-content/uploads/3d-150x150.jpg";
media.width = "80";
media.height = "80";
media.expanded_width = "120";
media.expanded_height = "120";
var attachment:Object = {};
attachment.name = "test name";
attachment.link = "http://blog.yoz.sk"
attachment.description = "test description";
attachment.caption = "test caption";
attachment.media = [media];
var data:URLVariables = new URLVariables();
data.message = "test message";
data.attachment = JSON.encode(attachment);
facebook.call("method/stream.publish", data, URLRequestMethod.POST, null, "https://api.facebook.com");