Extending FacebookOAuthGraph Class

While The Facebook Graph API article becomes pretty popular and a lot of developers keep asking me to publish some practices I use, I managed to create this blog post. Here is a list o some functions you may want to extend FacebookOAuthGraph Class with.

Extending & Singletonize

First, lets make our class singleton, so the whole flash project can simply access and use one instance

public class Facebook extends FacebookOAuthGraph
{
    private static const _instance:Facebook = new Facebook();
    public function Facebook()
    {
        super();
        useSecuredPath = true;
        if(instance)
            throw new Error("Use Facebook.instance!");
    }

    public static function get instance():Facebook
    {
        return _instance;
    }
    ....
}

Now anywhere in your project:

var facebook:Facebook = Facebook.instance;

Auto Execution After Authorization:

Lets say, when user clicks on publish post before your application is connected, just make connect() followed by desired function:

if(!facebook.authorized)
    facebook.connect();
facebook.publishPost(...)

publishPost() automatically executes again after connected thanks to Callbacks class

import sk.yoz.utils.Callbacks;

private function onAuthorized(event:FacebookOAuthGraphEvent):void
{
	Callbacks.execute("FBstuff");
}

public function publishPost(...):void
{
    if(!authorized)
        return Callbacks.add(this, arguments.callee, arguments, "FBstuff");

    // here continues the function code
    ...
}

Application Friends

Lets get list of friends using the application:

private function getAppFriends():void
{
	var data:URLVariables = new URLVariables();
	data.query = "SELECT uid, name, pic_square " + 
			"FROM user " + 
			"WHERE uid IN " + 
				"(SELECT uid2 FROM friend WHERE uid1=" + me.id + ") " + 
				"AND is_app_user";
	
	var loader:URLLoader = call("method/fql.query", data, 
		URLRequestMethod.POST, null, "https://api.facebook.com");
	loader.addEventListener(FacebookOAuthGraphEvent.DATA, onGetAppFriends);
}

private function onGetAppFriends(event:FacebookOAuthGraphEvent):void
{
	var xml:XML = new XML(event.rawData);
	var ns:Namespace = xml.namespace();
	default xml namespace = ns;
	xml.namespace(ns.prefix);
	xml.ignoreWhite = true;
	for each(var user:XML in xml.user)
	{
		user.uid.toString();
        user.name.toString();
		...
    }
    default xml namespace = new Namespace("");
}

Publishing Feeds

Publish feed with image:

public function publishPost(message:String, attachmentName:String, 
	attachmentDescription:String):void
{
	var media:Object = {};
	media.src = "http://mydomain.com/feedimage.jpg";
	media.href = "http://apps.facebook.com/myapp";
	media.type = "image";
	
	var attachment:Object = {};
	attachment.name = attachmentName;
	attachment.href = "http://apps.facebook.com/myapp";
	attachment.description = attachmentDescription;
	//attachment.caption = "test caption";
	attachment.media = [media];
	
	var data:URLVariables = new URLVariables();
	data.message = message;
	data.attachment = JSON.encode(attachment);
	
	call("method/stream.publish", data, URLRequestMethod.POST, 
		null, "https://api.facebook.com");
}

Publish feed with flash:

public function publishFlash(message:String, href:String, swfSrc:String,
	mediaSrc:String, attachmentName:String, attachmentCaption:String, 
	attachmentDescription:String, properties:Object=null):void
{
	var media:Object = {};
	media.type = "flash";
	media.swfsrc = swfSrc;
	media.imgsrc = mediaSrc;
	//media.width = "80";
	//media.height = "80";
	media.expanded_width = "460";
	media.expanded_height = "460";
	
	var attachment:Object = {};
	attachment.name = attachmentName;
	attachment.href = href;
	attachment.caption = attachmentCaption;
	attachment.description = attachmentDescription;
	attachment.media = [media];
	attachment.properties = properties; // action links
	// properties {prop1:{text: "value 1", href:"http://"}, prop2:...};
	
	var data:URLVariables = new URLVariables();
	data.message = message;
	data.attachment = JSON.encode(attachment);
	
	call("method/stream.publish", data, URLRequestMethod.POST, null, 
		"https://api.facebook.com");
}

Publish feed with action links:

var properties:Object = {};
properties.MOVE = {
	text: "mouse drag", 
	href:config.urlManager.boardURL};
properties.ZOOM = {
	text: "double click, [Page Up], [Page Down]", 
	href:config.urlManager.boardURL};
properties.ROTATE = {
	text: "[Ctrl] + mouse move", 
	href:config.urlManager.boardURL};

attachment.properties = properties;

Publishing feed using Graph API (docs, from facebook bugzilla):

  • forget everything related to the old “media” stuff of the attachment
  • put what you had in swfsrc into source (mandatory)
  • put what you had in imgsrc into picture (mandatory)
  • link is mandatory also and must points to the connect or canvas URL
  • Feed arguments: message, picture, link, name, caption, description, source

Uploading Photo

This is a piece of working code from Sean, more upload handling functions and album creating can be found here. thnx Sean:

// MultipartURLLoader by Eugene Zatepyakin can be found here: http://bit.ly/9wx4q7
public function uploadImageCall(bytes:ByteArray, message:String):MultipartURLLoader
{
    var mpLoader:MultipartURLLoader = new MultipartURLLoader();
    mpLoader.addVariable("message", message);
    mpLoader.addFile(bytes, "image.jpg", "image");
    loaderAddListeners(mpLoader.loader);
    mpLoader.load(apiSecuredPath + "/me/photos?access_token="+ token);
    return mpLoader;
}

Palming Token

When localy debuging your app you may want to palm off custom access_token:

if(parameters.debug)
    facebook.hackToken(parameters, "110363ABXY..."); // copypaste from callback.html hash
facebook.autoConnect(parameters);

in Facebook.as:

public function hackToken(parameters:Object, token:String):Object
{
	if(!parameters.session)
		parameters.session = JSON.encode({
			access_token:String(token).replace(/\%7C/g, "|")});
	return parameters;
}

Advanced Crossdomain Working Authorization

Or if you prefer or need advanced callback, that works crossdomain, and also for local debuging: 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" src="js/swfobject.js"></script> 
	<script type="text/javascript"> 
	<!--
		if(window.opener && window.opener.confirmFacebookConnection){
			window.opener.confirmFacebookConnection(window.location.hash);
			self.close();
		}else{
			var flashvars = {};
			flashvars.connectionName = "_facebookConnector";
			flashvars.methodName = "confirmConnection";
			flashvars.hash = window.location.hash;
			
			swfobject.embedSWF("flash/Callback.swf", "flash", "1", "1", "10.0.0",
				"expressInstall.swf", flashvars, {}, {});
		}
	//-->
	</script> 
</head> 
<body> 
	<p>You may now close this window.</p> 
	<div id="flash"></div> 
</body>
</html>

universal callback.swf (741 Bytes) main class:

package
{
    import flash.display.Sprite;
    import flash.net.LocalConnection;
    
    public class Callback extends Sprite
    {
        public function Callback()
        {
            super();
            var parameters:Object = loaderInfo.parameters
            var localConnection:LocalConnection = new LocalConnection();
            localConnection.send(
                parameters.connectionName, 
                parameters.methodName, 
                parameters.hash);
        }
    }
}

facebook.as class:

private var localConnection:LocalConnection;

override public function connect():void
{
	super.connect();
	
	if(!localConnection)
	{
		localConnection = new LocalConnection();
		localConnection.client = this;
		localConnection.allowDomain("*");
		localConnection.allowInsecureDomain("*");
		localConnection.connect("_facebookConnector");
	}
}

private function onAuthorized(event:FacebookOAuthGraphEvent):void
{
	// listener defined in Facebook constructor:
	// addEventListener(FacebookOAuthGraphEvent.AUTHORIZED, onAuthorized);
	
	try
	{
		localConnection.close();
		localConnection = null;
	}
	catch(error:Error){}
}

Getting All The Photos Of All Albums Of 1 User

Credits to Etienne, thnx

function getAllphotos(uid:String):void {
	var data:URLVariables = new URLVariables();
	data.query = "SELECT src_small, src_big " +
		"FROM photo " +
		"WHERE aid IN ( SELECT aid FROM album WHERE owner='"+HERE PUT THE USER ID+"' )"
	var loader:URLLoader = facebook.call("method/fql.query", data,URLRequestMethod.POST, 
		null, "https://api.facebook.com");
	loader.addEventListener(FacebookOAuthGraphEvent.DATA, onGetAllphotos);
}

Tagging photos

This script uses photos.addTag – part of old REST api (some more reading):

var data:URLVariables = new URLVariables();
data.pid = photoID;
data.tag_uid = userID; // user on photo we are tagging
data.x = 50; // 50% means center
data.y = 50; // 50% means middle
call("method/photos.addTag", data, URLRequestMethod.GET, null, "https://api.facebook.com");

Important: Graph API upload call returns the object_id. In order to get pid you have to lookup in photo table:

SELECT pid FROM photo WHERE object_id=response.id

Catching verify token error

In order to catch the ERROR event with verifyToken() method Nils suggested the following:

override public function verifyToken(token:String):URLLoader
{
    var loader:URLLoader = super.verifyToken(token);
    loader.addEventListener(FacebookOAuthGraphEvent.ERROR,
        function(event:FacebookOAuthGraphEvent):void
        {
            _authorized = false;
            _me = null;
            _token = null;
            EventDispatcher(event.currentTarget)
                .removeEventListener(event.type, arguments.callee);
            var type:String = FacebookOAuthGraphEvent.UNAUTHORIZED;
            dispatchEvent(new FacebookOAuthGraphEvent(type));
        });
    return loader;
}

133 comments so far

  1. Slavomir Durej December 1, 2010 11:26

    Hi Jozef,
    Great class, thanks for this, we’ve been using PHP solution for FB apps, but I think we’ll start using your solution from now on!
    Have one question though..
    It’s about autoconnect function.. It has a event callback : “AUTHORIZED” when the user get’s successfully authorised. Is there a callback when autoconnect couln’t authorise the user ?
    I’ve tried almost everything.. adding UNAUTHORIZED event to facebook instance, DATA event – none of the them get called on autoconnect fail..
    Also tried adding listeners to URLLoader instance returned from facebook.autoconnect (DATA, COMPLETE) no luck..
    Any idea how to solve this ?
    Thanks.

  2. Jozef Chúťka December 1, 2010 15:50

    Hi Slavo,
    I like to hear that you like it. I hope we can fix the issues now…

    there is an autoConnect() function that can take parameters (flashvars):
    – if parameters contains session and access_token the lib tries to verifyToken()
    – if it does not containt session lib uses token stored in sharedObject to verifyToken()

    verifyToken() creates URLLoader contacting facebook to see if the token is valid:
    – if the response is valid verifyTokenSuccess() is called dispatching AUTHORIZED on FacebookOAuthGraph instance
    – if the response is not valid there is no handler for this by default. you can extend verifyToken() function to receive error events over URLLoader…

    I am just reading that you are already able to handle events here (on autoConnect()), so what you have to do is, listen for HTTPStatusEvent and IOErrorEvent. I believe facebook responses with error when the authorization fail, that is why you failed with catching it like Event.COMPLETE

  3. Slavomir Durej December 1, 2010 23:38

    Jozef:
    IOErrorEvent.IO_ERROR did catch the autoconnect fail. Thanks for your help!

  4. Jerome December 7, 2010 13:57

    I try the crossdomain solution, but FlashPlayer always return an error:
    #2082: Connect failed because the object is already connected.

    PS :
    thanks for this great class !

  5. Jozef Chúťka December 8, 2010 14:29

    Jerome, you can not establish 2 LocalConnections with the same connectionName. make sure to close my blog or any other flash that may collide with your app

  6. PVieira December 9, 2010 17:03

    Hello again Jeff!

    Tell me something, we still can’t invite friends to use the application?
    If you can’t, is there any way to send a message, event or something similar to some id (writing the invitation there)?

    Cheers

  7. PVieira December 9, 2010 17:04

    When i say we can’t invite friends, i mean without using that standard FBML code.

  8. Jozef Chúťka December 10, 2010 18:32

    Hi PVieira,
    see http://blog.yoz.sk/2010/03/inviting-friends-into-facebook-application/ for inviting friends, there is no other option

  9. PVieira December 14, 2010 17:14

    Thanks Josef, i will try to use the light popup (i want to integrate this feature in a existing app).

  10. Garo December 15, 2010 21:51

    Hi Josef,

    I have a problem using the method video.upload, that parameter should not go to [no name]- data – The file for the video data. This is a ByteArray? I hope you can help as facebook returns me this error “error_code”: 7, “error_msg”: “This method must-run on api-video.facebook.com. If possible an example on using this method would be helpful. Thank you for your attention.

  11. Jozef Chúťka December 16, 2010 16:30

    Hi Garo,
    based on documentation you have to make video upload calls to http://api-video.facebook.com use MultipartURLLoader (see “Uploading Photo”) to insert bytes into request content

  12. MauroX December 16, 2010 18:31

    Hello,
    As Garo’m confused when trying to upload video to Facebook, this is my code:

    First call the method this way:
    var file: FileReference = FileReference (event.target);
    trace (file.dat is ByteArray);
    / / Video Uploader
    face.uploadVideoCall (file.data, “Video Test”);

    UploadVideoCall function is this:

    public function uploadVideoCall (bytes: ByteArray, message: String): void
    {
    trace (“Video UPLOADER”);
    var mpLoader: MultipartURLLoader MultipartURLLoader = new ();
    mpLoader.addEventListener (Event.COMPLETE, onMultiPartVideoLoader, false, 0, true);
    mpLoader.addVariable (“message”, message);
    mpLoader.addFile (bytes, “Video.flv”, “Test Video”);
    super.loaderAddListeners (mpLoader.loader)
    trace (“String GRAPH API:” + apiSecuredPath + “/ me / videos? access_token =” + _token)
    mpLoader.load (apiSecuredPath + “/ me / videos? access_token =” + _token)
    }
    onMultiPartVideoLoader private function (e: Event): void {
    trace (“Upload Sucefull)
    }

    The error I get is:

    Error # 2044: uncontrolled ioError: text = Error # 2032: Error sequence. URL:
    https: / / graph.facebook.com / me / videos?
    access_token = 127807137233234 | 2.gDyuwi_sPsqbg2QBkgE6tg__.86400.1292518800-
    1061543699 | U7XoBuXfxiDo0xhONxVQzpoCsxU

    Josef could tell me where I am failing to solve this problem.

  13. MauroX December 16, 2010 18:39

    In this case, that part is not using the method “video.upload” because in this way was like I got a facebook pictures and it works, I think the method used in this case is this http://developers. facebook.com / docs / reference / api / video, seeing the image works with this http://developers.facebook.com/docs/reference/api/photo honestly I feel very lost on this problem I hope suggestions.

  14. Garo December 16, 2010 19:20

    Hahahaha, MauroX I did this:

    var file: FileReference = FileReference (event.target);
    face.uploaderVideo (face.uploadVideo (file.data, “Video”));

    Wherein the method UploaderVideo:

    uploaderVideo public function (data: MultipartURLLoader): void {

    trace (“Data:” + data);
    var data: URLVariables URLVariables = new ();
    data.dat = data;
    data.title = “Video Title”;
    data.description = “Video Description”;
    var loader: URLLoader = super.call (“method / video.upload”, data, URLRequestMethod.GET, null, “http://api-video.facebook.com”);
    loader.addEventListener (FacebookOAuthGraphEvent.DATA, videoComplete, false, 0, true);
    }
    videoComplete private function (event: FacebookOAuthGraphEvent): void {
    trace (“Video Complete”);
    }
    This is the Method of Josef Upload Image modified to Video:

    public function uploadVideo (bytes: ByteArray, message: String): MultipartURLLoader
    {
    var mpLoader: MultipartURLLoader MultipartURLLoader = new ();
    mpLoader.addVariable (“message”, message);
    mpLoader.addFile (bytes, “videos.flv” “videos”);
    super.loaderAddListeners (mpLoader.loader)
    mpLoader.load (apiSecuredPath + “/ me / videos? access_token =” + _token)
    mpLoader return;
    }

    And I came out the following error:

    Security Sandbox Error.

  15. MauroX December 16, 2010 21:43

    Well, I managed to repair the problem that i had to type IOError, Facebook has managed to answer me upload the video to Facebook, but
    review and did not. And started to lose hope jajajajaja.

  16. MauroX December 17, 2010 00:31

    Hi Josef,

    I think here I come … This code is supposed to upload a video to Facebook,

    //Call Video Graph API
    public function uploadVideoCall(bytes:ByteArray, message:String):void
    {
    trace(“Video UPLOADER”);
    var mpLoader:MultipartURLLoader = new MultipartURLLoader();
    mpLoader.addEventListener(Event.COMPLETE,onMultiPartVideoLoader,false,0,true);
    mpLoader.addEventListener(IOErrorEvent.IO_ERROR,onMultiPartVideoLoader,false,0,true);
    mpLoader.addEventListener(ProgressEvent.PROGRESS,onMultiPartVideoLoaderProgress,false,0,true);
    mpLoader.addVariable(“message”, message);
    mpLoader.addFile(bytes, “Video.flv”, “Test Video”);
    super.loaderAddListeners(mpLoader.loader);
    mpLoader.load(apiSecuredPath + “/me/videos?access_token=”+ _token);
    }
    private function onMultiPartVideoLoader(e:Event):void{
    trace(“Sucefull Upload: “+e.currentTarget);
    }
    private function onMultiPartVideoLoaderError(e:IOErrorEvent):void{
    trace(“IO ERROR: “+e.currentTarget);
    }
    private function onMultiPartVideoLoaderProgress(event:ProgressEvent):void{
    trace(“\n” + event.bytesLoaded + ” bytes de ” + event.bytesTotal + ” bytes subidos.”);
    }
    ///////////////////////////////////////////////////////////////////////
    the problem is that I receive Uploaded response, but does not really. I think the problem is due to deliver a missing parameter type, but honestly not which is (I tried uploading a picture and works perfectly using this code and only changing mpLoader.load (apiSecuredPath “/ me / photos? access_token =” _token)) I have read and found that it is possible that this method or called in facebook is bad, I like me’d say if it’s true or not the video upload is still not well done on facebook. I await your response and I apologize for so much spam

  17. Jozef Chúťka December 17, 2010 12:13

    MauroX, Garo hi guys
    I really think you should use old rest api method for now for this. read
    http://developers.facebook.com/docs/reference/rest/video.upload
    what you should do is to use the proper arguments with the request resulting in something like this:
    mpLoader.load(“http://api-video.facebook.com/method/video.upload”);

  18. MauroX December 17, 2010 16:55

    I could not remove this error, any ideas?

    *** Security Sandbox Violation ***
    Has stopped connecting to http://api-video.facebook.com/method/video.upload
    – not permitted from
    http://www.quatiodevelopments.com/TestFacebook/FacebookMauroX.swf
    Security Error: Error # 2048: Security Sandbox Violation:
    http://www.quatiodevelopments.com/TestFacebook/FacebookMauroX.swf can not
    load data from http://api-video.facebook.com/method/video.upload.

  19. MauroX December 17, 2010 23:21

    Hi Josef,

    I tried everything:

    This is my Cross Domain:

    Also I’ve put

    Security.loadPolicyFile (“https://graph.facebook.com/crossdomain.xml”);

    but nothing … this is what I’ve read, but I failed to take this error:

    http://www.adobe.com/devnet/flashplayer/articles/fplayer9_security.html # _Findi
    ng_and_Fixing

    http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/common/html/wwhelp.htm
    ? context = LiveDocs_Parts & file = 00000349.html

    http://www.adobe.com/devnet/flashplayer/articles/fplayer9_security.html

    if you got any suggestions, I appreciate.

  20. Jozef Chúťka December 20, 2010 10:51

    MauroX, it really seems like there is an restrictive crossdomain on:
    http://api-video.facebook.com/crossdomain.xml
    so you will only be able to test it when running debug build from your local… what you should do is to try everything works there, and than make server side proxy using php or something you are more familiar with:
    http://efreedom.com/Question/1-3257729/Uploading-Video-Old-Facebook-REST-API

  21. PVieira December 20, 2010 13:35

    Hi again Jeff.

    I’m thinking about this method to post to wall and i was wondering: it’s only possible to post to the current user wall? Is not possible to post to his wall and to some page (for example the app profile page)?

    Cheers!

  22. Jozef Chúťka December 20, 2010 13:38

    Hi PVieira,
    check out
    http://blog.yoz.sk/2010/06/extending-facebookoauthgraph-class/#comment-2336
    and other material over this article with “target_id”

  23. Ben January 8, 2011 23:31

    Hello Jozef

    Thanks for your great blog!
    Here is my problem :

    The html page that wraps the swf is in domainA.com and declared in the facebook app, but the swf is in domainB.com (caching server, cdn with transparent redirection). When I try to connect to facebook, the auhorisation fails. Any idea?

    P.S. When the html and the swf are in the same domain, everything worksfine…

    Thanks for your help!

  24. Ben January 8, 2011 23:37
  25. Jozef Chúťka January 10, 2011 14:49

    @Ben,
    I can see you defined .swf script access for crossdomain for both .swf files, as well as localconnection name with underscore correctly. Popup opens and closes by itself (however I am not able to find close command in javascript – maybe injected from callback.swf?) … I can also see the redirect from facebook to
    http://www.monstromatic.com/bin/callback.html#access_token=***********&expires_in=4560
    so window.location.hash should contain correct info… I believe there is some issue with handling hash value in your flash. You should debug that

  26. mike January 15, 2011 13:58

    Hi – I’m able to authorize in my flash only app using your great code – dumb question tho – I have this return object where I can get first name , last name like so

    var obj:Object = event.currentTarget.me as Object;
    _firstname = obj.first_name;
    _lastname = obj.last_name;

    I’m confused about how to get access to the profile pic of the user tho – when i try the callComplete portion of the code – a status update gets posted but the result is event.rawdata is not recognized as a ByteArray – any idea what I might be doing wrong?

    Cheers

  27. Jozef Chúťka January 17, 2011 18:39

    mike, you can not access ByteArray without correct crossdomain.xml hosted on facebook domains, please follow:
    http://blog.yoz.sk/2010/05/facebook-graph-api-and-oauth-2-and-flash/#comment-2450 or other comments and responses containing “profile picture”

  28. Gopi January 20, 2011 10:32

    Hi Jozef Chúťka,

    I could post on authenticated user’s friends wall successfully only when the pop-up window (normal callback.html) closed automatically. Firefox and crome browsers are able to close the callback.html after authentication, but Internet explorer unable to close the callback.html automatically.

    please suggest !
    I would also like to know the purpose of Advanced callback.html

    -Gopi

  29. Jozef Chúťka January 20, 2011 18:40

    Hi Gopi,
    In order to make calls to api you need a token. When you authorize your app in popup, the token is pushed in the last step into callback.html. Now callback.html hast to push it back to your main app (via javascript-simple or LocalConnection-advanced callback). It it succeed with sending the token to the main app, it tries to close itself using javascript window.close() method. Could you somehow debug the issue with IE and send some report where the javascript fails?

  30. Vaishali Saxena June 13, 2012 12:06

    Hi, Jozef..
    Please, i used the photo uploading code through the graph API but i don’t know how i used it in my project..
    can you send me the step by step code for this with file names in which i have to update the code..

  31. Jozef Chúťka June 13, 2012 12:59

    hi Vaishali
    have a look at “Uploading Photo” section. copy and paste the conde into your Facebook class (extends FacebookOAuthGraph). you may be missing MultipartURLLoader dependency, you can download it from internet. everything else should be selfexplanatory

  32. Ronen Leipziger July 19, 2012 21:54

    Hi Jozef
    Great Job. Tanx. When I try to Publishing Feeds, all data is posted beside the image. No image is posted. I followed your code “as is”. Please advice.

  33. Jozef Chúťka July 26, 2012 11:31

    Hi Ronen,
    It may be caused by deprication of the rest api stream.publish ( https://developers.facebook.com/docs/reference/rest/stream.publish/ ) is replaced by graph api /feed … read section “Publishing feed using Graph API (docs, from facebook bugzilla):” in this article