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