Vixiom Axioms

January 10, 2008

Log into Facebook with Adobe AIR (without leaving the desktop)

Filed under: AIR, ActionScript, Facebook, Flex Alastair @ 9:40 am

I’ve been messing around with the Facebook API and one of the things I don’t like about the desktop version is that you still have to authorize an app through a browser. I know you only have to do this the first time but what’s the point of a desktop application if you have to open a browser? Here’s how to use AIR’s HTML control to login to Facebook without leaving the safe confines of your desktop. The example will log into facebook and grab the most recent noifications for the logged in user - that’d be you :)

If your making a Facebook app you’ll need the developer application added to your profile and have generated application keys. If this is all new to you see this page on how to get started. Once you have the developer application with a new app click on ‘My Applications’

And take note of your API Key, Secrect Key, and set your Callback URL (see note on Callback URL below).

On a regular facebook app the Callback URL would be the first page that shows up once you login but for our purposes it doesn’t really matter where it is or what it does, I have a simple PHP file that just outputs the returned auth_token variable.

<?php

$auth_token = $_GET[auth_token];
print_r($auth_token);

?>

But that’s was really just for when I was testing before moving to AIR it could be a blank HTML file. The last Facebook setting to check is to set ‘Application Type’ to ‘Desktop’

With your API Key, Secret Key noted download the source files for this tutorial and create a new AIR project. The source files include two Adobe libraries the as3facebooklib and elements from the core library as3corelib (which has tons of other useful stuff). In the file ’src/com/desktopfb/model/Model.as’ change ‘api_key’ and ’secret_key’ to your values. The model is a Singleton with some hactackstic exposed public variables - I went to art school so I don’t know any better ;)

package com.desktopfb.model
{
    import com.adobe.webapis.facebook.FacebookService;
    import com.adobe.webapis.facebook.User;

    [Bindable]
    public class Model
    {
        private static var instance:Model;

        // state
        public var appState:String = undefined;

        // service
        public var service:FacebookService;

        public var api_key:String = “YOUR API KEY HERE”;
        public var secrect_key:String = “YOUR SECRET KEY HERE”;
        public var auth_token:String = “”;

        // main user uid
        public var loggedInUser_uid:String;

        // main user
        public var loggedInUser:User;

        //////////////////////////////////////////////////////////////////////
        //
        // instance
        //
        //////////////////////////////////////////////////////////////////////

        public static function getInstance():Model
        {
            if ( instance == null ) instance = new Model();
            return instance as Model;
        }
    }
}

With those two changes made you can run the application, but… I’ll explain what’s going on. The main application file ’src/DesktopFB.mxml’ is the WindowedApplication wrapper, it’s the entry to the app and really only handles state (which is bound to the model’s ‘appState’ var). There are only two states the login view and the home view.

<?xml version=1.0 encoding=utf-8?>
<mx:WindowedApplication
    xmlns:mx=http://www.adobe.com/2006/mxml
    xmlns:view=com.desktopfb.view.*
    layout=vertical width=1024 height=768
    currentState=“{model.appState}”>

    <mx:states>
        <mx:State name=loggedIn>
            <mx:RemoveChild target=“{loginView}”/>
            <mx:AddChild>
                <view:HomeView />
            </mx:AddChild>
        </mx:State>
    </mx:states>

    <mx:Style source=css/app.css />

    <mx:Script>
        <![CDATA[
            import com.desktopfb.model.Model;

            [Bindable]
            private var model:Model = Model.getInstance();

        ]]>
    </mx:Script>

    <view:LoginView id=loginView />

</mx:WindowedApplication>

Next up the login view ’src/com/desktopfb/view/LoginView.mxml’. It guides us through the fairly complicated task of authorizing a Facebook user. Stepping throught the methods

  1. onCreationComplete(): fires right away and checks for a sharedObject (an ActionScript session/cookie), if it finds one it sets the appState var to ‘loggedIn’ and since it’s bound to the app’s current state will automatically take the suer to the home view. If it doesn’t find a sharedObject it starts the login sequence.
  2. startLoginSequence(): takes your keys packages them up in a service (including md5′ing them behind the scenes) and listens for a response ‘service.addEventListener( FacebookResultEvent.AUTH_CREATE_TOKEN, createTokenResponse )‘. If you’re new to web services in Flex/AIR that’s the routine; create a service, then map a method to the service via an event listener.
  3. createTokenResponse(): Sends the HTML control to the facebook login page with your API key appended (http://www.facebook.com/login.php?api_key=YOUR_KEY&v=1.0)
  4. the HTML control is set up to listen for location change events (locationChange = "onLocationChange()"), and once a user logs into your app in Facebook it redirects your to the CallbackURL, as the HTML has redirected the ‘locationChange’ event fires and because the URL is appended with ‘auth_token’ the ‘location.search(pattern)’ will match, the auth_token is saved to the model and then we go to the final set of authorizing a Facebook user getting a session key (logging into Facebook is not for the faint of heart!)
  5. getSession(): takes your auth_token and sends it to Facebook so they really really really know it’s you, it’s listener is ‘getSessionResponse
  6. getSessionResponse(): finally done! we have a session_key - it’s worth more than it’s weight in gold - save it to a shared object so we never have to go through this again. The app’s current state is set to ‘loggedIn’ and you switch to the home view.
<?xml version=1.0 encoding=utf-8?>
<mx:VBox xmlns:mx=http://www.adobe.com/2006/mxml
    width=100% height=100% horizontalAlign=center verticalAlign=top
    creationComplete=onCreationComplete()>

    <mx:Script>
        <![CDATA[
            import com.adobe.webapis.facebook.*;
            import com.adobe.webapis.facebook.events.FacebookResultEvent;

            import mx.managers.CursorManager;
            import mx.core.UIComponent;
            import com.desktopfb.model.Model;
            import flash.display.Sprite;
            import flash.html.HTMLLoader;
            import flash.net.URLRequest;

            private var service:FacebookService;
            private var model:Model = Model.getInstance();
            private var sharedObject:SharedObject;

            /**
             * onCreationComplete
             *
             * check sharedObject for a saved session key
             *
             */

            private function onCreationComplete():void
            {
                trace(“login view”);
                var facebookCookie:SharedObject = SharedObject.getLocal( “FacebookServiceTest” );

                // check ‘cookie’

                delete facebookCookie.data.session_key; // uncomment to clear cookie and force login

                if ( facebookCookie.data.session_key )
                {
                    trace(“Found stored auth token…”);

                    // Assign the token and permission to the service
                    service = new FacebookService( facebookCookie.data.api_key );
                    model.service = service;
                    service.session_key = facebookCookie.data.session_key;
                    service.secret = facebookCookie.data.secret;

                    // set app state as home
                    model.appState = “loggedIn”;
                }
                else
                {
                    startLoginSequence();
                }
            }

            /**
             * startLoginSequence
             *
             * send api and secret to Facebook to get auth_token
             *
             */

            public function startLoginSequence():void
            {
                service = new FacebookService( model.api_key );
                model.service = service;

                service.secret = model.secrect_key;

                service.addEventListener( FacebookResultEvent.AUTH_CREATE_TOKEN, createTokenResponse );
                service.auth.createToken();
            }

            /**
             * Create token response
             *
             * load auth_url in htmlWindow
             *
             */

            private function createTokenResponse( event:FacebookResultEvent ):void
            {
                trace(“createTokenResponse: success = “ + event.success);

                if ( event.success )
                {
                    // Have the service construct a login url
                    var auth_token:String = String( event.data );
                    var auth_url:String = service.getLoginURL( auth_token );

                    trace(auth_url);

                    // Open a htmlControl window to authenticate the user
                    htmlWindow.location = auth_url;
                    htmlWindow.reload();
                }
                else
                {
                    // error
                    traceError( FacebookError( event.data ) );
                }
            }

            /**
             * getSession
             *
             * After auth is returned get session
             *
             */
            private function getSession():void {

                // Get their authentication token, and call getTokenResponse
                service.addEventListener( FacebookResultEvent.AUTH_GET_SESSION, getSessionResponse );
                service.auth.getSession( model.auth_token );
            }

            /**
             * getSessionResponse
             *
             * last step of user authorization, save the facebook cookie
             *
             */

            private function getSessionResponse( event:FacebookResultEvent ):void
            {
                if ( event.success )
                {
                    var authSession:AuthSession = AuthSession( event.data );

                    // Assign the token and permission to the service for future calls
                    service.session_key = authSession.session_key;
                    service.secret = authSession.secret;

                    // Save the token in a shared object for future logins
                    var facebookCookie:SharedObject = SharedObject.getLocal( “FacebookServiceTest” );
                    facebookCookie.data.api_key = service.api_key;
                    facebookCookie.data.session_key = service.session_key;
                    facebookCookie.data.secret = service.secret;
                    facebookCookie.flush();

                    // set app state as home
                    model.appState = “loggedIn”;
                    model.loggedInUser_uid = authSession.uid;

                    // spit out authSession data
                    trace(“uid: “ + authSession.uid);
                    trace(“expires: “ + authSession.expires);
                    trace(“secret: “ + authSession.secret);
                    trace(“session_key: “ + authSession.session_key);
                }
                else
                {
                    // error
                    traceError( FacebookError( event.data ) );
                }
            }

            /**
             * onLocationChange
             *
             * watch and react to location changes
             *
             */

            private function onLocationChange():void
            {
                trace(“change”);
                var location:String = htmlWindow.location;
                trace(location);
                var pattern:RegExp = /auth_token/i;

                if (location.search(pattern) != -1)
                {
                    var locationArray:Array = location.split(“=”);

                    // save auth_token in model
                    model.auth_token = locationArray[1];
                    trace(model.auth_token);
                    getSession();
                }
            }

            /**
             * onComplete
             *
             * hide busy cursor when html finishes rendering
             *
             */

            private function onComplete():void
            {
                CursorManager.removeBusyCursor();
            }

            /**
             * domIni
             *
             * show busy cursor on html call
             *
             */

            private function domIni():void
            {
                CursorManager.setBusyCursor();
            }

            /**
             * traceError
             *
             * trace any errors to the console
             *
             */

            private function traceError( e:FacebookError ):void
            {
                trace(“error code: “ + e.errorCode);
                trace(“error message: “ + e.errorMessage);
            }

        ]]>
    </mx:Script>

    <mx:HTML id=htmlWindow width=800 height=400
    horizontalScrollPolicy=off verticalScrollPolicy=off
    locationChange=onLocationChange() complete=onComplete() htmlDOMInitialize=domIni()/>

</mx:VBox>

The home view. This is far less complicated than the login view, hopefully the comments let you know what’s going on, it’s just call and response (this one’s for you Tomo) for the user’s info and their notifications (which are outputted to a text area).

<?xml version=1.0 encoding=utf-8?>
<mx:HBox xmlns:mx=http://www.adobe.com/2006/mxml width=100% height=100% creationComplete=onCreationComplete()>

    <mx:Script>
        <![CDATA[
            import com.adobe.webapis.facebook.Notification;
            import com.adobe.webapis.facebook.methodgroups.Notifications;
            import mx.managers.CursorManager;
            import com.adobe.webapis.facebook.User;
            import com.adobe.webapis.facebook.FacebookError;
            import com.adobe.webapis.facebook.FacebookService;
            import com.adobe.webapis.facebook.events.FacebookResultEvent;
            import mx.utils.ObjectUtil;
            import com.desktopfb.model.Model;

            private var model:Model = Model.getInstance();
            private var service:FacebookService;

            /**
             * onCreationComplete
             *
             * get the logged in user
             *
             */

            private function onCreationComplete():void
            {
                service = model.service;
                service.addEventListener( FacebookResultEvent.USERS_GET_LOGGED_IN_USER, getLogedInUserResponse );
                service.users.getLoggedInUser();
            }