Integrating Twitter into your Windows Mobile Widget

I guess the first question that comes to mind is why would you want to integrate twitter into your widget. Until recently I would have agreed that it seems somewhat superfluous as there is an almost infinite number of Twitter clients out there, all claiming that they are better than all that have gone before them. However, I was recently trying to add a mechanism for commenting on items within a widget I was writing.  Now I could have easily built yet another commenting system, involving the usual candidates of sql server backend, wrapped in a simple REST or SOAP service blah blah blah, but I came to the conclusion this would simply add to the overhead of managing the application, particularly since up until now the widget didn’t rely on any backend services. I figured since a lot of users would probably have Twitter accounts I could just let them use their account in order to make comments. The fact that their comments would be seen in their Twitter feed would just add some free advertising for my widget.


So, having chosen Twitter to handle comments I then had to work out the best way to integrate them. If you’ve used any of the current generation of Twitter clients out there you will be familiar with having to enter your username and password into the application – this is bad as you don’t really have any idea what else that application may be doing with your credentials. Of course, they would argue that they need to prompt for this information so that they can post on your behalf. Up until recently they would have been correct. However, Twitter now supports OAuth and in particular a permutation specifically designed to support desktop (or rich client) applications. In a nutshell this involves routing the user to Twitter, getting them to sign in there and then routing back to the calling application with a suitable authorisation token.  For the desktop permutation there is an additional step which involves copying a one-time pin from Twitter into the calling application – this is because there is no way for Twitter to redirect back to your desktop application. This process can be used for integrating Twitter into your widget.


Now for the technical bits:


Step 1: Registration



  • You need to register your application with Twitter.  This can be done at http://twitter.com/oauth_clients.  Once registered you will be given a Consumer Key and Consumer Secret that you will need to take note of for use within your widget.

Step 2: Request Token



  • The first step in OAuth is to get a request token.  This is done by issuing a POST to http://twitter.com/oauth/request_token, supplying the consumer key you have been allocated, a timestamp and nonce value. The request also needs to contain a SHA1 signature.

  • The return values (if successful) is a token and a token secret.

Step 3: Direct User to Twitter to Sign In



  • Once your widget has the request token and associated secret, you need to direct the user to Twitter.com so that they can log in and authorise your widget.  To do this, direct them to “http://twitter.com/oauth/authorize?oauth_token= + Token + & oauth_callback=oob”

  • The user will log in and then be given an authorisation pin.  They need to copy this and paste it (or type it) into your widget – you’ll need to give them a mechanism for doing this!

Step 4: Access Token



  • After the user has entered the authorisation pin you need to use it, coupled with the consumer key and request token to issue a POST to http://twitter.com/oauth/access_token.

  • This will return an access token that you can then use to post updates to Twitter.

Step 5: Post Updates



Acknowledgments to John Kristian who contributed a Javascript Library for OAuth.  Other OAuth libraries are available for a variety of languages at http://oauth.net/code.


Full TwitterAPI Listing


Feel free to use this code.  Note however that there is almost no error handling so that it’s easier to follow what’s going on.



var TwitterAPI = function() {
    return {
        ConsumerSecret: “<enter Consumer Secret from Twitter app registration>”,
        ConsumerKey: “<enter Consumer Key from Twitter app registration>”,
        SignatureMethod: “HMAC-SHA1”,


        RegisterUser: function(callback, errorcallback) {


            var accessor = { consumerSecret: TwitterAPI.ConsumerSecret
                           , tokenSecret: “”
            };
            var message = { action: “
http://twitter.com/oauth/request_token”
                  , method: “POST”
                  , parameters: []
            };
            message.parameters.push([“oauth_consumer_key”, TwitterAPI.ConsumerKey]);
            message.parameters.push([“oauth_signature_method”, TwitterAPI.SignatureMethod]);
            message.parameters.push([“oauth_timestamp”, “”]);
            message.parameters.push([“oauth_nonce”, “”]);
            message.parameters.push([“oauth_signature”, “”]);
            OAuth.setTimestampAndNonce(message);
            OAuth.SignatureMethod.sign(message, accessor);
            var parameterMap = OAuth.getParameterMap(message.parameters);


            var params = TwitterAPI.buildParameters(parameterMap);


            var request = WidgetAPI.createXmlHttpRequest();
            request.open(message.method, message.action, true);
            request.setRequestHeader(“Content-type”, “application/x-www-form-urlencoded”);
            request.setRequestHeader(“Content-length”, params.length);
            request.setRequestHeader(“Connection”, “close”);


            // Set the callback
            request.onreadystatechange = function() {
                if (request.readyState == 4) {


                    var params2 = request.responseText.split(“&”);
                    for (var i = 0; i < params2.length; i++) {
                        var bits = params2[ i ].split(“=”);
                        if (bits.length == 2 && bits[0] == “oauth_token”) {
                            TwitterAPI.Token = bits[1];
                        }
                        if (bits.length == 2 && bits[0] == “oauth_token_secret”) {
                            TwitterAPI.TokenSecret = bits[1];
                        }
                    }


                    if (!TwitterAPI.Token || TwitterAPI.Token == null) {
                        if (errorcallback) {
                            errorcallback();
                        }
                        return;
                    }


                    callback();


                    var url = “http://twitter.com/oauth/authorize?oauth_token=” + TwitterAPI.Token + “&oauth_callback=oob”;
                    window.open(url);
                    request = null;
                }
            };


            request.send(params);
        },
        AuthenticateUser: function(pin, callback, errorcallback) {
            TwitterAPI.Pin = pin;
            var accessor = { consumerSecret: TwitterAPI.ConsumerSecret
                           , tokenSecret: TwitterAPI.TokenSecret
            };
            var message = { action: “
http://twitter.com/oauth/access_token”
                  , method: “POST”
                  , parameters: []
            };
            message.parameters.push([“oauth_consumer_key”, TwitterAPI.ConsumerKey]);
            message.parameters.push([“oauth_token”, TwitterAPI.Token]);
            message.parameters.push([“oauth_signature_method”, TwitterAPI.SignatureMethod]);
            message.parameters.push([“oauth_verifier”, TwitterAPI.Pin]);
            message.parameters.push([“oauth_timestamp”, “”]);
            message.parameters.push([“oauth_nonce”, “”]);
            message.parameters.push([“oauth_signature”, “”]);
            OAuth.setTimestampAndNonce(message);
            OAuth.SignatureMethod.sign(message, accessor);
            var parameterMap = OAuth.getParameterMap(message.parameters);


            var params = TwitterAPI.buildParameters(parameterMap);


            var request = WidgetAPI.createXmlHttpRequest();
            request.open(message.method, message.action, true);
            request.setRequestHeader(“Content-type”, “application/x-www-form-urlencoded”);
            request.setRequestHeader(“Content-length”, params.length);
            request.setRequestHeader(“Connection”, “close”);


            // Set the callback
            request.onreadystatechange = function() {
                if (request.readyState == 4) {
                    var params2 = request.responseText.split(“&”);
                    for (var i = 0; i < params2.length; i++) {
                        var bits = params2[ i ].split(“=”);
                        if (bits.length == 2 && bits[0] == “oauth_token”) {
                            TwitterAPI.Token = bits[1];
                        }
                        if (bits.length == 2 && bits[0] == “oauth_token_secret”) {
                            TwitterAPI.TokenSecret = bits[1];
                        }
                    }


                    if (params2<2 || !TwitterAPI.Token || TwitterAPI.Token == null) {
                        errorcallback();
                        return;
                    }


                    callback();
                }
            };


            request.send(params);
        },
        PostComment: function(comment, callback, errorcallback) {
            accessor = { consumerSecret: TwitterAPI.ConsumerSecret
                   , tokenSecret: TwitterAPI.TokenSecret
            };
            message = { action: “
http://twitter.com/statuses/update.json”
          , method: “POST”
          , parameters: []
            };
            message.parameters.push([“oauth_consumer_key”, TwitterAPI.ConsumerKey]);
            message.parameters.push([“oauth_token”, TwitterAPI.Token]);
            message.parameters.push([“status”, comment]);
            message.parameters.push([“oauth_version”, “1.0”]);
            message.parameters.push([“oauth_signature_method”, TwitterAPI.SignatureMethod]);
            message.parameters.push([“oauth_timestamp”, “”]);
            message.parameters.push([“oauth_nonce”, “”]);
            message.parameters.push([“oauth_signature”, “”]);


            accessor.tokenSecret = TwitterAPI.TokenSecret;
            OAuth.setTimestampAndNonce(message);
            OAuth.SignatureMethod.sign(message, accessor);
            var parameterMap = OAuth.getParameterMap(message.parameters);


            message.action = “http://twitter.com/statuses/update.json”;
            params = TwitterAPI.buildParameters(parameterMap);
            //params += “&status=test”;
            request = WidgetAPI.createXmlHttpRequest();
            request.open(message.method, message.action, true);
            request.setRequestHeader(“Content-type”, “application/x-www-form-urlencoded”);
            request.setRequestHeader(“Content-length”, params.length);
            //request.setRequestHeader(“Connection”, “close”);


            // Set the callback
            request.onreadystatechange = function() {
                if (request.readyState == 4) {
                    callback();
                }
            }
            request.send(params);
        },
        buildParameters: function(parameterMap) {
            var params = “”;
            for (var p in parameterMap) {
                if (params.length > 0) {
                    params += “&”;
                }
                params += OAuth.percentEncode(p) + “=” + OAuth.percentEncode(parameterMap[p]) + “”;
            }
            return params;
        }
    };
} ();


var Url = {


    // public method for url encoding
    encode: function(string) {
        return escape(this._utf8_encode(string));
    },


    // public method for url decoding
    decode: function(string) {
        return this._utf8_decode(unescape(string));
    },


    // private method for UTF-8 encoding
    _utf8_encode: function(string) {
        string = string.replace(/rn/g, “n”);
        var utftext = “”;


        for (var n = 0; n < string.length; n++) {


            var c = string.charCodeAt(n);


            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }


        }


        return utftext;
    },


    // private method for UTF-8 decoding
    _utf8_decode: function(utftext) {
        var string = “”;
        var i = 0;
        var c = c1 = c2 = 0;


        while (i < utftext.length) {


            c = utftext.charCodeAt(i);


            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if ((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i + 1);
                c3 = utftext.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }


        }


        return string;
    }


}

Leave a comment