Saturday, June 25, 2011

Deconstructing Facebook's Flash Cross Domain Handler in IE..

I got tired of trying to understand why Facebook kept breaking on their IE support, so decided to peer directly into how they use Flash to implement the cross-domain receiver (you can see inside the .js file they supply that it fetches the SWF file http://static.ak.fbcdn.net/rsrc.php/v1/yx/r/WFg56j28XFs.swf I now suspect the problems Fri. morning that other people reported had to do with 64-bit Internet Explorer, since the Adobe Flash version is still highly experimental, and rebooting my machine (and my other machines at work had no problems) solved the issue.

Nonetheless, the hard part was to find a decompiler that could convert the SWF file that gets loaded in Internet Explorer back into bytecode.  I tried to use flasm but this file was encoded using Adobe Flex 4, which was released around March 2010 so the open source tools are a bit scant.   I ended up using an unregistered version of Sothink's SWFDecompiler, which wants $70.00 for a license.  An older 6.0 version seems to have some quirky bugs where it actually lets you export the ActionScript files back to the original format.  Regardless, the program preserved all the original variable names, so you can pretty much have the original format, and I found a way to post the decompiled files in their entirety here:
http://hustoknow.blogspot.com/2011/06/facebooks-flash-xdcomm-receiver.html

The way in which Facebook has implemented cross-domain message passing in Internet Explorer mimics the use of the HTML5 postMessage.  Instead of creating an event sender/listener for messages from a different window to pass, Facebook relies on SWF objects to talk with other in Internet Explorer. In Flash, there is the concept of LocalConnection objects, where SWF objects can talk with other even when they are on different domains.   It injects this SWF file into both your site as well as after you login through the window popup (facebook.com).

<object type="application/x-shockwave-flash" id="XdComm" name="XdComm" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" allowscriptaccess="always"><param name="movie" value="https://s-static.ak.fbcdn.net/rsrc.php/v1/yx/r/WFg56j28XFs.swf"></param><param name="allowscriptaccess" value="always"></param></object>

So when you go to any Facebook Connect-enabled web site, you instantiate a SWF object that creates a XdComm object, along with an extra postMessage object.   The XdComm class holds a PostMessage() object, which in turns instantiates a LocalConnection() object, which is basically becomes a port sender/receiver for messages.  Adobe SWF objects, which are contained within ActiveX can be accessed via JavaScript, so the object tag gets added with the XdComm ID and Facebook's JavaScript code can access the postMessage_init() function, which is initialized in the SWF file.

   public function PostMessage()
        {
            this.connection = new LocalConnection();
            this.connection.client = this;
            this.connection.connect(Math.random().toString());
            ExternalInterface.addCallback("postMessage_init", this.init);
            ExternalInterface.addCallback("postMessage_send", this.send);
            return;
        }// end function

When you do an FB.init() on  your localhost, it instantiates FB.XD._openerOrigin (i.e. http://dev.myhost.com/ + 16-character random string),  which is used to define the connection name that SWF objects will be use to communicate.   The FB.XD_openerOrign was the key to the problems this past week -- Facebook was messing around with stuff for IE8, and broke the connection name that the SWF objects would agree on listening.   They later fixed it based on the reviews of the diff I examined.   When you type your login and password, the URL that is used when you submit goes to http://static.ak.fbcdn.net/connect/xd_proxy.php, along with the origin= query string parameter.    So origin must be the same on the window popup connecting to facebook.com as the original one that was initialized on your own Facebook Connect-enabled site.

  } else if (f.transport == 'flash') {
                    var d = window.location.toString() + Math.random();
                    window.FB_OnFlashXdCommReady = function() {
                        document.XdComm.postMessage_init('dummy', d);
                        document.XdComm.postMessage_send(a, f.origin);
                    };
                    createXdCommSwf();
                }


In addition to setting up the origin= query parameter, the FB.init() functions also sets up 3 separate callback functions: ok_session, cancel_session, and no_user, which are instantiated into a callback dictionary each accessible by  a random 16-character signature.   So if you've ever wondered why the query string on the Facebook login is so long, it's partially because of all the info they encode to handle the results of different authentications.  Regardless, the message that gets passed includes a bunch of different URL-encoded string, but one of them is a cb= parameter, which specifies which callback function to run.

Once that callback function is run, then the FB JavaScript calls FB.Auth.setSession() and fires off events...but the message between facebook.com and your site has been successfully exchanged...all via SWF objects in Internet Explorer!

Initial investigations of the XD.Flash...

One of the first things the Facebook Connect library does when a user on Internet Explorer clicks the "Facebook Connect" is to insert this Adobe Flash file:

<object type="application/x-shockwave-flash" id="XdComm" name="XdComm" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" allowscriptaccess="always"><param name="movie" value="https://s-static.ak.fbcdn.net/rsrc.php/v1/yx/r/WFg56j28XFs.swf"></param><param name="allowscriptaccess" value="always"></param></object>

(The classid refers to the ActiveX object of the Adobe Flash player, which apparently remains constant. The allowscriptaccess option apparently allows outbound URL requests.)

Within the Facebook Connect all.js file, there is a line that refers to initializing the XdComm object. It passes in two parts, one the actual name of the callback handler, and the second is the connection name on which to listen for messages. For messages to be received, the connection name must be the same.

document.XdComm.postMessage_init('FB.XD.Flash.onMessage', FB.XD._openerOrigin ? FB.XD._openerOrigin : FB.XD._origin);

When you login to Facebook Connect, you're POST'ng to xd_proxy.php, which also loads a SWF file (assuming transport=flash) and sets the origin to the one provided in the origin=. By doing so, both the window that was opened as well as the original document can now communicate with each other. The xd_proxy.php if you were to download it looks like:
<!doctype html>
<html>
    
    <head>
        <title>
            XD Proxy
        </title>
    </head>
    
    <body onload="doFragmentSend()">
        <div id="swf_holder" style="position: absolute; top: -10000px; width: 1px; height: 1px">
        </div>
        <script>
            var XdCommUrl = "http:\/\/static.ak.fbcdn.net\/rsrc.php\/v1\/yx\/r\/WFg56j28XFs.swf";
            var AllowFbCom = false;

            function resolveRelation(b) {
                var g, d, f = b.split('.'),
                    e = window;
                for (var a = 0, c = f.length; a < c; a++) {
                    g = f[a];
                    if (g === 'opener' || g === 'parent' || g === 'top') {
                        e = e[g];
                    } else if (d = /^frames\[['"]?([a-zA-Z0-9-_]+)['"]?\]$/.exec(g)) {
                        e = e.frames[d[1]];
                    } else throw new SyntaxError('Malformed id to resolve: ' + b + ', pt: ' + g);
                }
                return e;
            }
            function createXdCommSwf() {
                var a = !! document.attachEvent,
                    c = /(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))/.exec(navigator.userAgent),
                    b = (c[1] && (parseFloat(c[1]) >= 9)),
                    e = 'XdComm' + (b ? (Math.random() * (1 << 30)).toString(16).replace('.', '') : ''),
                    d = ('<object ' + 'type="application/x-shockwave-flash" ' + 'id="' + e + '" ' + (a ? 'name="XdComm" ' : '') + (a ? '' : 'data="' + XdCommUrl + '" ') + (a ? 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ' : '') + 'allowscriptaccess="always">' + '<param name="movie" value="' + XdCommUrl + '"></param>' + '<param name="allowscriptaccess" value="always"></param>' + '</object>');
                if (b) window.attachEvent('onunload', function() {
                    document.getElementById(e).removeNode(true);
                });
                document.getElementById('swf_holder').innerHTML = d;
            }
            function doFragmentSend() {
                if (!AllowFbCom && /(^|\.)facebook.com$/.test(document.domain.toString())) return;
                var c = window.location.toString(),
                    a = c.substr(c.indexOf('#') + 1),
                    f = {},
                    g = a.split('&'),
                    b, e;
                for (b = 0; b < g.length; b++) {
                    e = g[b].split('=', 2);
                    f[decodeURIComponent(e[0])] = decodeURIComponent(e[1]);
                }
                if (f.transport == 'postmessage') {
                    var h = resolveRelation(f.relation);
                    if (h) {
                        h.postMessage(a, f.origin);
                    } else if (f.relation == 'opener') {
                        var i = 0;
                        i = setInterval(function() {
                            if (window.fbrpc) {
                                clearInterval(i);
                                if (fbrpc.postMessage) {
                                    fbrpc.postMessage(a, f.origin);
                                } else fbrpc.call('postMessage', JSON.stringify({
                                    target: f.relation,
                                    message: a
                                }));
                            }
                        }, 10);
                    }
                } else if (f.transport == 'flash') {
                    var d = window.location.toString() + Math.random();
                    window.FB_OnFlashXdCommReady = function() {
                        document.XdComm.postMessage_init('dummy', d);
                        document.XdComm.postMessage_send(a, f.origin);
                    };
                    createXdCommSwf();
                }
            }
        </script>
    </body>

</html>    

Remember in the previous XdComm file that the postMessage_init() and postMessae_send() were added as registered callbacks. In ActionScript, you can invoke commands directly in JavaScript by calling them as if there were functions that are part of the Active-X module. The loading of the 'dummy' and postMessage_send() gets sent to the LocalConnection() origin name, which your original page is also listening. In this way, this message will be received by the onMessage handler with the Flash object of the all.js library, which is URI decoded before passing back so that the query string to be decoded and the callback specified in the cb= is provided.
Flash: {
        init: function() {
            FB.Flash.onReady(function() {
                document.XdComm.postMessage_init('FB.XD.Flash.onMessage', FB.XD._openerOrigin ? FB.XD._openerOrigin : FB.XD._origin);
            });
        },
        onMessage: function(a) {
            FB.XD.recv(decodeURIComponent(a));
        }
    },
(Also, there is also a postMessage included in the code, but presumably if not event listener is added, it will be ignored by Internet Explorer): FYI -- The old REST API relied on using the oauthRequest() code to sendXdHttpRequest() to make the external Facebook request via the FB.ApiServer.flash() command, which makes the external request, but this section of the code seems deprecated. When the request is finished, the XdCom library creates an event on FB_OnXdHttpResult using the ExternalInterfaceclass that communicates back to the JavaScript/HTML container.
oauthRequest: function(b, f, c, e, a) {
        try {
            FB.ApiServer.jsonp(b, f, c, FB.JSON.flatten(e), a);
        } catch (g) {
            if (FB.Flash.hasMinVersion()) {
                FB.ApiServer.flash(b, f, c, FB.JSON.flatten(e), a);
            } else throw new Error('Flash is required for this API call.');
        }
See the http://hustoknow.blogspot.com/2011/06/facebooks-flash-xdcomm-receiver.html for an examination on the Flash XDComm .swf file decompiled....

Friday, June 24, 2011

Decompiling Facebook's Flash XDComm Receiver..

Within Facebook's all.js file, there is a reference to a _swfPath that refers to http://static.ak.fbcdn.net/rsrc.php/v1/yx/r/WFg56j28XFs.swf. It's used for Internet Explorer installed with Flash to handle cross-domain requests. Ever wonder how it works? Well, you can do a wget and decompile it.

Decompiling the XDComm.swf file: None of the open source Flash decompilers work to decompile Facebook's XDComm Receiver, since it's compiled in Adobe Flex v4 (released apparently in March 2010). You can use the Sothink SWF Decompiler tool, but without paying $70.00, the program doesn't even allow you to export the files to ActionScript. Nonetheless, the unregistered version (as of the 6.00 version) limits you from decrypting more than the first two resources, but there's enough in the tool that allows you to save parts of the code.

Here are flashutils\PostMessage.as and XdComm.as. Hopefully it gives you a better idea of how the Flash cross-domain receiver works.

The files can also be downloaded from here:

http://bit.ly/kAJ7AJ
http://bit.ly/ltGkTF

Apparently the XdComm utilizes the LocalConnection library to communicate with the HTML page. It used the send/receive functionality and the FB.XD._origin variable to determine what the connection name should be used.

In http://hustoknow.blogspot.com/2011/06/how-flash-xdcomm-works-in-facebook.html, we'll walk through in more detail how the Flash XDComm Receiver works with Facebook.

XdComm.as:

package 
{
    import flash.display.*;
    import flash.events.*;
    import flash.external.*;
    import flash.net.*;
    import flash.system.*;
    import flashutils.*;

    public class XdComm extends Sprite
    {
        private var _cache:SharedObject;
        private var _cacheContext:String = "unknown";
        private var postMessage:PostMessage;
        private static var requestIdCount:int = 0;

        public function XdComm()
        {
            XdComm.fbTrace("XdComm Initialized", {});
            Security.allowDomain("*");
            Security.allowInsecureDomain("*");
            this.addEventListener(Event.ENTER_FRAME, this.init);
            return;
        }// end function

        private function init(event:Event) : void
        {
            XdComm.fbTrace("XdComm.init", {});
            this.removeEventListener(Event.ENTER_FRAME, this.init);
            this.postMessage = new PostMessage();
            ExternalInterface.addCallback("sendXdHttpRequest", this.sendXdHttpRequest);
            ExternalInterface.addCallback("setCache", this.setCache);
            ExternalInterface.addCallback("getCache", this.getCache);
            ExternalInterface.addCallback("setCacheContext", this.setCacheContext);
            ExternalInterface.addCallback("clearAllCache", this.clearAllCache);
            ExternalInterface.call("FB_OnFlashXdCommReady");
            return;
        }// end function

        public function sendXdHttpRequest(param1:String, param2:String, param3:String, param4) : int
        {
            var loader:URLLoader;
            var reqId:int;
            var loaded:Function;
            var key:String;
            var value:String;
            var method:* = param1;
            var url:* = param2;
            var requestBody:* = param3;
            var extraHeaders:* = param4;
            loaded = function (event:Event) : void
            {
                var _loc_2:* = loader.data.toString();
                XdComm.fbTrace("Requested completed", {data:_loc_2});
                ExternalInterface.call("FB_OnXdHttpResult", reqId, encodeURIComponent(_loc_2));
                return;
            }// end function
            ;
            XdComm.fbTrace("SendXdHttpRequest", {method:method, url:url, requestBody:requestBody, extraHeaders:extraHeaders});
            if (url.indexOf("https://") != 0 && url.indexOf("http://") != 0)
            {
                url = "http://" + url;
            }
            var host:* = PostMessage.extractDomain(url);
            if (host != "api.facebook.com" && host != "graph.facebook.com" && !/^(api|graph)\.[A-Za-z0-9\-\.]+\.facebook\.com$""^(api|graph)\.[A-Za-z0-9\-\.]+\.facebook\.com$/.test(host))
            {
                return 0;
            }
            var _loc_6:* = XdComm;
            var _loc_7:* = XdComm.requestIdCount + 1;
            _loc_6.requestIdCount = _loc_7;
            var req:* = new URLRequest(url);
            loader = new URLLoader();
            reqId = XdComm.requestIdCount;
            req.method = method;
            req.data = requestBody;
            if (extraHeaders != null)
            {
                var _loc_6:int = 0;
                var _loc_7:* = extraHeaders;
                while (_loc_7 in _loc_6)
                {
                    
                    key = _loc_7[_loc_6];
                    value = extraHeaders[key];
                    req.requestHeaders.push(new URLRequestHeader(key, value));
                }
            }
            loader.addEventListener(Event.COMPLETE, loaded);
            loader.load(req);
            return reqId;
        }// end function

        private function setCacheContext(param1:String) : void
        {
            if (param1 == null)
            {
                param1 = "unknown";
            }
            this._cacheContext = param1;
            return;
        }// end function

        private function clearAllCache() : void
        {
            this.cache.clear();
            this.cache.flush();
            return;
        }// end function

        private function getCache(param1:String) : String
        {
            return this.contextCache[param1];
        }// end function

        private function setCache(param1:String, param2:String) : void
        {
            var _loc_3:* = this.contextCache;
            _loc_3[param1] = param2;
            this.cache.flush();
            return;
        }// end function

        private function get cache() : SharedObject
        {
            if (this._cache == null)
            {
                this._cache = SharedObject.getLocal("cache");
            }
            return this._cache;
        }// end function

        private function get contextCache() : Object
        {
            var _loc_1:* = this.cache.data[this._cacheContext];
            if (_loc_1 == null)
            {
                _loc_1 = new Object();
                this.cache.data[this._cacheContext] = _loc_1;
            }
            return _loc_1;
        }// end function

        public static function traceObject(param1:Object, param2:int = 0, param3:String = "")
        {
            var _loc_6:* = undefined;
            var _loc_7:String = null;
            var _loc_4:String = "";
            var _loc_5:int = 0;
            while (_loc_5 < param2)
            {
                
                _loc_4 = _loc_4 + "\t";
                _loc_5++;
            }
            for (_loc_6 in param1)
            {
                
                param3 = param3 + (_loc_4 + "[" + _loc_6 + "] => " + param1[_loc_6]);
                _loc_7 = traceObject(param1[_loc_6], (param2 + 1));
                if (_loc_7 != "")
                {
                    param3 = param3 + (" {\n" + _loc_7 + _loc_4 + "}");
                }
                param3 = param3 + "\n";
            }
            if (param2 == 0)
            {
            }
            else
            {
                return param3;
            }
            return;
        }// end function

        public static function fbTrace(param1:String, param2:Object) : void
        {
            traceObject(param2);
            return;
        }// end function

    }
}

...and postMessage.as:
package flashutils
{
    import flash.events.*;
    import flash.external.*;
    import flash.net.*;
    import flash.utils.*;

    public class PostMessage extends Object
    {
        private var currentDomain:String;
        private var callback:String;
        private var connection:LocalConnection;
        private var connectionName:String;

        public function PostMessage()
        {
            this.connection = new LocalConnection();
            this.connection.client = this;
            this.connection.connect(Math.random().toString());
            ExternalInterface.addCallback("postMessage_init", this.init);
            ExternalInterface.addCallback("postMessage_send", this.send);
            return;
        }// end function

        public function getCurrentDomain() : String
        {
            if (!this.currentDomain)
            {
                try
                {
                    this.currentDomain = ExternalInterface.call("self.document.domain.toString");
                    this.fbTrace("getCurrentDomain", {currentDomain:this.currentDomain});
                }
                catch (e)
                {
                    logError("getCurrentDomain error", e);
                }
            }
            return this.currentDomain;
        }// end function

        public function onFacebookDomain() : Boolean
        {
            return /(^|\.)facebook\.com$""(^|\.)facebook\.com$/.test(this.getCurrentDomain()) || /(^|\.)fbcdn\.net$""(^|\.)fbcdn\.net$/.test(this.getCurrentDomain());
        }// end function

        public function init(param1:String, param2:String) : void
        {
            var cb:* = param1;
            var name:* = param2;
            this.fbTrace("init", {cb:cb, name:name});
            try
            {
                if (!this.onFacebookDomain() && PostMessage.extractDomain(name) != this.getCurrentDomain())
                {
                    this.logError("init", "name must be a URL on the current domain: " + name);
                }
                else
                {
                    this.callback = cb;
                    if (name == this.connectionName)
                    {
                        return;
                    }
                    this.connectionName = name;
                    name = encodeURIComponent(name);
                    this.connection = new LocalConnection();
                    this.connection.client = this;
                    this.connection.connect(name);
                }
            }
            catch (e)
            {
                logError("init", e.toString());
            }
            return;
        }// end function

        public function send(param1:String, param2:String) : void
        {
            var msg:* = param1;
            var name:* = param2;
            this.fbTrace("send", {name:name, msg:msg});
            if (!this.connection)
            {
                this.logError("send", "connection has not been initialized.");
                return;
            }
            try
            {
                name = encodeURIComponent(name);
                this.connection.send(name, "recv", msg);
            }
            catch (e)
            {
                logError("send", e.toString() + ". name: " + name + ", msg: " + msg);
            }
            return;
        }// end function

        public function recv(param1:String) : void
        {
            var deliverMessage:Function;
            var msg:* = param1;
            deliverMessage = function (event:TimerEvent) : void
            {
                var evt:* = event;
                try
                {
                    ExternalInterface.call(callback, encodeURIComponent(msg));
                }
                catch (e)
                {
                    logError("recv", e.toString());
                }
                return;
            }// end function
            ;
            this.fbTrace("recv", {msg:msg});
            var timer:* = new Timer(1, 1);
            timer.addEventListener(TimerEvent.TIMER, deliverMessage);
            timer.start();
            return;
        }// end function

        private function logError(param1:String, param2:Object) : void
        {
            XdComm.fbTrace("Error: XdComm.PostMessage." + param1, param2);
            return;
        }// end function

        private function fbTrace(param1:String, param2:Object) : void
        {
            XdComm.fbTrace("XdComm.PostMessage." + param1, param2);
            return;
        }// end function

        public static function extractDomain(param1:String) : String
        {
            return /^\w+:\/\/([^\/:]*)""^\w+:\/\/([^\/:]*)/.exec(param1)[1];
        }// end function

    }
}

Thursday, June 23, 2011

Daily snapshots of Facebook Connect Library

Nathan Friedly from Sociable Labs put together a set of utils to grab daily snapshots of Facebook's connect.js here:

https://github.com/nfriedly/connect-js

One thing you can do to keep yourself informed of changes that Facebook makes is to fork the repo, git clone the repo to your dev server, install the crontab script, and put a GitHub post-receive email hook to notify you of all changes.  Then the changes made by Facebook can more quickly diagnosed by developers like you!



Often times Facebook only updates the Unix timestamp on the file, so here is a revised version of the script to only download/update the all_deminified.js file if there is more than one line of change made.
#!/bin/bash  -ex
cd `dirname $0`
TODAY=`date --rfc-3339=date`
JS_OUTPUT="js/all_${TODAY}.js"
if [ ! -d "js" ] 
then
  mkdir "js"
fi
/usr/bin/wget https://connect.facebook.net/en_US/all.js -O ${JS_OUTPUT}
/usr/bin/python jsbeautifier.py -o all_deminified.js ${JS_OUTPUT}
# Avoid sending out unnecessary updates if only the timestamp has changed.
ALL_JS_DIFF=`git diff --shortstat all_deminified.js | grep -v "1 insertions"`
if [ ! -z "$ALL_JS_DIFF" ]                            
then                                                  
  echo "Commit has changed..."                        
  git --no-pager diff . # Just to see what changed...turn off the pager.
  /usr/bin/git add all_deminified.js
  /usr/bin/git commit -m "Facebook Connect changes for $TODAY"
  /usr/bin/git push origin
fi                     

Since the GitHub post-receive hook does not provide nice friendly diffs, we can also add a post-commit hook in our local repository and use the Ruby git-commit notifier to notify us whenever a change has been made. This script dumps out last two revisions made in the commit(older to newer revision first). Since the git-commit-notifier will only trigger if there is something more than the timestamp, we should expect diff emails only when there is a more substantial change made in the Facebook JavaScript. You have to setup your own git-commit-notifier.yml and point it to an SMTP host (it apparently does not work with Gmail hosts since STARTTLS may be required).

vi .git/hooks/post-commit
#!/bin/bash -ex
HASH_DIFF=`git log --pretty=%H --reverse | tail -2 | xargs echo`
git-commit-notifier /home/rhu/fb/connect-js/git-commit-notifier.yml ${HASH_DIFF} refs/heads/master

Useful command to examine overall network stats

netstat -nat | awk '{print $6}' | sort | uniq -c | sort -n

Wednesday, June 22, 2011

How Facebook's xd_proxy.php seemed to have broken IE8...

The problem related to IE8, 32-bit and 64-bit versions of Internet Explorer with Flash installed when invoking the FB.getLoginStatus() call. Facebook recently broke IE8 with a non-closing xd_proxy.php. I just did a diff compare between the last set of changes today from a 6/17 snapshot and noticed this line change:
<         document.XdComm.postMessage_init('FB.XD.Flash.onMessage', FB.XD._origin);
---
>         document.XdComm.postMessage_init('FB.XD.Flash.onMessage', FB.XD._openerOrigin ? FB.XD._openerOrigin : FB.XD._origin);
But why would this line do anything? The issues point to problems with IE8 (not IE7) and Flash, which may explain why I didn't see any issues on IE7. IE9, which I was also running on my desktop but did not have Flash installed, may also explain why I didn't encounter the problem.

Facebook relies on the HTML5 postMessage functionality to send cross-browser JavaScript code between different window browsers visiting different domains. For Firefox/Chrome, HTML5 support works great to pass JavaScript code from Facebook and inject it into your local app. But with IE8, the postMessage seems to have issues with popups so Facebook has gone for two different approaches, one with a Flash widget that mimics the same behavior as postMessage and using IFrame's. The latter works for IE with no Flash, the former requires a minimum of Adobe Flash 9.0.159.0 or 10.0.22.87.

Well, it turns out Facebook on the last push last week added this section of code, which tries to handle specific cases of using IE8:
var a = !! window.attachEvent;
    if (FB.XD._transport != 'postmessage' && a && window.postMessage) {
      FB.XD._openerTransport = FB.XD._transport;
      FB.XD._openerOrigin = FB.XD._origin;
      FB.XD._nonOpenerOrigin = d;
    }
(The method of transport is 'flash', it supports the window.attachEvent, and two !! means double-negated, and IE8 apparently does support window.postMessage):

...which is part of this section:
var d = (window.location.protocol + '//' + window.location.host + '/' + FB.guid());
  if (window.addEventListener && !window.attachEvent && window.postMessage) {
      FB.XD._origin = d;
      FB.XD.PostMessage.init();
      FB.XD._transport = 'postmessage';
    } else if (!b && FB.Flash.hasMinVersion()) {
      if (document.getElementById('fb-root')) {
        var c = document.domain;
        if (c == 'facebook.com') c = window.location.host;
        FB.XD._origin = (window.location.protocol + '//' + c + '/' + FB.guid());
        FB.XD.Flash.init();
        FB.XD._transport = 'flash';
      } else {
        if (FB.log) FB.log('missing fb-root, defaulting to fragment-based xdcomm');
        FB.XD._transport = 'fragment';
        FB.XD.Fragment._channelUrl = b || window.location.toString();
      }
    } else {
      FB.XD._transport = 'fragment';
      FB.XD.Fragment._channelUrl = b || window.location.toString();
    }
    var a = !! window.attachEvent;
    if (FB.XD._transport != 'postmessage' && a && window.postMessage) {
      FB.XD._openerTransport = FB.XD._transport;
      FB.XD._openerOrigin = FB.XD._origin;
      FB.XD._nonOpenerOrigin = d;
    }

(Background info: The line (c == 'facebook.com') checks to see if the window you're opening is www.facebook.com, which the JavaScript on their site seems to set document.domain to facebook.com (There is JS in their site that sets document.domain=window.location.hostname.replace(/^.*(facebook\..*)$/i,'$1')). If it is, then it sets the c variable to www.facebook.com (or any other facebook.com site you're looking at). Otherwise, it sets the origin location from document.domain, which is usually the page you're loading.

Also, within their cross-domain handler (xdHandler), Facebook also added these lines:
if (FB.XD._openerTransport) if (e == 'opener') {
FB.XD._transport = FB.XD._openerTransport;
FB.XD._origin = FB.XD._openerOrigin;
} else {
FB.XD.PostMessage.init();
cFB.XD._transport = 'postmessage';
FB.XD._origin = FB.XD._nonOpenerOrigin;
}
So XD._origin can be either FB.XD_openerOrigin or FB.XD_nonOpenerOrigin....in this latest change that broke IE8, it caused some issue. In the latest revision, they gave higher precedence to FB.XD_openerOrigin, since FB.XD._origin can switch between the two different values.

The trouble is created if getLoginStatus() is invoked before the Facebook Connect button is clicked, since the Flash receiver is expecting the value of FB.XD._origin when it was first initialized. The FB.guid() calls basically generate a random 16-character string, which explains why there are two different string values for FB.XD._openerOrigin and FB.XD._nonOpenerOrigin.

FB.XD._origin = (window.location.protocol + '//' + c + '/' + FB.guid());
        FB.XD.Flash.init();
But since getLoginStatus creates three different callback routines, XD._origin can be overwritten:
e = FB.Auth.xdHandler;
      FB.copy(a.params, {
        no_session: e(b, c, 'parent', false, 'notConnected'),
        no_user: e(b, c, 'parent', false, 'unknown'),
        ok_session: e(b, c, 'parent', false, 'connected'),
You can confirm this issue by putting breakpoints before/after the xdHandler no_session, no_user, and ok_session callbacks are initialized:
>>FB.XD._openerOrigin
"http://dev.myhost.com/f27227ed548279c"
>>FB.XD._origin
"http://dev.myhost.com/f1b7fa085f189da"

...so when the Facebook Connect button is loaded with the Flash cross-domain receiver, FB.XD._origin is not its original value:
FB.Flash.onReady(function() {
        document.XdComm.postMessage_init('FB.XD.Flash.onMessage', FB.XD._origin);
      });
Presumably the message passed to the Flash receiver is used to verify that the 'opener' succeeded, triggering the Flash callbacks (FB.XD._callbacks) to continue. Since the FB.XD._origin is different than the one that was used at initialization, the Flash XD receiver seems not to fire (I don't have the .swf file disassembled, so can't confirm).

Subsequently, the entire sign-on process fails. The problem did not happen until last week when the connect.js library began to change FB.XD._origin within the xdHandler() code, which was not caught apparently because it was not tested with getLoginStatus() before deploying.

(For background info only: The FB.XD._origin also gets passed into the URL query string when you click the Facebook connect, and usually it gets checked by Facebook to see if it's a valid origin URL in your Facebook app. You could login to your Facebook Connect site (if you host on myhost.com and try through myotherhost.com) and observe the same phenemonon with the XD proxy. Now Facebook warns you with a "Given URL is not allowed by the Application configuration." Regardless, the FB.XD._origin seems to be used by the Flash callback routine to determine whether to fire).

So the lesson learned is that in your browser testing, you have to cover all bases with checking against IE7/IE8/IE9 with and without Flash (minimum of 9.0.159.0 or 10.0.22.87 -- yes, the code seems supports a minimum of either of both versions). You can disable/enable the Adobe/Shockwave Flash inside the Add-Ons section of Internet Explorer. If you have customer support issues, you can also point them to this link http://www.adobe.com/software/flash/about/(or shortened to http://adobe.ly/9uzSR3) to help figure out what Flash version customers are using. Windows 7 comes with both the 32-bit and 64-bit on Internet Explorer, so you can run both to test (though keep in mind there is only a 64-bit preview Flash release on Adobe's web site).

One thing I've also done is added a cronjob task to download versions of Facebook Connect so that you can deminify and compare diffs. This approach will hopefully let developers stay on top of Facebook Connect changes. You should also download a JavaScript compiler and deminify the code so that the diffs are easy to compare!
# m h  dom mon dow   command
0 2 * * 0 bash -c "/usr/bin/wget https://connect.facebook.net/en_US/all.js -O ~/js/all_`date --rfc-3339=date`.js"

Also, in case you want the last 4 snapshots of the all.js library, I've posted them here:

http://bit.ly/mhH6re
The files are as follows:
all.js.4.deminify is the one that fixed the issue.
all.js.3.deminify created the IE8 bug.
all.js.2.deminify was the previous version before it all happened.

You can look at the timestamps of each file at the top:
import datetime
datetime.fromtimestamp(1308323215)
>>> datetime.datetime.fromtimestamp(1308323215)
datetime.datetime(2011, 6, 17, 8, 6, 55)
You can also setup your own ability to track Facebook Connect changes too (thanks to Nate Friedly):
http://hustoknow.blogspot.com/2011/06/daily-snapshots-of-facebook-connect.html

Want to know more? More details too about Facebook's SWF Flash cross-domain plugin....
http://hustoknow.blogspot.com/2011/06/deconstructing-facebooks-flash-cross.html

Wednesday, June 15, 2011

ssh-agent and agent forwarding on Windows machines

Ubuntu and MacOS machines have things setup already for ssh-agent forwarding. For Ubuntu, whenever you open a terminal, the script in /etc/X11/Xsession.d/90x11-common_ssh-agent starts things up, meaning that all of you have to do is enable AgentForwarding in the /etc/sshd/ssh_config or your personal ~/.ssh/ssh_config to get things to work:
STARTSSH=
SSHAGENT=/usr/bin/ssh-agent
SSHAGENTARGS=
STARTUP="$SSHAGENT $SSHAGENTARGS ${TMPDIR:+env TMPDIR=$TMPDIR} $STARTUP"
To get SSH agent forwarding to work, you therefore must have ssh-agent running before you connect to a server (you can type set | grep SSH_AUTH_SOCK to verify). To verify that the server to which you connect also has agent forwarding, do another set | grep SSH_AUTH_SOCK. Sometimes it can get confusing depending on your server configuration (i.e. if some of your boxes are only setup to accept public/private key exchanges instead of passwords).

On Windows 7/XP machines, it's a bit more complicated. If you're using Cygwin/OpenSSH, you can store your private/public key inside your /home/<username>/.ssh and add the following section
(taken from http://computercamp.cdwilson.us/post/638356902) to run ssh-agent, direct the output to a /.ssh/environment file, and then setting the environment variables so that subsequent SSH calls will use the SSH_AUTH_SOCK environment (SSH_AGENT_PID can be used for killing the PID of the process as shown in the code below.
SSH_ENV=”${HOME}/.ssh/environment”
SSHAGENT=/usr/bin/ssh-agent
SSHAGENTARGS=”-s”

function start_agent {
     echo “Initialising new SSH agent…”
     ${SSHAGENT} | sed ‘s/^echo/#echo/’ > “${SSH_ENV}”
     echo succeeded
     chmod 600 “${SSH_ENV}”
     . “${SSH_ENV}” > /dev/null
     ssh-add
}

# Source SSH settings, if applicable

if [ -f “${SSH_ENV}” ]; then
     . “${SSH_ENV}” > /dev/null
     ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ > /dev/null || {
         start_agent;
     }
else
     start_agent;
fi

One thing I did have to do is to add a "ssh-add" at the last line of the start_agent code, which will cause the program to look inside the /home/username/.ssh/id_rsa for the private key. If you have the file stored in a different place, you can provide the full pathname with ssh-add. The "ssh-add -l" command will also verify which keys have been added. You should see the MD5 checksum of the private/public key that was added.

Also, SecureCRT apparently has a more tightly bound use of ssh-agent by relying on protected shared memory to implement its key agent, so you should be able to provide your SSH private key, enable the "Add Keys to Agent" and "Enable Agent Forwarding" options and things should work without this extra nuisance of invoking ssh-agent on bash startup.
http://www.vandyke.com/support/tips/agent_forwarding.html

SecureCRT integrates its key agent much more tightly than most other SSH clients, using a bit of protected system memory shared by all instances of SecureCRT, the Activator in the system tray, and the command-line clients. Once a passphrase has been unlocked by one client, agent services remain available to all of them. This includes the SecureFX product as well.

WiFi Analyzer

http://www.metageek.net/products/inssider/download/

Sunday, June 12, 2011

Install DCP-7040 on Ubuntu 64-bit..

1. sudo apt-get install brother-cups-wrapper-laser

2. Plug the printer into the USB slot. When asked to install a Printer, use the DCP-7020 (no 64-bit driver exists for the DCP-7040) and select the first option for the Brother DCP-7020 foomatic/hl1250 (first option). The ljet4 (third option) will only cause you to print blank pages.

3. Go to http://welcome.solutions.brother.com/bsc/public_s/id/linux/en/index.html and install brscan/brscan2/brscan3 depending on the Brother unit. In this case, brscan3 is what is needed.

4. sudo dpkg -i /tmp/brscan3-0.2.11-4.amd64.deb

Friday, June 10, 2011

Using Facebook's Batch API...

If you've built any type of Facebook API integration, chances are that you will gravitate towards batching up your API network requests. Since you are allowed a maximum of 20 requests per batch, you can effectively speed-up your overall performance by 14-15x (my own basic measurements) There may also be overall throttle limits to consider, but in general, the performance is a huge boost from sending individual network calls for each request.

While the API documentation is posted at http://developers.facebook.com/docs/api/batch/, there are several things that aren't well documented.

    1. You must provide some type of access token to issue Facebook Batch API requests. Even if you have access_token within the query strings of each individual request, Facebook still expects you to provide some type of access_token= in the POST request. You may encounter 404 errors without providing the access_token parameter. The access_token= parameter can either be a user OR application token. While there may be situations where you want to use a user token, what if you wanted to fetch multiple pages with different user tokens? What if the user's token has expired or he/she has changed the password, invalidating this token? For these situations, you can use the application token (http://developers.facebook.com/docs/authentication/), using the technique described in the "App Login" section. The code in Python looks like the following:
    def fb_get_app_token():
        """ For Facebook API requests, we need to get an app token to make Graph API calls.  We can't                                                                                                                       
        use fb_get_with_params() since it expects either/and a user/asset, which we don't use.  Also,                                                                                                                       
        the response from Facebook isn't in JSON format so we have to regex parse it."""
        try:
            token_request = urllib.urlencode({ 'client_id': settings.FACEBOOK_APP_ID,
                                               'client_secret': settings.FACEBOOK_SECRET_KEY,
                                               'grant_type': 'client_credentials'
                                               })
    
            token = urllib2.urlopen("https://graph.facebook.com/oauth/access_token?%s" % token_request).read()
        except urllib2.HTTPError, e:
            logging.debug("Exception trying to grab Facebook App token (%s)" % e)
            return None
    
        matches = re.match(r"access_token=(?P.*)", token).groupdict()
        return matches.get('token', '')
    
    You can then use this access_token as the fallback access token. 2. Buried within the Batch API section is the "Specifying different access tokens for different operations". The documentation states that the "access_token should be query string or form post parameter". There isn't an example of how to use it, but basically the access_token should be set in the relative_url path.
    curl -F ‘access_token=…’ \
         -F ‘batch=[{ “method”: ”GET”, \
                      "relative_url": "me?access_token=ACCESS_TOKEN1", \
                    }, \
                    { "method": "GET", \
                      "relative_url": "124456?access_token=ACCESS_TOKEN2", \
                    }]'
    
    If you try to put a separate "access_token" : "ABC", Facebook won't recognize the token. You must put it in the query string for GET requests:
    { "method": "GET", \
                      "access_token" : ACCESS_TOKEN,
                      "relative_url": "124456?access_token=ACCESS_TOKEN2", \
                    }]'
    
    3. The JSON dictionary that is returned must be JSON-decoded twice: once to retrieve the overall response, and then another to decode the 'body' key. The JSON decoder in Python doesn't seem to know how to decode nested dictionaries.

Printing on Windows 64 with the Konica Minolta PagePro 1350W

Konica apparently does not have a native Windows 64-bit driver, which apparently is required because the Minolta PagePro 1350W is sort of like a WinModem in that it contains no PostScript/PCL processor. Therefore, while the company apparently provides univeral drivers, http://kmbs.konicaminolta.us/content/support/universaldrivers.html, none of these will work. If you try to print to the 1350W, you'll find that the green LED lights up but nothing comes out.

The explanation is described here:

http://www.printerforums.net/substitute-driver-konica-minolta-pagepro-1350w-t4848.html

Instead, the Windows GDI display information is passed to your Windows
print driver--which in this case is actually the RIP, the raster image
processor itself for this marking engine. Once the RIP is finished (it
uses your host computer's power to do all the work in turning your
document into a laser bitmap), it sends the bitmap to the marking engine
over the USB cable.

The workaround is to use a WinXP machine as the print server, and install the PagePro 1400W on your Windows 7 machine (http://printer.konicaminolta.com/support/current_printers/PP1400W_sup.htm#Drivers) You must also disable Bidirectional Printing since the printer driver tries to get an acknowledgment back from the printer before printing the full document. The steps to setup the print server is described below:

http://tiredblogger.wordpress.com/2007/03/11/howto-pagepro-1350w-on-x64-vista/

Thursday, June 2, 2011

jQuery v1.4 and live handlers on IE

This bug report explains why setting 'disabled' on elements through IE were not being disabled...works for elements that use bind() but not live()....it has been fixed since 1.5. It explains a lot why setting 'disabled' to work on Firefox/Chrome but still has issues on IE. Upgrading to 1.5/1.6 should resolve the issue, though the Ajax module has been completely rewritten to allow for chaining callback routines (see http://blog.jquery.com/2011/01/31/jquery-15-released/).

http://bugs.jquery.com/ticket/6911

jQuery 1.4.3:
function liveHandler( event ) {
 var stop, maxLevel, elems = [], selectors = [],
  related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
  events = jQuery.data( this, this.nodeType ? "events" : "__events__" );

 if ( typeof events === "function" ) {
  events = events.events;
 }

 // Make sure we avoid non-left-click bubbling in Firefox (#3861)
 if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) {
  return;
 }

jQuery 1.5.2:
function liveHandler( event ) {
    var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
        elems = [],
        selectors = [],
        events = jQuery._data( this, "events" );

    // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)                                                                                                                                     
    if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
        return;
    }

Wednesday, June 1, 2011

htmlStorage bug in Facebook's new SDK ..

Facebook must have rolled out a new SDK that breaks IE7. You can't see the problem in IE8/IE9 and simulating the bug within the browser since they already support the localStorage object. You have to load an IE7 browser to see the issue:

http://bugs.developers.facebook.net/show_bug.cgi?id=18039

If you put this line in your JavaScript code before the Facebook code loads,

if (typeof(localStorage) === "undefined") {
  var localStorage = {};
}

The problem appears to happen inside the FB.init code:

FB.provide('', {
  initSitevars: {},
  init: function(a) {
    FB.TemplateData.init();
    FB.Event.subscribe('auth.sessionChange', FB.TemplateData.update);

Commenting out the FB.TemplateData.init() and FB.TemplateData.update code fixes things..