Saturday, June 25, 2011

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....

No comments:

Post a Comment